diff --git a/.github/.pyspelling.yml b/.github/.pyspelling.yml new file mode 100644 index 000000000..512d9a26d --- /dev/null +++ b/.github/.pyspelling.yml @@ -0,0 +1,24 @@ +matrix: +- name: Proto + aspell: + lang: en + d: en_US + camel-case: true + sources: + - ./*.proto + dictionary: + wordlists: + - .github/spelling_custom_words_en_US.txt + output: build/dictionary/proto.dic + pipeline: + - pyspelling.filters.context: + context_visible_first: false + escapes: '\\[\\`~]' + delimiters: + - open: '(?s)^(?P *\/\/)' + close: '^(?P=open)$' + - pyspelling.filters.html: + comments: true + attributes: + - title + - alt diff --git a/.github/spelling_custom_words_en_US.txt b/.github/spelling_custom_words_en_US.txt new file mode 100644 index 000000000..ee6e0358f --- /dev/null +++ b/.github/spelling_custom_words_en_US.txt @@ -0,0 +1,390 @@ +abc +absteigen +adas +ADR +affine +agbl +al +Allgemeine +Altenheim +Anfang +Anglais +anlage +Anlieger +antecessor +arg +asam +atsc +auch +auf +auml +Ausfahrt +außer +Baugebiet +Baustelle +Baustellen +Baustellenausfahrt +bbcenter +Beauce +befahrbar +befahren +Begriffe +bei +beleuchtung +Benrath +Bereich +Besondere +Bewohner +BGBl +bggr +Biblio +bicyclesback +bildgebendes +bool +boolean +Botts +bp +br +britannica +candela +cartesian +Cattles +cattles +cd +cellpadding +cellspacing +centerline +Cianciaruso +CIE +cie +clothoid +cmyk +CNG +colspan +CONNECTINGRAMP +contraints +coord +courtneystrong +Crabb +css +customizable +dataset +daytimes +dBm +de +delineator +dem +der +des +detections +Deutsches +discretized +dms +doi +doppler +drivable +dt +Duesseldorf +Durchgangsverkehr +dvr +dürfen +easting +EBIKES +edn +edu +egm +eilvterm +ein +Einheiten +Elektrotechnik +ellpadding +Elsevier +Encyclopaedia +endcode +Ende +Endeninch +endlink +endrules +engl +enum +enums +eoas +epochconverter +erlaubt +et +euler +EV +executability +Fachthemen +Fahrbahn +Fahrbahnbelag +Fahrbahnmarkierung +Fahrverhalten +Fahrzeugdynamik +Fak +fcm +fehlende +Feiertagen +Flächen +flächen +fmu +Formelsammlung +Formelzeichen +fov +fp +Frei +frei +fuer +fullreports +ge +geb +gebührenpflichtig +Gedeon +Gefahrenzeichen +gekennzeichneten +gekennzeichneter +Gelegenheitsverkehr +Geospatial +gesetze +geändert +github +GNSS +grayscale +gridded +Groessen +GroundTruthInit +Grüne +gulleys +haben +Hafengebiet +Hagen +halten +hier +Hochwasser +href +hsv +htm +html +http +https +hypot +ietf +illuminance +ilv +im +Immission +incrementing +Industriegebiet +innerhalb +Institut +interoperable +Intl +io +IRI +iso +itu +jpg +Karamihas +Karlsruhe +keine +kfz +Krankenhaus +Kreis +kreuzt +Kuppe +Ladevorgangs +Ladezone +Lambertian +Laperriere +latencies +Lichttechnik +lidar +lidar's +Linksabbieger +lm +lte +luminance +luv +lx +Lärmschutz +Mainz +Meteorologische +metoffice +milliwatt +mit +Modellierung +MULTITRACK +nd +nde +NEBEL +Nebenstrecke +neuer +nicht +Nicolaus +noao +NONDRIVING +Normung +northing +Nr +ns +num +Nur +nur +oder +OFFRAMP +oktas +ONEWAY +onlinepubs +ONRAMP +opendrive +opengroup +openscenario +optischen +Ordnung +osi +OSI's +osmp +Ouml +ouml +parametrization +parametrize +Parkausweis +Parken +Parkflächen +Parkschein +Parkstände +Paulat +pdf +png +Polizeikontrolle +polyline +positionally +POSIX +powertrain +pre +preannouncement +priori +proj +proto +protobuf +publikationen +pubrec +px +QLTkit +radiocommunication +Rauch +rbd +rcs +rdquo +README +Reißverschluss +rfc +rgb +rgbir +rggb +Richtzeichen +rmse +Rollsplitt +Rosenberger +räumen +Sa +SAE +sae +samstags +Schienenfahrzeuge +Schleudergefahr +Schlupf +Schulbus +Schule +Schulweg +Seitenstreifen +SEMITRACTOR +signalway +sl +snr +Sonn +specular +Sprengarbeiten +Spurrinnen +sr +sslnet +Steinburg +steradian +Strahlungsphysik +Strassenfahrzeuge +Strassenverkehrs +strassenverkehrsordnung +Straßenschaden +StVO +stvo +StVZO +subtype +subtypes +superelevation +svg +szlig +Tait +TBD +td +tf +tf +th +Thibodeau +tml +todo +trilateration +tt +tupel +ubc +ubc +uint +uk +umich +umtri +und +Unfall +UNGATED +Universitaet +unix +uri +uuml +vda +verkehr +Verkehrseinrichtungen +Verkehrsf +Verkehrsführung +Verkehrstechnik +verkehrszeichen +Verschmutzte +Vorfahrt +Vorschriftzeichen +Vorweg +vz +VzKat +vzkat +wa +Welle +Wellenausbreitung +Wendemöglichkeit +werden +werktags +Wernli +wgs +Wiesbaden +wikipedia +Wilster +wp +wsdot +www +während +xrat +XXXX +xy +xyz +xz +Zeitschrift +Zou +Zufahrt +Zuflussregelung +Ölspur +überholt diff --git a/.github/workflows/antora-generator.yml b/.github/workflows/antora-generator.yml index 251478266..6e38c9126 100644 --- a/.github/workflows/antora-generator.yml +++ b/.github/workflows/antora-generator.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Trigger generator if: ${{ env.MUP_KEY != '' }} - uses: peter-evans/repository-dispatch@v2 + uses: peter-evans/repository-dispatch@v3 with: token: ${{ secrets.MACHINE_USER_PAT }} event-type: antora-build-trigger diff --git a/.github/workflows/protobuf.yml b/.github/workflows/protobuf.yml index c719a68ce..e0398dc6c 100644 --- a/.github/workflows/protobuf.yml +++ b/.github/workflows/protobuf.yml @@ -1,11 +1,26 @@ name: ProtoBuf CI Builds +env: + PROTOBUF_VERSION: 3.20.1 + PROTOBUF_VARIANT: '-all' # Use '-all' prior to 22.0, '' after + ABSEIL_VERSION: 20230802.1 + on: push: pull_request: branches: [ master ] jobs: + spellcheck: + name: Spellcheck + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v4 + - uses: rojopolis/spellcheck-github-actions@0.36.0 + name: Spellcheck + with: + config_path: .github/.pyspelling.yml + build-proto2-linux64: name: Build Proto2 Linux 64 @@ -13,39 +28,60 @@ jobs: steps: - name: Checkout OSI - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: submodules: true + - name: Check Build Setup + run: | + ( result=0 ; for f in *.proto ; do grep -wq "$f" CMakeLists.txt || { echo "Missing $f in CMakeLists.txt" && let "result++"; } ; done ; exit $result ) + ( result=0 ; for f in *.proto ; do grep -q '"'$f'"' setup.py || { echo "Missing $f in setup.py" && let "result++"; } ; done ; exit $result ) + - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: '3.7' + python-version: '3.8' - name: Install Python Dependencies - run: python -m pip install --upgrade pip setuptools wheel pyyaml + run: | + python -m pip install --upgrade pip + python -m pip install build + python -m pip install -r requirements_tests.txt + + - name: Check black format + run: | + black --check --diff . - name: Install Doxygen run: sudo apt-get install doxygen graphviz - name: Cache Dependencies id: cache-depends - uses: actions/cache@v3 + uses: actions/cache@v4 with: - path: protobuf-3.20.1 + path: protobuf-${{ env.PROTOBUF_VERSION }} key: ${{ runner.os }}-v2-depends - - name: Download ProtoBuf + - name: Download ProtoBuf ${{ env.PROTOBUF_VERSION }} if: steps.cache-depends.outputs.cache-hit != 'true' - run: curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protobuf-all-3.20.1.tar.gz && tar xzvf protobuf-all-3.20.1.tar.gz + run: curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v${{env.PROTOBUF_VERSION}}/protobuf${{env.PROTOBUF_VARIANT}}-${{env.PROTOBUF_VERSION}}.tar.gz && tar xzvf protobuf${{env.PROTOBUF_VARIANT}}-${{env.PROTOBUF_VERSION}}.tar.gz - - name: Build ProtoBuf - if: steps.cache-depends.outputs.cache-hit != 'true' - working-directory: protobuf-3.20.1 + - name: Download Abseil ${{ env.ABSEIL_VERSION }} + if: steps.cache-depends.outputs.cache-hit != 'true' && env.PROTOBUF_VARIANT == '' + run: curl -OL https://github.com/abseil/abseil-cpp/archive/refs/tags/${{env.ABSEIL_VERSION}}.tar.gz && tar xzvf ${{env.ABSEIL_VERSION}}.tar.gz && rm -rf protobuf-${{env.PROTOBUF_VERSION}}/third_party/abseil-cpp && mv abseil-cpp-${{env.ABSEIL_VERSION}} protobuf-${{env.PROTOBUF_VERSION}}/third_party/abseil-cpp + + - name: Build ProtoBuf ${{ env.PROTOBUF_VERSION }} via autotools + if: steps.cache-depends.outputs.cache-hit != 'true' && env.PROTOBUF_VARIANT == '-all' + working-directory: protobuf-${{ env.PROTOBUF_VERSION }} run: ./configure DIST_LANG=cpp --prefix=/usr && make - - name: Install ProtoBuf - working-directory: protobuf-3.20.1 + - name: Build ProtoBuf ${{ env.PROTOBUF_VERSION }} via cmake + if: steps.cache-depends.outputs.cache-hit != 'true' && env.PROTOBUF_VARIANT == '' + working-directory: protobuf-${{ env.PROTOBUF_VERSION }} + run: cmake -DCMAKE_CXX_STANDARD=17 -Dprotobuf_BUILD_SHARED_LIBS=ON -Dprotobuf_BUILD_TESTS=OFF . && cmake --build . --config Release -j 4 + + - name: Install ProtoBuf ${{ env.PROTOBUF_VERSION }} + working-directory: protobuf-${{ env.PROTOBUF_VERSION }} run: sudo make install && sudo ldconfig - name: Install proto2cpp @@ -54,27 +90,31 @@ jobs: - name: Prepare C++ Build run: mkdir build - # Versioning - - name: Get versioning + - name: Add Development Version Suffix + if: ${{ !startsWith(github.ref, 'refs/tags') }} + run: | + echo "VERSION_SUFFIX = .dev`date -u '+%Y%m%d%H%M%S'`" >> VERSION + + - name: Get git Version id: get_version - run: echo ::set-output name=VERSION::$(git describe --always) + run: echo "VERSION=$(git describe --tags --always)" >> $GITHUB_OUTPUT - name: Prepare Documentation Build run: | sed -i 's/PROJECT_NUMBER\s*= @VERSION_MAJOR@.@VERSION_MINOR@.@VERSION_PATCH@/PROJECT_NUMBER = master (${{ steps.get_version.outputs.VERSION }})/g' doxygen_config.cmake.in - echo "EXCLUDE_PATTERNS = */osi3/* */protobuf-3.20.1/* */proto2cpp/* */flatbuffers/*" >> doxygen_config.cmake.in + echo "EXCLUDE_PATTERNS = */osi3/* */protobuf-*/* */proto2cpp/* */flatbuffers/*" >> doxygen_config.cmake.in echo "GENERATE_TREEVIEW = YES" >> doxygen_config.cmake.in - name: Configure C++ Build working-directory: build - run: cmake -D FILTER_PROTO2CPP_PY_PATH=$GITHUB_WORKSPACE/proto2cpp .. + run: cmake -D FILTER_PROTO2CPP_PY_PATH=$GITHUB_WORKSPACE/proto2cpp ${{ env.PROTOBUF_VARIANT =='' && '-DCMAKE_CXX_STANDARD=17' }} .. - name: Build C++ working-directory: build run: cmake --build . --config Release -j 4 - name: Build Python - run: python setup.py build && python setup.py sdist + run: python -m build - name: Install Python run: python -m pip install . @@ -84,13 +124,20 @@ jobs: - name: Archive Documentation if: ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: linux64-doc path: doc/html if-no-files-found: error - - name: deploy to gh-pages if push to master branch + - name: Upload Python Distribution + if: ${{ github.event_name == 'pull_request' || ( github.event_name == 'push' && ( github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') ) ) }} + uses: actions/upload-artifact@v4 + with: + name: python-dist + path: dist/ + + - name: Deploy to gh-pages if push to master branch if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} uses: peaceiris/actions-gh-pages@v3 with: @@ -104,36 +151,48 @@ jobs: steps: - name: Checkout OSI - uses: actions/checkout@v2 + uses: actions/checkout@v4 with: submodules: true - name: Setup Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: - python-version: '3.7' + python-version: '3.8' - name: Install Python Dependencies - run: python -m pip install --upgrade pip setuptools wheel pyyaml + run: | + python -m pip install --upgrade pip + python -m pip install build + python -m pip install -r requirements_tests.txt - name: Cache Dependencies id: cache-depends - uses: actions/cache@v2 + uses: actions/cache@v4 with: - path: protobuf-3.20.1 + path: protobuf-${{ env.PROTOBUF_VERSION }} key: ${{ runner.os }}-v2-depends - - name: Download ProtoBuf + - name: Download ProtoBuf ${{ env.PROTOBUF_VERSION }} if: steps.cache-depends.outputs.cache-hit != 'true' - run: curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protobuf-all-3.20.1.tar.gz && tar xzvf protobuf-all-3.20.1.tar.gz + run: curl -OL https://github.com/protocolbuffers/protobuf/releases/download/v${{env.PROTOBUF_VERSION}}/protobuf${{env.PROTOBUF_VARIANT}}-${{env.PROTOBUF_VERSION}}.tar.gz && tar xzvf protobuf${{env.PROTOBUF_VARIANT}}-${{env.PROTOBUF_VERSION}}.tar.gz - - name: Build ProtoBuf - if: steps.cache-depends.outputs.cache-hit != 'true' - working-directory: protobuf-3.20.1 + - name: Download Abseil ${{ env.ABSEIL_VERSION }} + if: steps.cache-depends.outputs.cache-hit != 'true' && env.PROTOBUF_VARIANT == '' + run: curl -OL https://github.com/abseil/abseil-cpp/archive/refs/tags/${{env.ABSEIL_VERSION}}.tar.gz && tar xzvf ${{env.ABSEIL_VERSION}}.tar.gz && rm -rf protobuf-${{env.PROTOBUF_VERSION}}/third_party/abseil-cpp && mv abseil-cpp-${{env.ABSEIL_VERSION}} protobuf-${{env.PROTOBUF_VERSION}}/third_party/abseil-cpp + + - name: Build ProtoBuf ${{ env.PROTOBUF_VERSION }} via autotools + if: steps.cache-depends.outputs.cache-hit != 'true' && env.PROTOBUF_VARIANT == '-all' + working-directory: protobuf-${{ env.PROTOBUF_VERSION }} run: ./configure DIST_LANG=cpp --prefix=/usr && make - - name: Install ProtoBuf - working-directory: protobuf-3.20.1 + - name: Build ProtoBuf ${{ env.PROTOBUF_VERSION }} via cmake + if: steps.cache-depends.outputs.cache-hit != 'true' && env.PROTOBUF_VARIANT == '' + working-directory: protobuf-${{ env.PROTOBUF_VERSION }} + run: cmake -DCMAKE_CXX_STANDARD=17 -Dprotobuf_BUILD_SHARED_LIBS=ON -Dprotobuf_BUILD_TESTS=OFF . && cmake --build . --config Release -j 4 + + - name: Install ProtoBuf ${{ env.PROTOBUF_VERSION }} + working-directory: protobuf-${{ env.PROTOBUF_VERSION }} run: sudo make install && sudo ldconfig - name: Prepare C++ Build @@ -144,19 +203,52 @@ jobs: bash convert-to-proto3.sh rm *.pb2 + - name: Add Development Version Suffix + if: ${{ !startsWith(github.ref, 'refs/tags') }} + run: | + echo "VERSION_SUFFIX = .dev`date -u '+%Y%m%d%H%M%S'`" >> VERSION + - name: Configure C++ Build working-directory: build - run: cmake .. + run: cmake ${{ env.PROTOBUF_VARIANT =='' && '-DCMAKE_CXX_STANDARD=17' }} .. - name: Build C++ working-directory: build run: cmake --build . --config Release -j 4 - name: Build Python - run: python setup.py build && python setup.py sdist + run: python -m build - name: Install Python run: python -m pip install . - name: Run Python Tests run: python -m unittest discover tests + + publish-python-dist: + name: Publish Python Distribution + + runs-on: ubuntu-22.04 + + permissions: + id-token: write + + needs: [build-proto2-linux64, build-proto3-linux64] + + if: ${{ github.event_name == 'push' && ( github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v') ) }} + + steps: + - name: Download Distribution + uses: actions/download-artifact@v4 + with: + name: python-dist + path: dist/ + + - name: Publish Snapshot Release on TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ + + - name: Publish Full Release on PyPI + if: startsWith(github.ref, 'refs/tags/v') && ( ! contains(github.ref, '-') ) + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 471651b6d..c68183981 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: steps: # Check out the GitHub repository - name: Checkout interface - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: dist/open-simulation-interface # Set the version variable based on the latest tag (could be replaced with github-internal predefined variable?) @@ -66,7 +66,7 @@ jobs: steps: # Check out the Antora generator - name: Checkout Antora generator - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: OpenSimulationInterface/osi-antora-generator path: antora @@ -94,7 +94,7 @@ jobs: args: antora.sh # Upload the created artifact for later jobs to use - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: antora path: antora/site @@ -107,19 +107,19 @@ jobs: steps: # Check out the repository (again) - name: Checkout interface - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: path: dist/open-simulation-interface_${{needs.setup.outputs.output1}} # Check out the sensor model packaging repo - name: Checkout sensor model packaging - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: repository: OpenSimulationInterface/osi-sensor-model-packaging path: dist/osi-sensor-model-packaging fetch-depth: 0 # Retrieve the Antora artifact from the previous job - name: Retrieve Antora artifact - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: antora path: dist/ASAM_OSI_Standard_${{needs.setup.outputs.output1}}/ @@ -154,13 +154,13 @@ jobs: mv osi-sensor-model-packaging osi-sensor-model-packaging_${{ env.OSMP_VERSION }} # Package all collected deliverables - name: Zip Release - uses: TheDoctor0/zip-release@0.6.2 + uses: TheDoctor0/zip-release@0.7.6 with: filename: ASAM_OSI_${{needs.setup.outputs.output1}}.zip directory: dist # Upload the created artifact for the publish job - name: Upload artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: deliverables path: dist/ASAM_OSI_${{needs.setup.outputs.output1}}.zip @@ -173,7 +173,7 @@ jobs: steps: # Retrieve the previously uploaded deliverables artifact - name: Retrieve previous artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: deliverables # Add the new zip file with the deliverables to the respective release @@ -182,3 +182,11 @@ jobs: with: file: ASAM_OSI_${{needs.setup.outputs.output1}}.zip tag: ${{ github.ref }} + - name: Trigger generator + if: ${{ env.MUP_KEY != '' }} + uses: peter-evans/repository-dispatch@v2 + with: + token: ${{ secrets.MACHINE_USER_PAT }} + event-type: antora-build-trigger + repository: OpenSimulationInterface/osi-antora-generator + client-payload: '{"src": "${{ github.repository }}", "ref": "master"}' diff --git a/.gitignore b/.gitignore index 26cc62ce5..e29f907d0 100644 --- a/.gitignore +++ b/.gitignore @@ -20,8 +20,6 @@ build cmake_install.cmake install_manifest.txt osi_version.proto -version.py -pyproject.toml compile_commands.json @@ -32,6 +30,8 @@ compile_commands.json # Python-generated files __pycache__/ +.venv/ +venv/ *.py[cod] proto2cpp.log .clang-format diff --git a/CMakeLists.txt b/CMakeLists.txt index dc99bbadb..6057fddf8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) endif() # Set the C++ standard -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard to be used") set(CMAKE_CXX_STANDARD_REQUIRED ON) # Optional Flatbuffer support @@ -82,8 +82,10 @@ set(OSI_PROTO_FILES osi_trafficlight.proto osi_trafficupdate.proto osi_trafficcommand.proto + osi_trafficcommandupdate.proto osi_referenceline.proto osi_roadmarking.proto + osi_route.proto osi_lane.proto osi_logicallane.proto osi_featuredata.proto diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 000000000..ceeea233f --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1 @@ +include VERSION diff --git a/VERSION b/VERSION index 24beea772..ffc0deabd 100644 --- a/VERSION +++ b/VERSION @@ -1,3 +1,3 @@ VERSION_MAJOR = 3 -VERSION_MINOR = 6 +VERSION_MINOR = 7 VERSION_PATCH = 0 diff --git a/doc/architecture/architecture_overview.adoc b/doc/architecture/architecture_overview.adoc index 6ef5b76ee..31f28c154 100644 --- a/doc/architecture/architecture_overview.adoc +++ b/doc/architecture/architecture_overview.adoc @@ -7,7 +7,8 @@ endif::[] OSI contains an object-based environment description that uses the message format of the https://github.com/protocolbuffers/protobuf/wiki[Protocol Buffer] library. Google developed and maintains the Protocol Buffer library. OSI defines top-level messages that are used to exchange data between separate models. -Top-level messages define the `GroundTruth` interface, the `SensorData` interface, and – since OSI version 3.0.0 – the interfaces `SensorView`, `SensorViewConfiguration`, and `FeatureData`. +Top-level messages define the `GroundTruth` interface, the `SensorData` interface, and – since OSI version 3.0.0 – the interfaces `SensorView` and `SensorViewConfiguration`. +Further top-level messages that were added in later versions of OSI are `HostVehicleData`, `TrafficCommand`, `TrafficCommandUpdate`, `TrafficUpdate`, `MotionRequest`, and `StreamingUpdate`. The following figure shows the interfaces and models involved in modeling a sensor. @@ -51,7 +52,8 @@ This has been done to allow backward-compatible changes in the field. Additionally, this is the default behavior in Protocol Buffer version 3 that no longer has the `required` type. Setting all fields to `optional` thus ensures update compatibility. However, this does not mean that it is optional to fill the field. -For the purpose of providing a complete interface, all existing fields should be set, unless not setting a field carries a specific meaning, as indicated in the accompanying comment. +Fields with the rule `is_set` are mandatory and have to be set at all times. +All other fields have to be set according to the requirements of the connected models. NOTE: All field numbers equal to or greater than 10000 are available for user-specific extensions via custom fields. No future evolution of OSI will therefore use field numbers equal to or greater than 10000. diff --git a/doc/architecture/feature_data.adoc b/doc/architecture/feature_data.adoc deleted file mode 100644 index f6ebf271f..000000000 --- a/doc/architecture/feature_data.adoc +++ /dev/null @@ -1,9 +0,0 @@ -ifndef::include-only-once[] -:root-path: ../ -include::{root-path}_config.adoc[] -endif::[] -= Feature data - -`FeatureData` messages contain detected features in the reference frame of a sensor. -`FeatureData` messages are generated from `GroundTruth` messages. -They serve, for example, as an input to sensor models simulating object detection or feature fusion models. diff --git a/doc/architecture/formatting_script.adoc b/doc/architecture/formatting_script.adoc new file mode 100644 index 000000000..d8beec595 --- /dev/null +++ b/doc/architecture/formatting_script.adoc @@ -0,0 +1,29 @@ +ifndef::include-only-once[] +:root-path: ../ +include::{root-path}_config.adoc[] +endif::[] += Trace-file formatting script + +The OSI repository contains a Python script for converting trace files from one format to another. +The formatting script is stored in `open-simulation-interface/osi3trace/`. + +**osi2read.py** + +`osi2read.py` converts trace files to human-readable `.txth` trace files. +This script takes the following parameters: + +`--data`, `-d`:: +String containing the path to the file with serialized data. + +`--type`, `-t`:: +Optional string describing the message type used to serialize data. +`'SensorView'`, `'GroundTruth'`, or `'SensorData'` are permitted values. +The default value is `'SensorView'`. + +`--output`, `-o`:: +Optional string containing the name of the output file. +The default value is `None`, in which case the output file name is set to the name of the input file, with the file extension being replaced by `.txth`. + +**Related topics** + +* <> \ No newline at end of file diff --git a/doc/architecture/formatting_scripts.adoc b/doc/architecture/formatting_scripts.adoc deleted file mode 100644 index 9b1a26857..000000000 --- a/doc/architecture/formatting_scripts.adoc +++ /dev/null @@ -1,56 +0,0 @@ -ifndef::include-only-once[] -:root-path: ../ -include::{root-path}_config.adoc[] -endif::[] -= Trace-file formatting scripts - -The OSI repository contains Python scripts for converting trace files from one format to another. -The formatting scripts are stored in `open-simulation-interface/format/` - -**txt2osi.py** - -`txt2osi.py` converts plain-text trace files to binary `.osi` trace files. -This script takes the following parameters: - -`--data`, `-d`:: -String containing the path to the file with serialized data. - -`--type`, `-t`:: -Optional string describing the message type used to serialize data. -`'SensorView'`, `'GroundTruth'`, or `'SensorData'` are permitted values. -The default value is `'SensorView'`. - -`--output`, `-o`:: -Optional string containing the name of the output file. -The default value is `'converted.osi'`. - -`--compress`, `-c`:: -Optional Boolean controlling whether to compress the output to an lzma file. -`True`, or `False` are permitted values. -The default value is `False`. - -**osi2read.py** - -`osi2read.py` converts trace files to human-readable `.txth` trace files. -This script takes the following parameters: - -`--data`, `-d`:: -String containing the path to the file with serialized data. - -`--type`, `-t`:: -Optional string describing the message type used to serialize data. -`'SensorView'`, `'GroundTruth'`, or `'SensorData'` are permitted values. -The default value is `'SensorView'`. - -`--output`, `-o`:: -Optional string containing the name of the output file. -The default value is `'converted.txth'`. - -`--format`, `-f`:: -Optional string containing the format type of the trace file. -`'separated'`, or `None` are permitted values. -The default value is `None`. - -**Related topics** - -* <> \ No newline at end of file diff --git a/doc/architecture/reference_points_coordinate_systems.adoc b/doc/architecture/reference_points_coordinate_systems.adoc index 4c6ea7343..b32eb8392 100644 --- a/doc/architecture/reference_points_coordinate_systems.adoc +++ b/doc/architecture/reference_points_coordinate_systems.adoc @@ -13,6 +13,7 @@ The global coordinate system is an inertial x/y/z-coordinate system. The origin is the global reference point that is determined by the environment simulation. This reference point may be derived from map data or other considerations. Global coordinates can be mapped to a geographic coordinate system via `osi3::GroundTruth::proj_string`. +Note that before applying any PROJ transformations to global coordinates, the `osi3::GroundTruth::proj_frame_offset` must be applied. Host vehicle coordinate system:: The host vehicle coordinate system's origin is defined to be at the center of the rear axle of the host vehicle. diff --git a/doc/architecture/sensor_data.adoc b/doc/architecture/sensor_data.adoc index f06383960..b44f0eeaa 100644 --- a/doc/architecture/sensor_data.adoc +++ b/doc/architecture/sensor_data.adoc @@ -9,3 +9,7 @@ They can be generated from `GroundTruth` messages, `SensorView` messages, `Featu With the exception of feature data, all information regarding the environment is given with respect to the virtual sensor coordinate system. Feature data is given with respect to the physical sensor coordinate system. Sensor data can be used as input for an automated driving function, a sensor model simulating limited perception, or a sensor fusion model. + +`SensorData` messages include `FeatureData` messages which contain detected features in the reference frame of a sensor. +`FeatureData` messages are generated from `GroundTruth` messages. +They serve, for example, as an input to sensor models simulating object detection or feature fusion models. diff --git a/doc/architecture/trace_file_formats.adoc b/doc/architecture/trace_file_formats.adoc index 0ed0a4515..60f2d2a4b 100644 --- a/doc/architecture/trace_file_formats.adoc +++ b/doc/architecture/trace_file_formats.adoc @@ -5,7 +5,7 @@ endif::[] [#top-osi_trace_file_formats] = OSI trace file formats -There are multiple formats for storing multiple serialized OSI messages in one trace file. +There are two formats for storing multiple serialized OSI messages in one trace file. *.osi:: Binary trace file. @@ -13,11 +13,12 @@ Messages are separated by a length specification before each message. The length is represented by a four-byte, little-endian, unsigned integer. The length does not include the integer itself. -*.txt:: -Plain-text trace file. -Messages are separated by `$$__$$`. - *.txth:: Human-readable plain-text trace file. Messages are separated by newlines. + +NOTE: Previous releases of OSI also supported a so-called plain-text trace file format, with file extension `.txt`. +This legacy format did not contain plain-text, but rather binary protobuf messages separated by a special separator. +For obvious reasons the format was deprecated and fully replaced with the `.osi` binary file format. +This release no longer contains any support for the legacy `.txt` file format. These files may be used for manual checks. diff --git a/doc/architecture/trace_file_naming.adoc b/doc/architecture/trace_file_naming.adoc index bbc892422..1c73ed90c 100644 --- a/doc/architecture/trace_file_naming.adoc +++ b/doc/architecture/trace_file_naming.adoc @@ -14,21 +14,35 @@ The names of OSI trace files should have the following format: **Types** -`sd`:: -Trace file contains `SensorData` messages. - `sv`:: Trace file contains `SensorView` messages. +`svc`:: +Trace file contains `SensorViewConfiguration` messages. + `gt`:: Trace file contains `GroundTruth` messages. -`tu`:: -Trace file contains `TrafficUpdate` messages. +`hvd`:: +Trace file contains `HostVehicleData` messages. + +`sd`:: +Trace file contains `SensorData` messages. `tc`:: Trace file contains `TrafficCommand` messages. +`tcu`:: +Trace file contains `TrafficCommandUpdate` messages. + +`tu`:: +Trace file contains `TrafficUpdate` messages. + +`mr`:: +Trace file contains `MotionRequest` messages. + +`su`:: +Trace file contains `StreamingUpdate` messages. **Example** diff --git a/doc/images/OSI_Planned_Route.png b/doc/images/OSI_Planned_Route.png new file mode 100644 index 000000000..17e490ced Binary files /dev/null and b/doc/images/OSI_Planned_Route.png differ diff --git a/doc/images/OSI_Route_Segment.png b/doc/images/OSI_Route_Segment.png new file mode 100644 index 000000000..be13b5672 Binary files /dev/null and b/doc/images/OSI_Route_Segment.png differ diff --git a/doc/images/osi-streaming-principle.PNG b/doc/images/osi-streaming-principle.png similarity index 100% rename from doc/images/osi-streaming-principle.PNG rename to doc/images/osi-streaming-principle.png diff --git a/doc/open-simulation-interface_user_guide.adoc b/doc/open-simulation-interface_user_guide.adoc index b9864441e..69296d267 100644 --- a/doc/open-simulation-interface_user_guide.adoc +++ b/doc/open-simulation-interface_user_guide.adoc @@ -15,8 +15,6 @@ include::./architecture/architecture_overview.adoc[leveloffset=+2] include::./architecture/ground_truth.adoc[leveloffset=+3] -include::./architecture/feature_data.adoc[leveloffset=+3] - include::./architecture/sensor_view.adoc[leveloffset=+3] include::./architecture/sensor_view_configuration.adoc[leveloffset=+3] @@ -71,7 +69,7 @@ include::./architecture/trace_file_naming.adoc[leveloffset=+3] include::./architecture/trace_file_example.adoc[leveloffset=+3] -include::./architecture/formatting_scripts.adoc[leveloffset=+3] +include::./architecture/formatting_script.adoc[leveloffset=+3] // Setting up OSI diff --git a/doc/setup/installing_linux_python.adoc b/doc/setup/installing_linux_python.adoc index b1b84a32c..de234498f 100644 --- a/doc/setup/installing_linux_python.adoc +++ b/doc/setup/installing_linux_python.adoc @@ -4,12 +4,31 @@ include::{root-path}_config.adoc[] endif::[] = Installing OSI for Python on Linux +== Installing from PyPI + +Steps:: + +. Open a terminal. ++ +. Optionally create and activate a new virtual environment. ++ +---- +python3 -m venv venv +source venv/bin/activate +---- ++ +. Install Open Simulation Interface. ++ +---- +pip install open-simulation-interface +---- + +== Installing from source + *Prerequisites:: * You have installed everything described in <>. -* You have installed _pip3_. -* You have installed _python-setuptools_. -* For a local installation, you have installed _virtualenv_. +* You have installed _pip_. Steps:: @@ -26,15 +45,10 @@ git clone https://github.com/OpenSimulationInterface/open-simulation-interface.g cd open-simulation-interface ---- + -. Create a new virtual environment. -+ ----- -virtualenv -p python3 venv ----- -+ -. Activate the virtual environment. +. Optionally create and activate a new virtual environment. + ---- +python3 -m venv venv source venv/bin/activate ---- + @@ -42,11 +56,11 @@ source venv/bin/activate .. Local installation + ---- -python3 -m pip install . +pip install . ---- + .. Global installation + ---- -sudo pip3 install . +sudo pip install . ---- diff --git a/doc/setup/installing_prerequisites.adoc b/doc/setup/installing_prerequisites.adoc index 00bd6fb90..430499bed 100644 --- a/doc/setup/installing_prerequisites.adoc +++ b/doc/setup/installing_prerequisites.adoc @@ -51,7 +51,7 @@ vcpkg install --triplet=x64-windows-static-md protobuf Dynamic linking (NOT RECOMMENDED):: As already mentioned, shared linking is possible on Linux, but NOT RECOMMENDED. -However, for dynamic linking install _protobuf_ (version 3.0.0 or higher) with apt: +However, for dynamic linking install _protobuf_ (version 2.6.1 or higher) with apt: ---- sudo apt-get install libprotobuf-dev protobuf-compiler ---- @@ -64,7 +64,7 @@ This means that your OSI is build statically but still linking dynamically again Here, again either _protobuf_ has to build statically from source or some solution e.g. vcpkg needs to be utilized. We recommend the following (as in the README of the OSI project): -Install _protobuf_ (version 3.0.0 or higher) from source with `CXXFLAGS="-fPIC"` to allow static linking of your OSI FMUs (replace with preferred release): +Install _protobuf_ (version 2.6.1 or higher) from source with `CXXFLAGS="-fPIC"` to allow static linking of your OSI FMUs (replace with preferred release): ---- wget https://github.com/protocolbuffers/protobuf/releases/download//protobuf-all-.tar.gz tar -xzf protobuf-all-.tar.gz diff --git a/doc/setup/installing_windows_python.adoc b/doc/setup/installing_windows_python.adoc index ecb3d31cf..37a2bba30 100644 --- a/doc/setup/installing_windows_python.adoc +++ b/doc/setup/installing_windows_python.adoc @@ -4,6 +4,27 @@ include::{root-path}_config.adoc[] endif::[] = Installing OSI for Python on Windows +== Installing from PyPI + +Steps:: + +. Open a terminal. ++ +. Optionally create and activate a new virtual environment. ++ +---- +python -m venv venv +venv\Scripts\activate +---- ++ +. Install Open Simulation Interface. ++ +---- +pip install open-simulation-interface +---- + +== Installing from source + Prerequisites:: * You have installed everything described in <>. @@ -25,8 +46,15 @@ git clone https://github.com/OpenSimulationInterface/open-simulation-interface.g cd open-simulation-interface ---- + -. Run the setup script. +. Optionally create and activate a new virtual environment. ++ +---- +python -m venv venv +venv\Scripts\activate +---- ++ +. Install Open Simulation Interface. + ---- -python setup.py install +pip install . ---- diff --git a/format/OSITrace.py b/format/OSITrace.py deleted file mode 100644 index f6d3d39fe..000000000 --- a/format/OSITrace.py +++ /dev/null @@ -1,244 +0,0 @@ -""" -Module to handle and manage OSI scenarios. -""" -from collections import deque -import time -import lzma -import struct - -from osi3.osi_sensorview_pb2 import SensorView -from osi3.osi_groundtruth_pb2 import GroundTruth -from osi3.osi_sensordata_pb2 import SensorData -import warnings -warnings.simplefilter('default') - -SEPARATOR = b'$$__$$' -SEPARATOR_LENGTH = len(SEPARATOR) -BUFFER_SIZE = 1000000 - - -def get_size_from_file_stream(file_object): - """ - Return a file size from a file stream given in parameters - """ - current_position = file_object.tell() - file_object.seek(0, 2) - size = file_object.tell() - file_object.seek(current_position) - return size - - -MESSAGES_TYPE = { - "SensorView": SensorView, - "GroundTruth": GroundTruth, - "SensorData": SensorData -} - - -class OSITrace: - """This class wrap OSI data. It can import and decode OSI scenarios.""" - - def __init__(self, path=None, type_name="SensorView"): - self.scenario_file = None - self.message_offsets = None - self.type_name = type_name - self.timestep_count = 0 - self.retrieved_scenario_size = 0 - self._int_length = len(struct.pack("= scenario_size: - break - self.message_offsets.append(message_offset) - - if eof: - self.retrieved_scenario_size = scenario_size - else: - self.retrieved_scenario_size = self.message_offsets[-1] - self.message_offsets.pop() - - return len(self.message_offsets) - - def retrieve_message(self): - scenario_size = get_size_from_file_stream(self.scenario_file) - buffer_deque = deque(maxlen=2) - - self.message_offsets = [0] - eof = False - - # TODO Implement buffering for the scenarios - self.scenario_file.seek(0) - serialized_message = self.scenario_file.read() - INT_LENGTH = len(struct.pack(" 1000000000: - # Throw a warning if trace file is bigger than 1GB - gb_size_input = round(message_length/1000000000, 2) - gb_size_output = round(3.307692308*message_length/1000000000, 2) - warnings.warn(f"The trace file you are trying to make readable has the size {gb_size_input}GB. This will generate a readable file with the size {gb_size_output}GB. Make sure you have enough disc space and memory to read the file with your text editor.", ResourceWarning) - - with open(name, 'a') as f: - - if interval is None and index is None: - for i in self.get_messages(): - f.write(str(i)) - - if interval is not None and index is None: - if type(interval) == tuple and len(interval) == 2 and interval[0]= len(self.message_offsets): + self.retrieve_offsets(index) + if self.message_cache is not None and index in self.message_cache: + return self.message_cache[index] + return self.retrieve_message(index=index) + + def get_messages(self): + """ + Yield an iterator over all messages in the file. + """ + return self.get_messages_in_index_range(0, None) + + def get_messages_in_index_range(self, begin, end): + """ + Yield an iterator over messages of indexes between begin and end included. + """ + if begin >= len(self.message_offsets): + self.retrieve_offsets(begin) + self.restart(begin) + current = begin + while end is None or current < end: + if self.message_cache is not None and current in self.message_cache: + yield self.message_cache[current] + else: + message = self.retrieve_message() + if message is None: + break + yield message + current += 1 + + def close(self): + if self.file: + self.file.close() + self.file = None + self.current_index = None + self.message_cache = None + self.message_offsets = None + self.read_complete = False + self.read_limit = None + self.type = None diff --git a/osi_common.proto b/osi_common.proto index ec84ed522..cd49951a6 100644 --- a/osi_common.proto +++ b/osi_common.proto @@ -159,7 +159,7 @@ message Dimension3d // \f$ Rotation_{yaw,pitch,roll} = // Rotation_{yaw}*Rotation_{pitch}*Rotation_{roll} \f$ // -// \f$ vector_{gobal coord system} := Rotation_{yaw, pitch, roll} * vector_{local coord system} +local_{origin::position} \f$ +// \f$ vector_{global coord system} := Rotation_{yaw, pitch, roll} * vector_{local coord system} +local_{origin::position} \f$ // // \attention This definition changed in OSI version 3.0.0. Previous OSI // versions (V2.xx) had an other definition. @@ -195,8 +195,11 @@ message Orientation3d // truth, the identifier of an item (object, lane, sign, etc.) must remain // stable over its lifetime. \c Identifier values may be only be reused if the // available address space is exhausted and the specific values have not been in -// use for several timesteps. Sensor specific tracking IDs have no restrictions +// use for several time steps. Sensor specific tracking IDs have no restrictions // and should behave according to the sensor specifications. +// Purely simulation technical IDs, like sensor IDs, are not required to be +// unique among all simulated items, but rather unique within the context of the +// given message type. // // The value MAX(uint64) = 2^(64) -1 = // 0b1111111111111111111111111111111111111111111111111111111111111111 is @@ -204,7 +207,7 @@ message Orientation3d // message Identifier { - // The identifier's value. + // The value of the identifier. // // \rules // is_greater_than_or_equal_to: 0 @@ -245,12 +248,16 @@ message ExternalReference // reverse domain name notation with lower-case type field // is recommended to guarantee unique and interoperable identification. // - optional string type = 2; + // \rules + // is_set + // \endrules + // + optional string type = 2; // The external identifier reference value. // // The repeated string is chosen as a common description of the external - // identifier, because a variety of identificatier types could be + // identifier, because a variety of identifier types could be // involved . // // For example, referencing a unique lane in OpenDRIVE requires the @@ -387,6 +394,102 @@ message LogicalLaneAssignment optional double angle_to_lane = 4; } +// \brief A bounding box description. +// +// A bounding box representing a sub-section of its parent's overall +// dimension, either that of a \c BaseMoving or \c BaseStationary . +// +// The parent frame of the \c BoundingBox is identical to the parent frame +// of the \c MovingObject or \c StationaryObject it is associated to. For +// example, if the parent object coordinates are given relative to the +// global coordinate system, then the \c BoundingBox coordinates are also +// given relative to the global coordinate system. +// +// \note The overall bounding box of the object is still defined using the +// dimension, position and orientation of the \c BaseMoving or +// \c BaseStationary . +// +message BoundingBox +{ + // The 3D dimensions of the bounding box. + // + optional Dimension3d dimension = 1; + + // The 3D position of the bounding box. The position is the center + // of the bounding box and the pivot for the \c dimension and \c orientation. + // + // \note The position should be within the same coordinate frame as + // its parent, not relative to coordinate frame of the parent object. + // The position becomes global/absolute if the parent frame is inertial + // (all parent frames up to ground truth). + // + optional Vector3d position = 2; + + // The 3D orientation of the bounding box. + // + // \note The orientation should be within the same coordinate frame as + // its parent, not relative to the coordinate frame of the parent object. + // The orientation becomes global/absolute if the parent frame is inertial + // (all parent frames up to ground truth). + // + optional Orientation3d orientation = 3; + + // The type of object contained in the bounding box. + // + optional Type contained_object_type = 4; + + // Opaque reference of an associated 3D model of the bounding box. + // + // \note It is implementation-specific how model_references are resolved to + // 3d models. This means the coordinate system, model origin, and model + // orientation are also implementation-specific. + // + optional string model_reference = 5; + + // Definition of different types of object contained within the bounding box + // + enum Type + { + // Object of unknown type (must not be used in ground truth). + // + TYPE_UNKNOWN = 0; + + // Any other type of object. + // + TYPE_OTHER = 1; + + // The main structure of an object, e.g. a chassis of a vehicle, + // or the central structure of a building, a tree trunk, etc. + // + TYPE_BASE_STRUCTURE = 2; + + // A protruding, integral part of an object, which is not + // temporarily attached, e.g. a tree crown, a light pole arm, or a + // parking house gate. The protruding structure is meant to be an + // additional part to a base structure. + // + TYPE_PROTRUDING_STRUCTURE = 3; + + // Additional, temporarily attached cargo to an object. + // + TYPE_CARGO = 4; + + // The door of an object. + // + // For vehicles, this includes driver and passenger doors, trunk + // and front hoods, and fuel or charging port covers. + // + TYPE_DOOR = 5; + + // The side mirror of a vehicle. + // + // \note The side mirror is not included in the overall bounding box + // of the parent object. + // + TYPE_SIDE_MIRROR = 6; + } +} + // // \brief The base attributes of a stationary object or entity. // @@ -405,6 +508,9 @@ message BaseStationary // The 3D dimensions of the stationary object (bounding box), e.g. a // landmark. // + // \note The \c #dimension must completely enclose the geometry of the + // \c BaseStationary . + // optional Dimension3d dimension = 1; // The reference point for position and orientation, i.e. the center (x,y,z) @@ -447,6 +553,23 @@ message BaseStationary // The polygon is defined counter-clockwise. // repeated Vector2d base_polygon = 4; + + // Sub-divisions of the overall bounding box of the \c BaseStationary object. + // + // The bounding box sections can include separate parts on partially-opaque + // objects such as trees with a distinction between trunk and crown. + // + // \note The bounding box sub-divisions can extend beyond the overall + // bounding box, however no actual geometry must reside outside of the + // overall bounding box. + // + // \note If any sub-divisions are provided, then they must cover all + // occupied space of the overall bounding box. In other words, a consumer + // of this data is guaranteed that any part of the overall bounding box + // that is not covered by any sub-division is free of physical objects, + // and thus no collisions can occur there. + // + repeated BoundingBox bounding_box_section = 5; } // @@ -469,6 +592,9 @@ message BaseMoving { // The 3D dimension of the moving object (its bounding box). // + // \note The \c #dimension must completely enclose the geometry of the + // \c BaseMoving with the exception of the side mirrors for vehicles. + // // \note The bounding box does NOT include side mirrors for vehicles. // optional Dimension3d dimension = 1; @@ -569,6 +695,24 @@ message BaseMoving // The polygon is defined counter-clockwise. // repeated Vector2d base_polygon = 7; + + // Sub-divisions of the overall bounding box of the \c BaseMoving object. + // + // The bounding box sections can include side mirrors, cargo, etc. for + // vehicles, as well as body-part sections for pedestrians. Note that for + // more precise pedestrian information \c PedestrianAttributes can be used. + // + // \note The bounding box sub-divisions can extend beyond the overall + // bounding box, however no actual geometry must reside outside of the + // overall bounding box, with the specific exception of the side mirrors. + // + // \note If any sub-divisions are provided, then they must cover all + // occupied space of the overall bounding box. In other words, a consumer + // of this data is guaranteed that any part of the overall bounding box + // that is not covered by any sub-division is free of physical objects, + // and thus no collisions can occur there. + // + repeated BoundingBox bounding_box_section = 9; } // @@ -673,7 +817,7 @@ message SpatialSignalStrength // message ColorDescription { - // Greyscale color model + // Grayscale color model // optional ColorGrey grey = 1; @@ -699,13 +843,13 @@ message ColorDescription } // -// \brief Greyscale color model +// \brief Grayscale color model // -// ColorGrey defines a greyscale. +// ColorGrey defines a grayscale. // message ColorGrey { - // Definition of a greyscale + // Definition of a grayscale // // Range: [0,1] // @@ -952,3 +1096,15 @@ message KeyValuePair // optional string value = 2; } + +// +// \brief Polygon in 3 dimensions +// +// A polygon in 3 dimensions which contains a list of vertices. +// +message Polygon3d +{ + // A list of vertices + // + repeated Vector3d vertex = 1; +} diff --git a/osi_detectedobject.proto b/osi_detectedobject.proto index d651b949e..3ea53c4e0 100644 --- a/osi_detectedobject.proto +++ b/osi_detectedobject.proto @@ -17,6 +17,10 @@ message DetectedItemHeader // Specific ID of the detected item as assigned by the sensor internally. // Needs not to match with \c #ground_truth_id. // + // \rules + // is_set + // \endrules + // optional Identifier tracking_id = 1; // The ID of the original detected item in the ground truth. @@ -76,11 +80,11 @@ message DetectedItemHeader // MEASUREMENT_STATE_OTHER = 1; - // Entity has been measured by the sensor in the current timestep. + // Entity has been measured by the sensor in the current time step. // MEASUREMENT_STATE_MEASURED = 2; - // Entity has not been measured by the sensor in the current timestep. + // Entity has not been measured by the sensor in the current time step. // Values provided by tracking only. // MEASUREMENT_STATE_PREDICTED = 3; @@ -321,6 +325,10 @@ message DetectedMovingObject // \note This field is mandatory if the \c CandidateMovingObject::type // is \c MovingObject::TYPE_VEHICLE . // + // \rules + // check_if this.type is_equal_to 2 else do_check is_set + // \endrules + // optional MovingObject.VehicleClassification vehicle_classification = 3; // Pedestrian head pose for behavior prediction. Describes the head @@ -335,6 +343,10 @@ message DetectedMovingObject // \note This field is mandatory if the \c CandidateMovingObject.type is // \c MovingObject::TYPE_PEDESTRIAN // + // \rules + // check_if this.type is_equal_to 3 else do_check is_set + // \endrules + // // \par Reference: // // [1] Patton, K. T. & Thibodeau, G. A. (2015). Anatomy & Physiology. 9th Edition. Elsevier. Missouri, U.S.A. ISBN 978-0-323-34139-4. p. 1229. @@ -354,6 +366,10 @@ message DetectedMovingObject // \note This field is mandatory if the \c CandidateMovingObject::type // is \c MovingObject::TYPE_PEDESTRIAN // + // \rules + // check_if this.type is_equal_to 3 else do_check is_set + // \endrules + // // \par Reference: // [1] Patton, K. T. & Thibodeau, G. A. (2015). Anatomy & Physiology. 9th Edition. Elsevier. Missouri, U.S.A. ISBN 978-0-323-34139-4. p. 1229. // diff --git a/osi_environment.proto b/osi_environment.proto index fb77438fc..ae3c55011 100644 --- a/osi_environment.proto +++ b/osi_environment.proto @@ -40,7 +40,7 @@ message EnvironmentalConditions // sorted by languages](https://www.epochconverter.com/#code). // // \par References: - // [1] ITU Radiocommunication Assembly. (2002). Recommondation ITU-R TF.460-6 Standard-frequency and time-signal emissions. (Rec. ITU-R TF.460-6). Retrieved January 25, 2020, from http://www.itu.int/dms_pubrec/itu-r/rec/tf/R-REC-TF.460-6-200202-I!!PDF-E.pdf \n + // [1] ITU Radiocommunication Assembly. (2002). Recommendation ITU-R TF.460-6 Standard-frequency and time-signal emissions. (Rec. ITU-R TF.460-6). Retrieved January 25, 2020, from http://www.itu.int/dms_pubrec/itu-r/rec/tf/R-REC-TF.460-6-200202-I!!PDF-E.pdf \n // [2] The Open Group. (2018). POSIX.1-2017 The Open Group Base Specifications Issue 7, 2018 edition. IEEE Std 1003.1-2017 (Revision of IEEE Std 1003.1-2008). Retrieved January 25, 2020, from https://pubs.opengroup.org/onlinepubs/9699919799/xrat/contents.html // optional int64 unix_timestamp = 8; @@ -358,7 +358,7 @@ message EnvironmentalConditions // 8 oktas means that the sky is completely covered with clouds and no sky blue can be recognized. // // \par References: - // [1] CIE engl. International Commission on Illumination. (2020). CIE S017:2020 ILV: Intl. Lighitng Vocabulary, 2nd edn.. Retrieved March 8, 2022, from https://cie.co.at/eilvterm/17-29-116 \n + // [1] CIE engl. International Commission on Illumination. (2020). CIE S017:2020 ILV: Intl. Lighting Vocabulary, 2nd edn.. Retrieved March 8, 2022, from https://cie.co.at/eilvterm/17-29-116 \n // [2] UBC The University of British Columbia. (2018). ATSC 113 Weather for Sailing, Flying & Snow Sports. Retrieved March 8, 2022, from https://www.eoas.ubc.ca/courses/atsc113/flying/met_concepts/01-met_concepts/01c-cloud_coverage/index.html // enum FractionalCloudCover diff --git a/osi_featuredata.proto b/osi_featuredata.proto index 6cb30bee4..c82c3c727 100644 --- a/osi_featuredata.proto +++ b/osi_featuredata.proto @@ -36,7 +36,7 @@ message FeatureData // // \note Required for ultrasonic sensors: Detections will be send by the // emitting ultrasonic sensor, including all indirect detections received - // by neighbouring sensors. + // by neighboring sensors. // repeated UltrasonicDetectionData ultrasonic_sensor = 4; @@ -92,6 +92,10 @@ message SensorDetectionHeader // \par Reference: // [1] DIN Deutsches Institut fuer Normung e. V. (2013). DIN ISO 8855 Strassenfahrzeuge - Fahrzeugdynamik und Fahrverhalten - Begriffe. (DIN ISO 8855:2013-11). Berlin, Germany. // + // \rules + // is_set + // \endrules + // optional MountingPosition mounting_position = 3; // The origin/orientation of the sensor frame represents the current @@ -124,6 +128,10 @@ message SensorDetectionHeader // This ID can equal \c SensorData::sensor_id, if \c SensorData holds only // data from one sensor/sensor model. // + // \rules + // is_set + // \endrules + // optional Identifier sensor_id = 7; // The extended qualifier describes the reason (not the effect) why the @@ -214,7 +222,7 @@ message SensorDetectionHeader // EXTENDED_QUALIFIER_INPUT_NOT_AVAILABLE = 9; - // Internal reason (e.g. an internal HW or SW error has occurred). + // Internal reason (e.g. an internal hardware or software error has occurred). // EXTENDED_QUALIFIER_INTERNAL_REASON = 10; @@ -324,7 +332,7 @@ message RadarDetection // // \note Unambiguous measurements have the ambiguity ID 0. // - // \note Multiple seperate detections, from e.g. a large object, do not + // \note Multiple separate detections, from e.g. a large object, do not // necessarily on their own create any ambiguity. Therefore they do not // usually share an ambiguity ID. They can however be ambiguous // with other detections. @@ -451,7 +459,7 @@ message LidarDetection // \endrules // // \par Reference: - // [1] Rosenberger, P., Holder, M.F., Cianciaruso, N. et al. (2020). Sequential lidar sensor system simulation: a modular approach for simulation-based safety validation of automated driving Automot. Engine Technol. 5, Fig 7, Fig 8. Retrieved May 10, 2021, from https://doi.org/10.1007/s41104-020-00066-x + // [1] Rosenberger, P., Holder, M.F., Cianciaruso, N. et al. (2020). Sequential lidar sensor system simulation: a modular approach for simulation-based safety validation of automated driving Automotive Engine Technology 5, Fig 7, Fig 8. Retrieved May 10, 2021, from https://doi.org/10.1007/s41104-020-00066-x // optional double echo_pulse_width = 11; @@ -460,6 +468,10 @@ message LidarDetection // Unit: m/s // optional double radial_velocity = 12; + + // ID of the corresponding lidar beam. + // + optional Identifier beam_id = 13; } // @@ -497,7 +509,7 @@ message UltrasonicDetectionSpecificHeader // // \image html OSI_USSensor.svg // -// \note Direct detecions lies on circles with the sending sensor as centre. +// \note Direct detections lie on circles with the sending sensor as center. // message UltrasonicDetectionData { @@ -532,7 +544,7 @@ message UltrasonicDetectionData // // \image html OSI_USSensor_direct.svg // -// \note Direct detecions lies on circles with the sensor as centre. +// \note Direct detections lie on circles with the sensor as center. // message UltrasonicDetection { @@ -582,7 +594,7 @@ message UltrasonicDetection // // \image html OSI_USSensor_indirect.svg // -// \note Indirect detecions lies on ellipses with the sending resp. receiving +// \note Indirect detections lie on ellipses with the sending resp. receiving // sensor in the focal points. // message UltrasonicIndirectDetection @@ -718,7 +730,7 @@ message CameraDetection // optional bool shape_classification_background = 5; - // The defined shape is foregroud. + // The defined shape is foreground. // The probability for this classification is at least // \c #shape_classification_probability. // @@ -844,7 +856,7 @@ message CameraDetection // optional bool shape_classification_pedestrian_rear = 26; - // This probability defines the mininimum probability for each selected + // This probability defines the minimum probability for each selected // shape classification. // // \rules @@ -904,6 +916,10 @@ message CameraDetection // enum Color { + // Allow aliases in enum + // + option allow_alias = true; + // Color of the shape is unknown (must not be used in ground // truth). // @@ -917,7 +933,13 @@ message CameraDetection // COLOR_BLACK = 2; - // Shape with grey color. + // Shape with gray color. + // + COLOR_GRAY = 3; + + // Shape with gray color. + // + // \note Deprecated variant spelling of COLOR_GRAY // COLOR_GREY = 3; @@ -977,7 +999,7 @@ message CameraDetection // Allowed number of referenced points: 2 or 3 // // Allowed number of referenced points = 2: first and third corner of - // the box. Box is alligned horizontal resp. vertical. + // the box. Box is aligned horizontal resp. vertical. // // Allowed number of referenced points = 3: first, second and third // corner of the box. fourth corner is calculated by first+third-second @@ -1045,7 +1067,7 @@ message CameraPoint // optional double existence_probability = 1; - // Measured point refered by one camera detection given in spherical + // Measured point referred by one camera detection given in spherical // coordinates in the sensor coordinate system. // optional Spherical3d point = 2; diff --git a/osi_groundtruth.proto b/osi_groundtruth.proto index 5cf5477b5..ddcb25e96 100644 --- a/osi_groundtruth.proto +++ b/osi_groundtruth.proto @@ -47,6 +47,10 @@ message GroundTruth // The interface version used by the sender (i.e. the simulation // environment). // + // \rules + // is_set + // \endrules + // optional InterfaceVersion version = 1; // The data timestamp of the simulation environment. The zero time point is @@ -60,6 +64,10 @@ message GroundTruth // (there is no inherent latency for ground truth data, as opposed to // sensor data). // + // \rules + // is_set + // \endrules + // optional Timestamp timestamp = 2; // The ID of the host vehicle object referencing to \c MovingObject . @@ -68,6 +76,7 @@ message GroundTruth // // \rules // refers_to: MovingObject + // is_set // \endrules // optional Identifier host_vehicle_id = 3; @@ -118,13 +127,14 @@ message GroundTruth // [1] ISO International Organization for Standardization. (2013). ISO 3166-1 Codes for the representation of names of countries and their subdivisions - Part 1: Country codes. (ISO 3166-1:2013). Geneva, Switzerland. // // \rules - // is_iso_country_code: + // is_iso_country_code // \endrules // optional uint32 country_code = 13; // Projection string that allows to transform all coordinates in GroundTruth - // into a different cartographic projection. + // into a different cartographic projection after the \c proj_frame_offset + // has been applied. // // The string follows the PROJ rules for projections [1]. // @@ -169,4 +179,52 @@ message GroundTruth // Logical lanes used e.g. by traffic agents // repeated LogicalLane logical_lane = 19; + + // Coordinate frame offset to be used for PROJ transformations. + // + optional ProjFrameOffset proj_frame_offset = 20; + + // + // \brief Coordinate frame offset to transform from OSI's global coordinate + // system to a coordinate reference system to be used for given PROJ + // transformations. + // + // If an offset is defined, always apply the \c proj_frame_offset on + // global OSI coordinates before applying any transformations defined in + // \c proj_string. + // + // To apply the offset, global coordinates are first translated by the given + // positional offset (x,y,z). Then, the yaw angle is used to rotate around + // the new origin. + // + // The offset is applied on global OSI coordinates using an affine + // transformation with rotation around z-axis: + // + // xWorld = xOSI * cos(yaw) - yOSI * sin(yaw) + xOffset + // + // yWorld = xOSI * sin(yaw) + yOSI * cos(yaw) + yOffset + // + // zWorld = zOSI + zOffset + // + // + // If no yaw is provided (recommended), the formulas simplify to: + // + // xWorld = xOSI + xOffset + // + // yWorld = yOSI + yOffset + // + // zWorld = zOSI + zOffset + // + message ProjFrameOffset + { + // Positional offset for relocation of the coordinate frame. + // + optional Vector3d position = 1; + + // Yaw/heading angle for re-orientation of the coordinate frame around + // the z-axis. + // + optional double yaw = 2; + } + } diff --git a/osi_hostvehicledata.proto b/osi_hostvehicledata.proto index cdd723118..026de2a7e 100644 --- a/osi_hostvehicledata.proto +++ b/osi_hostvehicledata.proto @@ -4,6 +4,7 @@ option optimize_for = SPEED; import "osi_version.proto"; import "osi_common.proto"; +import "osi_route.proto"; package osi3; @@ -91,6 +92,10 @@ message HostVehicleData // optional VehicleMotion vehicle_motion = 13; + // Currently planned route of the vehicle + // + optional Route route = 14; + // // \brief Base parameters and overall states of the vehicle. // @@ -139,7 +144,7 @@ message HostVehicleData // OPERATING_STATE_BOARDING = 4; - // Entertainment, navigation or similiar systems can be used by the driver. + // Entertainment, navigation or similar systems can be used by the driver. // The vehicle can not be driven. // Usually the driver sits in the vehicle before or after a drive. // @@ -563,7 +568,7 @@ message HostVehicleData // STATE_OTHER = 1; - // The function has errored in some way that renders it ineffective. + // The function has thrown an error in some way that renders it ineffective. // STATE_ERRORED = 2; @@ -579,7 +584,7 @@ message HostVehicleData STATE_AVAILABLE = 4; // The function is available but conditions have not caused it to be - // triggered, for example, no vehicles in front to trigger a FCW. + // triggered, for example, no vehicles in front to trigger a forward collision warning. // STATE_STANDBY = 5; diff --git a/osi_lane.proto b/osi_lane.proto index c95a30de1..2ac6b0094 100644 --- a/osi_lane.proto +++ b/osi_lane.proto @@ -42,6 +42,7 @@ message Lane // // \rules // is_globally_unique + // is_set // \endrules // optional Identifier id = 1; @@ -57,7 +58,7 @@ message Lane // // For example, to reference a lane defined in an OpenDRIVE map // the items should be set as follows: - // * reference = URI to map, can remain empty if identical with definiton + // * reference = URI to map, can remain empty if identical with definition // in \c GroundTruth::map_reference // * type = "net.asam.opendrive" // * identifier[0] = id of t_road @@ -188,7 +189,7 @@ message Lane // Indicates that the host vehicle travels on this particular lane. // The host vehicle may travel on more than one lane at once. This does - // also apply for the \c CanditateLane in the \c DetectedLane . + // also apply for the \c CandidateLane in the \c DetectedLane . // optional bool is_host_vehicle_lane = 2; @@ -208,8 +209,8 @@ message Lane // cl: center line // lb: lane boundary // - // \attention The points desribing the center line must be set in the - // same ordering (ascending or descending) as the points desribing the + // \attention The points describing the center line must be set in the + // same ordering (ascending or descending) as the points describing the // lane boundaries. Example: If the points are deducted from a map format, // the order of points is recommended to be in line with the road coordinate // (e.g. s-coordinate in OpenDRIVE). @@ -440,7 +441,7 @@ message Lane // SUBTYPE_NORMAL = 2; - // A lane that is designated for bicylists. + // A lane that is designated for bicycles. // // Since it is not intended to be used for normal automotive // driving, it should be used in combination with TYPE_NONDRIVING. diff --git a/osi_logicaldetectiondata.proto b/osi_logicaldetectiondata.proto index cbf0e71fe..fb7d09f11 100644 --- a/osi_logicaldetectiondata.proto +++ b/osi_logicaldetectiondata.proto @@ -19,6 +19,10 @@ message LogicalDetectionData // The interface version used by the sender (i.e. the simulation // environment). // + // \rules + // is_set + // \endrules + // optional InterfaceVersion version = 1; // Header attributes of fused detections from multiple sensors and sensor types. @@ -219,7 +223,7 @@ message LogicalDetection // \endrules // // \par Reference: - // [1] Rosenberger, P., Holder, M.F., Cianciaruso, N. et al. (2020). Sequential lidar sensor system simulation: a modular approach for simulation-based safety validation of automated driving Automot. Engine Technol. 5, Fig 7, Fig 8. Retrieved May 10, 2021, from https://doi.org/10.1007/s41104-020-00066-x + // [1] Rosenberger, P., Holder, M.F., Cianciaruso, N. et al. (2020). Sequential lidar sensor system simulation: a modular approach for simulation-based safety validation of automated driving Automotive Engine Technology 5, Fig 7, Fig 8. Retrieved May 10, 2021, from https://doi.org/10.1007/s41104-020-00066-x // optional double echo_pulse_width = 12; } diff --git a/osi_logicallane.proto b/osi_logicallane.proto index f709a89a6..fc5d9f86a 100644 --- a/osi_logicallane.proto +++ b/osi_logicallane.proto @@ -3,6 +3,8 @@ syntax = "proto2"; option optimize_for = SPEED; import "osi_common.proto"; +import "osi_object.proto"; +import "osi_trafficsign.proto"; package osi3; @@ -56,6 +58,7 @@ message LogicalLaneBoundary // // \rules // is_globally_unique + // is_set // \endrules // optional Identifier id = 1; @@ -317,7 +320,7 @@ message LogicalLaneBoundary // - The red area is the area where \c l1 overlaps \c l2. This is recorded in // #overlapping_lane of \c l1. // -// As can be seen in the images, the two highlighted lanes are neighbours for +// As can be seen in the images, the two highlighted lanes are neighbors for // part of their length, but it makes no sense for them to have the same // reference line, since they diverge significantly. // @@ -346,7 +349,7 @@ message LogicalLane // // For example, to reference a lane defined in an OpenDRIVE map // the items should be set as follows: - // * reference = URI to map, can remain empty if identical with definiton + // * reference = URI to map, can remain empty if identical with definition // in \c GroundTruth::map_reference // * type = "net.asam.opendrive" // * identifier[0] = id of t_road @@ -419,10 +422,10 @@ message LogicalLane // not required that the reference line has the same direction as the // driving direction of the lane. // - // Neighbouring lanes (i.e. lanes that are neighbours and whose directions + // Neighboring lanes (i.e. lanes that are neighbors and whose directions // do not diverge significantly) are strongly encouraged to reference the // same ReferenceLine, so that vehicles that are next to each other on - // neighbouring lanes have comparable S positions. + // neighboring lanes have comparable S positions. // // The S coordinate of the reference line makes it easy to find e.g. which // object is next on a lane, using the LogicalLaneAssignment of the @@ -586,10 +589,14 @@ message LogicalLane // optional string street_name = 16; + // A list of traffic rules on the lane. + // + repeated TrafficRule traffic_rule = 17; + // // Definition of available lane types. // - // This is mostly aligned with OpenDRIVE, except that lane types modelling + // This is mostly aligned with OpenDRIVE, except that lane types modeling // access restrictions (e.g. "taxi") are not made available here. These are // already deprecated in OpenDRIVE. To support this, access restrictions // should be added later, in alignment with OpenDRIVE. @@ -615,7 +622,7 @@ message LogicalLane // TYPE_NORMAL = 2; - // A lane that is designated for bicylists. + // A lane that is designated for bicycles // Note that biking lanes that cross the road (e.g. on an intersection) // are also labeled with this type. // @@ -821,5 +828,132 @@ message LogicalLane // optional double end_s_other = 5; } + + // + // Describes traffic rules on a lane. + // The traffic rule can thereby be induced by regulations, traffic signs + // or by other means. If the modeled traffic rule is induced by a traffic sign + // the information should be identical with the respective traffic sign. + // + // Note: Every instance should be corresponding to only one specific rule. + // The type of the traffic rule should be set using the respective field. + // Additionally, every message should contain the traffic rule validity information + // and the respective field for the respective traffic rule type. + // In case of traffic rule (priority) conflicts for rules of the same type that + // can not be depicted using the traffic rule validity only the currently + // valid rule should be provided. + // + // Note: Each traffic rule corresponds to only one lane. If the traffic rule + // is also valid on adjacent/successor/predecessor lanes it needs to be + // specified for each lane individually. + // + // \brief Logical Model of a traffic rule on a lane. + // + message TrafficRule { + + // The type of the traffic rule. + // + // This specifies the type of the traffic rule to be modeled. + // Based on the type the respective message containing the information + // corresponding to the traffic rule should be filled. + // + optional TrafficRuleType traffic_rule_type = 1; + + // The validity information of the traffic rule. + // + optional TrafficRuleValidity traffic_rule_validity = 2; + + // Traffic rule information for traffic rule of type speed limit. + // + optional SpeedLimit speed_limit = 3; + + // + // The type of the the traffic rule. + // + enum TrafficRuleType { + + // Traffic rule is of type speed limit + // + TRAFFIC_RULE_TYPE_SPEED_LIMIT = 0; + } + + // + // \brief Validity information for a traffic rule. + // + message TrafficRuleValidity { + + // + // The starting point of the traffic rule validity on the lane. + // Must be in range [\c sStart,\c sEnd] of the reference line. + // + // Note: The traffic rule applies only to traffic with notional + // direction of travel from the start_s coordinate towards + // the end_s coordinate. For unidirectional lanes this must + // match the direction of travel as specified by the + // move_direction field of the logical lane. For bidirectional + // lanes this allows the specification of separate rules for + // each direction of travel. + // + optional double start_s = 1; + + // + // The ending point of the traffic rule validity on the lane. + // Must be in range [\c sStart,\c sEnd] of the reference line. + // + optional double end_s = 2; + + // + // List of traffic participant types for which the speed limit is valid. + // If the traffic rule validity is independent of the vehicle type + // the list should be empty. + // + repeated TypeValidity valid_for_type = 3; + + // + // \brief Type of traffic participant for which a rule is valid. + // + message TypeValidity { + + // + // The type of object for which the traffic rule is valid. + // + optional MovingObject.Type type = 1; + + // + // Vehicle classification type for traffic participants. + // + // Should be set to TYPE_UNKNOWN if type is not TYPE_VEHICLE + // or the rule is valid for all vehicle types. + // + optional MovingObject.VehicleClassification.Type vehicle_type = 2; + + // + // Role of traffic participant. + // + // Should be set to ROLE_UNKNOWN if type is not TYPE_VEHICLE + // or the rule is valid for all vehicle roles. + // + optional MovingObject.VehicleClassification.Role vehicle_role = 3; + } + } + + // + // \brief Speed limit on a lane. + // + message SpeedLimit { + + // + // The value of the speed limit. + // The unit field in the TrafficSignValue message may only be set to + // units associated with velocities and must not be UNKNOWN. + // + // Note: All speed limits are to be modeled this way, independent + // of how they are induced. + // + optional TrafficSignValue speed_limit_value = 1; + + } + } + } diff --git a/osi_motionrequest.proto b/osi_motionrequest.proto index 82560c8e6..be81b0804 100644 --- a/osi_motionrequest.proto +++ b/osi_motionrequest.proto @@ -25,11 +25,19 @@ message MotionRequest { // The interface version used by the sender (simulation environment). // + // \rules + // is_set + // \endrules + // optional InterfaceVersion version = 1; // The data timestamp of the simulation environment. // A reference to \c Timestamp message. // + // \rules + // is_set + // \endrules + // optional Timestamp timestamp = 2; // Define the type that is used to specify the motion request. diff --git a/osi_object.proto b/osi_object.proto index 9d802b680..fb0c518b3 100644 --- a/osi_object.proto +++ b/osi_object.proto @@ -21,6 +21,7 @@ message StationaryObject // // \rules // is_globally_unique + // is_set // \endrules // optional Identifier id = 1; @@ -47,7 +48,7 @@ message StationaryObject // // For example, to reference an object defined in an OpenDRIVE map // the items should be set as follows: - // * reference = URI to map, can remain empty if identical with definiton + // * reference = URI to map, can remain empty if identical with definition // in \c GroundTruth::map_reference // * type = "net.asam.opendrive" // * identifier[0] = "object" for t_road_objects_object and @@ -222,6 +223,10 @@ message StationaryObject // enum Material { + // Allow aliases in enum + // + option allow_alias = true; + // Type of the material is unknown (must not be used in ground // truth). // @@ -251,7 +256,13 @@ message StationaryObject // MATERIAL_STONE = 6; - // Glas structure. + // Glass structure. + // + MATERIAL_GLASS = 7; + + // Glass structure. + // + // \note Deprecated variant spelling of MATERIAL_GLASS // MATERIAL_GLAS = 7; @@ -302,6 +313,10 @@ message StationaryObject // enum Color { + // Allow aliases in enum + // + option allow_alias = true; + // Color is unknown (must not be used in ground truth). // COLOR_UNKNOWN = 0; @@ -338,7 +353,13 @@ message StationaryObject // COLOR_BLACK = 8; - // GREY. + // GRAY. + // + COLOR_GRAY = 9; + + // GRAY. + // + // \note Deprecated variant spelling of COLOR_GRAY // COLOR_GREY = 9; @@ -388,6 +409,7 @@ message MovingObject // // \rules // is_globally_unique + // is_set // \endrules // optional Identifier id = 1; @@ -498,8 +520,6 @@ message MovingObject // * identifier[0] = Entity-Type ("Vehicle" or "Pedestrian") // * identifier[1] = name of Vehicle/Pedestrian in Entity // - // \todo OpenSCENARIO currently does not provide an animal type. - // // \note For non-ASAM Standards, it is implementation-specific how // source_reference is resolved. // @@ -607,7 +627,7 @@ message MovingObject repeated WheelData wheel_data = 7; // Angle of the steering wheel. - // Zero means the steering wheel is in its center postion, a positive value + // Zero means the steering wheel is in its center position, a positive value // means the steering wheel is turned to the left and a negative value // means the steering wheel is turned to the right of the center position. // @@ -688,7 +708,7 @@ message MovingObject // optional string model_reference = 9; - // The value describes the kinetic friction of the tyre's contact point. + // The value describes the kinetic friction of the tire's contact point. // If different friction coefficients due to more than one contact points are available, // this value contains the average. // @@ -744,20 +764,26 @@ message MovingObject // other OpenX standards (in particular, OpenScenario 1.x and 2.x, and OpenLabel). // This is primarily for historical reasons. Where a single type from a // different standard can map to multiple OSI types it is left up to the - // discretion of the OSI implementor how that mapping is achieved. For example, - // a simulator may use the dimensions of a provided 3d model of a vehicle with type - // "car" in OpenScenario, to determine whether it should be a TYPE_SMALL_CAR or - // TYPE_MEDIUM_CAR in OSI. + // discretion of the OSI implementer how that mapping is achieved. In previous + // versions, for example, a simulator might have used the dimensions of a provided + // 3d model of a vehicle with type "car" in OpenScenario, to determine whether it + // should be a TYPE_SMALL_CAR or TYPE_MEDIUM_CAR in OSI. As part of the harmonization + // effort, it should now map to TYPE_CAR, which is an alias of the old TYPE_MEDIUM_CAR, + // and all other car type enums have been deprecated in favor of TYPE_CAR. // // \note Vehicle type classification is a complex area and there are no - // universally recognised standards. As such, the boundaries between some of the - // OSI vehicle types are not well-defined. It is left to the implementor to + // universally recognized standards. As such, the boundaries between some of the + // OSI vehicle types are not well-defined. It is left to the implementer to // decide how to distinguish between them and agree that with any applications which // make use of that specific interface instance. For example, how to distinguish // between a HEAVY_TRUCK and a DELIVERY_VAN, or a TRAILER and a SEMITRAILER. // enum Type { + // Allow aliases in enum + // + option allow_alias = true; + // Type of vehicle is unknown (must not be used in ground truth). // TYPE_UNKNOWN = 0; @@ -770,17 +796,30 @@ message MovingObject // // Definition: Hatchback car with maximum length 4 m. // + // \note Deprecated differentiation, use TYPE_CAR instead + // TYPE_SMALL_CAR = 2; // Vehicle is a compact car. // // Definition: Hatchback car with length between 4 and 4.5 m. // + // \note Deprecated differentiation, use TYPE_CAR instead + // TYPE_COMPACT_CAR = 3; + // Vehicle is a car. + // + // This is to be used for all car-like vehicles, without any + // further differentiated type available. + // + TYPE_CAR = 4; + // Vehicle is a medium car. // - // Definition: Hatchback or sedan with lenght between 4.5 and 5 m. + // Definition: Hatchback or sedan with length between 4.5 and 5 m. + // + // \note Deprecated differentiation, use the alias TYPE_CAR instead // TYPE_MEDIUM_CAR = 4; @@ -788,6 +827,8 @@ message MovingObject // // Definition: Sedan or coupe that is longer then 5 m. // + // \note Deprecated differentiation, use TYPE_CAR instead + // TYPE_LUXURY_CAR = 5; // Vehicle is a delivery van. @@ -851,7 +892,7 @@ message MovingObject // TYPE_WHEELCHAIR = 15; - // Vehicle is a stand-up or kickboard scooter, including + // Vehicle is a stand-up scooter, including // motorized versions. // TYPE_STANDUP_SCOOTER = 17; @@ -1172,6 +1213,20 @@ message MovingObject // optional bool missing = 5; + // The velocity of the bone. + // + // Reference System is the root, defined by bbcenter_to_root + // (\c PedestrianAttributes::bbcenter_to_root). + // + optional Vector3d velocity = 6; + + // The orientation rate of the bone. + // + // Reference System is the root, defined by bbcenter_to_root + // (\c PedestrianAttributes::bbcenter_to_root). + // + optional Orientation3d orientation_rate = 7; + // The type of the bone. // // \image html OSI_PedestrianModelHierarchy.jpg diff --git a/osi_referenceline.proto b/osi_referenceline.proto index b6b3bc5e4..e49faac7d 100644 --- a/osi_referenceline.proto +++ b/osi_referenceline.proto @@ -32,6 +32,7 @@ message ReferenceLine // // \rules // is_globally_unique + // is_set // \endrules // optional Identifier id = 1; @@ -52,7 +53,7 @@ message ReferenceLine // ReferenceLine is a polyline, where the coordinates of points are // calculated by projection onto the nearest point on the line. // - // \attention DEPRECATED: Due to the shortcomings documenten below, this + // \attention DEPRECATED: Due to the shortcomings documented below, this // type will be removed in 4.0.0. // TYPE_POLYLINE = 0; @@ -157,12 +158,12 @@ message ReferenceLine // point on the polyline. The resulting ST coordinate uniquely maps back // to \c P1. // - \c P2 has multiple points "nearest points" on the polyline. - // As can be seen here, two ST coordinates map to \c P2 (red and grey + // As can be seen here, two ST coordinates map to \c P2 (red and gray // dotted line). Following the rules above, the one with the smallest S // value is chosen (the red dotted line). // - \c P3 has a unique "nearest point" on the polyline. However, multiple // points map to the same ST coordinate as that of \c P3, e.g. \c P4 - // (drawn in grey). + // (drawn in gray). // - Finally, \c P5 shows how the reference line is extended infinitely for // points that are "outside" the reference line. // @@ -268,10 +269,10 @@ message ReferenceLine // in OSI. There are a few difficulties with this: // - The T coordinate is nearly the same as for OpenDRIVE, but // unfortunately not perfectly. In OpenDRIVE, if the road is tilted using - // superElevation, then the t coordinate system is tilted along, so the T + // superelevation, then the t coordinate system is tilted along, so the T // coordinate is no longer calculated in the XY plane (as proposed for - // OSI). It doesn't seem feasable to implement the same tilting for OSI, - // so simulation tools will have to consider superElevation and convert + // OSI). It doesn't seem feasible to implement the same tilting for OSI, + // so simulation tools will have to consider superelevation and convert // the T coordinate accordingly: t_OSI = t_OpenDRIVE * // cos(alpha), where alpha is the superelevation angle. // - The angle will not be perfectly the same, due to the use of line diff --git a/osi_roadmarking.proto b/osi_roadmarking.proto index d8ed5a2e0..ea1b85912 100644 --- a/osi_roadmarking.proto +++ b/osi_roadmarking.proto @@ -29,6 +29,10 @@ message RoadMarking { // The ID of the road marking. // + // \rules + // is_set + // \endrules + // optional Identifier id = 1; // The base parameters of the road marking. @@ -63,7 +67,7 @@ message RoadMarking // // For example, to reference a signal defined in an OpenDRIVE map // the items should be set as follows: - // * reference = URI to map, can remain empty if identical with definiton + // * reference = URI to map, can remain empty if identical with definition // in \c GroundTruth::map_reference // * type = "net.asam.opendrive" // * identifier[0] = id of t_road_signals_signal diff --git a/osi_route.proto b/osi_route.proto new file mode 100644 index 000000000..794c6bb20 --- /dev/null +++ b/osi_route.proto @@ -0,0 +1,138 @@ +syntax = "proto2"; + +option optimize_for = SPEED; + +import "osi_common.proto"; + +package osi3; + +// +// \brief A route in the road network +// +// A route is an e.g. planned or suggested path for an agent to travel from one +// location to another within the road network. It is composed of a list of route +// segments, which form a continuous path through the road network and should be +// traversed in the order they are listed. +// The route allows the simulation environment to provide agents with high level +// path information, similar to that of a map or a navigation system, without the +// need for the agent model to perform complex path planning on its own. This +// allows for an efficient control of the agent's general direction, while +// simultaneously giving it enough freedom on how to traverse the path. +// +// ## Example +// +// The example below shows the \link Route route\endlink of a vehicle. +// +// \image html OSI_Planned_Route.png "Route" width=850px +// +// The route is composed of three \link RouteSegment route segments\endlink RS1-3, +// each indicated by a yellow outline. Two of the route segments +// (RS2 and RS3) only contain a single \link LogicalLaneSegment logical lane segment\endlink +// (highlighted in blue), while RS1 is composed of three +// logical lane segments (green, blue and red). +// +message Route +{ + // The unique id of the route. + // + // \note This field is mandatory. + // + // \note This id must be unique within all route messages exchanged with + // one traffic participant. + // + // \rules + // is_set + // \endrules + // + optional Identifier route_id = 1; + + // Route segments that form the route of an agent. + // + // Consecutive segments should be connected without gaps, meaning that the + // two of them should form a continuous area. + // + repeated RouteSegment route_segment = 2; + + // + // \brief A segment of a logical lane. + // + // \note The LogicalLaneSegment allows that start_s > end_s. + // If start_s < end_s, then the traffic agent should traverse the + // segment in the logical lane's reference line definition direction. + // If end_s > start_s, then the traffic agent should traverse the + // segment in the opposite of the logical lane's reference line + // definition direction. + // + message LogicalLaneSegment + { + // The ID of the logical lane this segment belongs to. + // + // \rules + // refers_to: LogicalLane + // \endrules + // + optional Identifier logical_lane_id = 1; + + // S position on the logical lane where the segment starts. + // + optional double start_s = 2; + + // S position on the logical lane where the segment ends. + // + optional double end_s = 3; + } + + // + // \brief A segment of a route. + // + // A route segment describes a segment of a traffic agent's route through the + // logical lanes of the road network. + // + // Each time there is a successor-predecessor relation between the logical + // lanes along the route (i.e. a logical lane ends, and is continued by another + // logical lane, e.g. at a junction border), a new RouteSegment starts. The + // RouteSegment then lists the logical lane segments that can be used to + // travel through this space of the road. + // + // Together, the listed logical lane segments should form a continuous area, + // where the traffic agent can move freely. These will mostly be parallel + // lanes, though lanes may overlap (e.g. if one lane splits into two on a + // junction). In general, the logical lane segments in a RouteSegment will + // have the same length, though there are exceptions (e.g. if a lane + // widening occurs, the newly appearing lane will have a shorter length). + // + // Typically a route segment will be either + // - a set of parallel lanes between two junctions, or + // - parallel lanes on an intersection with the same driving direction + // + // ## Example + // + // Consider the \link RouteSegment route segment\endlink between two intersections, + // shown in the image below. + // + // \image html OSI_Route_Segment.png "RouteSegment" width=850px + // + // In the example, a single route segment RS with three + // \link LogicalLaneSegment logical lane segments\endlink LL1, LL2 and LL3 is + // shown. The segments are indicated by the green, blue and red highlighted areas, + // one for each underlying logical lane The starting + // s-position of each segment is indicated by the yellow dotted line and the s- prefix + // (note that the start of LL2 lies further to the left, outside of the image), + // while the ending s-position of all segments is shown by the yellow dotted line e-RS. + // + // As it can be seen in the example, all logical lane segments are parallel, + // but two of them are opening at a later position, so their starting + // s-positions will be different. + // + message RouteSegment + { + + // Logical lane segments that form a route segment. + // + // The logical lane segments of a route segment should be connected without + // gaps, meaning that, together, the lane segments should form a continuous + // area. + // + repeated LogicalLaneSegment lane_segment = 1; + } +} diff --git a/osi_sensordata.proto b/osi_sensordata.proto index 689149370..9ece68316 100644 --- a/osi_sensordata.proto +++ b/osi_sensordata.proto @@ -102,6 +102,10 @@ message SensorData { // The interface version used by the sender. // + // \rules + // is_set + // \endrules + // optional InterfaceVersion version = 1; // The timestamp of the sensor data. Zero time is arbitrary but must be @@ -129,6 +133,10 @@ message SensorData // a dumb sensor with no internal time concept), the two timestamps might // also be identical, but delayed from the \c GroundTruth timestamp. // + // \rules + // is_set + // \endrules + // optional Timestamp timestamp = 2; // The sensors estimated location of the host vehicle @@ -156,6 +164,10 @@ message SensorData // object output; it is distinct from the IDs of its physical detectors, // which are used in the detected features. // + // \rules + // is_set + // \endrules + // optional Identifier sensor_id = 5; // The virtual mounting position of the sensor (origin and orientation @@ -186,6 +198,10 @@ message SensorData // vehicle's bounding box \c MovingObject::base . \c // BaseMoving::orientation. // + // \rules + // is_set + // \endrules + // optional MountingPosition mounting_position = 6; // The root mean squared error of the mounting position. @@ -297,4 +313,38 @@ message SensorData // in cartesian coordinates. // optional LogicalDetectionData logical_detection_data = 27; + + // + // \brief Virtual detection area of a sensor + // + // The virtual detection area describes the nominal area the sensor is capable of covering + // in its current operating mode, without taking occlusion or other statistical effects into account. + // This information can be used for visualization or other development purposes as a rough guide + // to nominal sensor performance. + // + // It is described by a set of polygons in cartesian coordinates as a pragmatic approximation for + // the rough shapes expected. + // + message VirtualDetectionArea + { + // List of polygons. Each polygon represents a surface of the virtual detection area + // and is given with respect to the virtual sensor coordinate system. + // + repeated Polygon3d polygon = 1; + } + + // Virtual detection area of the sensor + // + optional VirtualDetectionArea virtual_detection_area = 28; + + // The system time of the modeled source of the sensor data, given + // in UTC (Unix Epoch timestamp). + // + // The system time can be used to transmit the internal time of the + // simulated component that supplies the sensor data, which might + // not coincide with the simulation time as transmitted in the + // timestamp field. Example use cases include recorded data traces + // or the simulation of time synchronization mechanisms and errors. + // + optional Timestamp system_time = 29; } diff --git a/osi_sensorview.proto b/osi_sensorview.proto index 8d595ed92..68973fbf1 100644 --- a/osi_sensorview.proto +++ b/osi_sensorview.proto @@ -36,6 +36,10 @@ message SensorView { // The interface version used by the sender (simulation environment). // + // \rules + // is_set + // \endrules + // optional InterfaceVersion version = 1; // The data timestamp of the simulation environment. Zero time is arbitrary @@ -48,6 +52,10 @@ message SensorView // (there is no inherent latency for sensor view data, as opposed to // sensor data). // + // \rules + // is_set + // \endrules + // optional Timestamp timestamp = 2; // The ID of the sensor at host vehicle's \c #mounting_position. @@ -57,7 +65,7 @@ message SensorView // which are used in the detected features. // // \rules - // is_globally_unique + // is_set // \endrules // optional Identifier sensor_id = 3; @@ -92,6 +100,10 @@ message SensorView // vehicle's bounding box \c MovingObject::base . \c // BaseMoving::orientation. // + // \rules + // is_set + // \endrules + // optional MountingPosition mounting_position = 4; // The root mean squared error of the mounting position. @@ -127,6 +139,7 @@ message SensorView // // \rules // refers_to: 'MovingObject' + // is_set // \endrules // optional Identifier host_vehicle_id = 8; @@ -287,7 +300,7 @@ message LidarSensorView // normal to surface angle. // - // The normal of the transmitted beam to the object, roadmarking etc + // The normal of the transmitted beam to the object, road marking, etc. // encounter. \note data is in Lidar coordinate system // // Unit: unit vector @@ -295,7 +308,7 @@ message LidarSensorView optional Vector3d normal_to_surface = 5; // ID of the detected object this reflection is associated to. - // can be used for raytracing debug + // can be used for ray tracing debug // // \note ID = MAX(uint64) indicates no reference to an object. optional Identifier object_id = 6; diff --git a/osi_sensorviewconfiguration.proto b/osi_sensorviewconfiguration.proto index fc3720ebc..48bfd8f8c 100644 --- a/osi_sensorviewconfiguration.proto +++ b/osi_sensorviewconfiguration.proto @@ -66,6 +66,10 @@ message SensorViewConfiguration { // The interface version used by the sender (simulation environment). // + // \rules + // is_set + // \endrules + // optional InterfaceVersion version = 1; // The ID of the sensor at host vehicle's mounting_position. @@ -77,6 +81,10 @@ message SensorViewConfiguration // The ID is to be provided by the environment simulation, the sensor // model is not in a position to provide a useful default value. // + // \rules + // is_set + // \endrules + // optional Identifier sensor_id = 2; // The virtual mounting position of the sensor (origin and orientation @@ -176,7 +184,7 @@ message SensorViewConfiguration // update to the sensor input should happen at 0.048s, or 0.018s // after simulation start. This convention is needed to ensure // stable phase position of the offset in the case of changing - // simulation start times, e.g. for partial resimulation. + // simulation start times, e.g. for partial re-simulation. // // Unit: s optional Timestamp update_cycle_offset = 9; @@ -193,10 +201,15 @@ message SensorViewConfiguration // Omit Static Information // // This flag specifies whether \c GroundTruth information that - // was already provided using a GroundTruthInit parameter + // was already provided using a GroundTruthInit parameter (e.g. OSMP GroundTruthInit) // at initialization time shall be omitted from the \c SensorView // ground truth information. // + // Setting the \c #omit_static_information field allows a clear split + // between the dynamic simulation data, which is contained in ground truth + // messages with the \c #omit_static_information flag, and the static + // simulation data, which is contained in the (OSMP) GroundTruthInit. + // optional bool omit_static_information = 11; // Generic Sensor View Configuration(s). diff --git a/osi_streamingupdate.proto b/osi_streamingupdate.proto index b1ad1121a..ceafb05cd 100644 --- a/osi_streamingupdate.proto +++ b/osi_streamingupdate.proto @@ -23,13 +23,17 @@ package osi3; // // \note The receiver of partial streaming update messages can only rely on the // most up-to-date information at the corresponding timestamp. E.g. omitting -// objects does not indicate static behaviour but it may be sufficient for the +// objects does not indicate static behavior but it may be sufficient for the // use case to update certain objects at a later point in time. // message StreamingUpdate { // The interface version used by the sender. // + // \rules + // is_set + // \endrules + // optional InterfaceVersion version = 1; // The data timestamp where the information of contained objects is calculated. @@ -38,6 +42,10 @@ message StreamingUpdate // Zero time does not need to coincide with the UNIX epoch. // Recommended is the starting time point of the simulation. // + // \rules + // is_set + // \endrules + // optional Timestamp timestamp = 2; // The list of stationary objects (excluding traffic signs and traffic diff --git a/osi_trafficcommand.proto b/osi_trafficcommand.proto index 6e717e3e8..d8763b430 100644 --- a/osi_trafficcommand.proto +++ b/osi_trafficcommand.proto @@ -21,6 +21,10 @@ message TrafficCommand { // The interface version used by the sender (scenario engine). // + // \rules + // is_set + // \endrules + // optional InterfaceVersion version = 1; // The data timestamp of the simulation environment. Zero time is arbitrary @@ -33,10 +37,18 @@ message TrafficCommand // There is no inherent latency for traffic command data, as opposed // to sensor data. // + // \rules + // is_set + // \endrules + // optional Timestamp timestamp = 2; // The ID of this traffic participant. // + // \rules + // is_set + // \endrules + // optional Identifier traffic_participant_id = 3; // Commanded traffic action(s) if any. @@ -55,7 +67,7 @@ message TrafficCommand // // \note This message is notionally a multiple choice selection, that is, only // certain combinations of atomic traffic actions shall be transmitted within -// certain time intervals, for example, for plausibity reasons. The restrictions +// certain time intervals, for example, for plausibility reasons. The restrictions // regarding that are not part of this message, yet are seen as a task of the // scenario description, for example, OpenSCENARIO. // @@ -65,7 +77,7 @@ message TrafficCommand // model that certain actions must or shall be terminated, there are // explicit actions nested inside this message (AbortActionsAction, // EndActionsAction), which hold a reference to the respective actions. -// Futhermore, there exists a \c TrafficCommandUpdate message for the +// Furthermore, there exists a \c TrafficCommandUpdate message for the // traffic participant to report back on potentially dismissed actions. // message TrafficAction @@ -130,6 +142,10 @@ message TrafficAction // \note This id must be unique within all traffic command // messages exchanged with one traffic participant. // + // \rules + // is_set + // \endrules + // optional Identifier action_id = 1; } @@ -185,19 +201,19 @@ message TrafficAction // message DynamicConstraints { - // Maximum acceleration the distance contoller is allowed to use for keeping distance. + // Maximum acceleration the distance controller is allowed to use for keeping distance. // // Unit: m/s^2 // optional double max_acceleration = 1; - // Maximum deceleration the distance contoller is allowed to use for keeping distance. + // Maximum deceleration the distance controller is allowed to use for keeping distance. // // Unit: m/s^2 // optional double max_deceleration = 2; - // Maximum speed the distance contoller is allowed to use for keeping distance. + // Maximum speed the distance controller is allowed to use for keeping distance. // // Unit: m/s // @@ -453,7 +469,7 @@ message TrafficAction // \brief End actions action. // - // This action tells a traffic participant that the exection of the + // This action tells a traffic participant that the execution of the // referenced actions is regarded as successfully performed. The // termination of the referenced actions is allowed to be performed // gracefully. @@ -512,7 +528,7 @@ message TrafficAction // relative to a target traffic participant. The longitudinal distance is defined as the distance // along the centerline of the lane, on which the (host) traffic participant is currently located. // The interpolation strategy between centerline points for calculating - // that distance along the centerline is open to the traffic participant modeller. + // that distance along the centerline is open to the traffic participant modeler. // // \note This action is aligned with LongitudinalDistanceAction of OpenSCENARIO 1.0 // defining the reference traffic participant and the distance. @@ -540,11 +556,11 @@ message TrafficAction // participant receiving this action and the reference traffic // participant. // True: Longitudinal distance is measured using the distance between closest bounding box points. - // False: Longitudinal distance is mesasured using the distance between the center of each object's bounding box. + // False: Longitudinal distance is measured using the distance between the center of each object's bounding box. // optional bool freespace = 4; - // Define wheather the traffic participant should only reach the distance once + // Define whether the traffic participant should only reach the distance once // or if it should also keep the distance after having reached it. // True: the traffic participant shall approach the reference participant // and follow with the distance specified until the action is aborted (communicate with AbortActionsAction). @@ -553,7 +569,7 @@ message TrafficAction // optional bool follow = 5; - // Parameter that assings either unlimited dynamics (if omitted) + // Parameter that assigns either unlimited dynamics (if omitted) // or limited maxAcceleration/maxDeceleration/maxSpeed to the action. // optional DynamicConstraints dynamic_constraints = 6; @@ -565,7 +581,7 @@ message TrafficAction // a target traffic participant. The lateral distance is defined along an imaginative perpendicular line // with respect to the centerline of the current (host) traffic participant's lane. // The interpolation strategy between centerline points for calculating that distance along the imaginative - // perpendicular line is open to the traffic participant modeller. + // perpendicular line is open to the traffic participant modeler. // // \note This action is aligned with LateralDistanceAction of OpenSCENARIO 1.0 // defining the reference traffic participant and the distance. @@ -593,11 +609,11 @@ message TrafficAction // participant receiving this action and the reference traffic // participant. // True: Lateral distance is measured using the distance between closest bounding box points. - // False: Lateral distance is mesasured using the distance between the center of each object's bounding box. + // False: Lateral distance is measured using the distance between the center of each object's bounding box. // optional bool freespace = 4; - // Define wheather the traffic participant should only reach the distance once + // Define whether the traffic participant should only reach the distance once // or if it should also keep the distance after having reached it. // True: the traffic participant shall approach the reference participant // and follow with the distance specified until the action is aborted (communicate with AbortActionsAction). @@ -606,7 +622,7 @@ message TrafficAction // optional bool follow = 5; - // Parameter that assings either unlimited dynamics (if omitted) + // Parameter that assigns either unlimited dynamics (if omitted) // or limited maxAcceleration/maxDeceleration/maxSpeed to the action. // optional DynamicConstraints dynamic_constraints = 6; diff --git a/osi_trafficcommandupdate.proto b/osi_trafficcommandupdate.proto index d55d38ff3..c341acfdc 100644 --- a/osi_trafficcommandupdate.proto +++ b/osi_trafficcommandupdate.proto @@ -29,6 +29,10 @@ message TrafficCommandUpdate { // The interface version used by the sender (traffic participant model). // + // \rules + // is_set + // \endrules + // optional InterfaceVersion version = 1; // The data timestamp of the simulation environment. Zero time is arbitrary @@ -36,11 +40,19 @@ message TrafficCommandUpdate // coincide with the UNIX epoch. It is recommended to use zero timestamp as // the starting time point of the simulation. // + // \rules + // is_set + // \endrules + // optional Timestamp timestamp = 2; // The ID of this traffic participant which must coincide with a prior sent ID, cf. // \c TrafficCommand::traffic_participant_id. // + // \rules + // is_set + // \endrules + // optional Identifier traffic_participant_id = 3; // Actions which a traffic participant dismisses and which are not yet ended or diff --git a/osi_trafficlight.proto b/osi_trafficlight.proto index 887a99fd4..6dfba2929 100644 --- a/osi_trafficlight.proto +++ b/osi_trafficlight.proto @@ -20,6 +20,7 @@ message TrafficLight // // \rules // is_globally_unique + // is_set // \endrules // optional Identifier id = 1; @@ -123,7 +124,7 @@ message TrafficLight // Boolean flag to indicate that the traffic light is taken out of service. // This can be achieved by visibly crossing the light, covering it completely - // or swiching the traffic light off. + // or switching the traffic light off. // optional bool is_out_of_service = 6; diff --git a/osi_trafficsign.proto b/osi_trafficsign.proto index f15aad856..fa3a0e4d3 100644 --- a/osi_trafficsign.proto +++ b/osi_trafficsign.proto @@ -173,6 +173,7 @@ message TrafficSign // // \rules // is_globally_unique + // is_set // \endrules // optional Identifier id = 1; @@ -3446,7 +3447,7 @@ message TrafficSign // --> // // - // StVO 353 - Valid only until october 2022. + // StVO 353 - Valid only until October 2022. // // // @@ -3816,7 +3817,7 @@ message TrafficSign // --> // // - // StVO 380 - Valid only until october 2022. + // StVO 380 - Valid only until October 2022. // // // @@ -3835,7 +3836,7 @@ message TrafficSign // --> // // - // StVO 381 - Valid only until october 2022 + // StVO 381 - Valid only until October 2022 // // // @@ -3922,7 +3923,7 @@ message TrafficSign // --> // // - // StVO 388 - Valid only until october 2022 + // StVO 388 - Valid only until October 2022 // // // @@ -3939,7 +3940,7 @@ message TrafficSign // --> // // - // StVO 389 - Valid only until october 2022 + // StVO 389 - Valid only until October 2022 // // // @@ -5373,7 +5374,7 @@ message TrafficSign // // // - // \note Additional traffic signs are modelled as separate main + // \note Additional traffic signs are modeled as separate main // signs. // TYPE_MOBILE_LANE_CLOSURE = 139; @@ -6760,7 +6761,7 @@ message TrafficSign // [\c rules above](\ref TYPE_TIME). //
// See also: [\c Two time ranges](\ref StVO_1040-31), - // [\c Working days except saturdays](\ref StVO_1042-38). + // [\c Working days except Saturdays](\ref StVO_1042-38). // // // @@ -6789,7 +6790,7 @@ message TrafficSign // ”werktags außer samstags” // // - // Working days except saturdays. + // Working days except Saturdays. // // // @@ -8197,7 +8198,7 @@ message TrafficSign // \c #ACTOR_BUSES // // - // Except buses in ocasional service. + // Except buses in occasional service. //
// Set \c TrafficSignValue::text as "im Gelegenheitsverkehr". // @@ -9453,7 +9454,7 @@ message TrafficSign // DIRECTION_DIRECT_135_DEG_LEFT = 9; - // A straight arrow pointing oposite to the direction of + // A straight arrow pointing opposite to the direction of // driving. // DIRECTION_DIRECT_180_DEG = 10; @@ -9539,7 +9540,7 @@ message TrafficSign DIRECTION_CIRCLE_135_DEG_LEFT = 25; // An arrow that includes a fraction of a circle and points - // in the oposite to the direction of driving. Can be used + // in the opposite to the direction of driving. Can be used // in detours in roundabouts. // DIRECTION_CIRCLE_180_DEG = 26; diff --git a/osi_trafficupdate.proto b/osi_trafficupdate.proto index 5b157c668..277766121 100644 --- a/osi_trafficupdate.proto +++ b/osi_trafficupdate.proto @@ -20,13 +20,17 @@ package osi3; // information is provided as a MovingObject. Certain fields of this // sub-message are not required to be set and will be ignored by the // simulation environment, because they are static information. -// Instead of creating a seperate message type for only the non-static +// Instead of creating a separate message type for only the non-static // information, re-use existing message. // message TrafficUpdate { // The interface version used by the sender (traffic participant model). // + // \rules + // is_set + // \endrules + // optional InterfaceVersion version = 1; // The data timestamp of the simulation environment. Zero time is arbitrary @@ -39,6 +43,10 @@ message TrafficUpdate // There is no inherent latency for moving object update data, as opposed // to sensor data. // + // \rules + // is_set + // \endrules + // optional Timestamp timestamp = 2; // Updated traffic participant data diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..6627fa881 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = [ + "setuptools", + "wheel", + "protoc-wheel-0==24.4", +] +build-backend = "setuptools.build_meta" + +[project] +name = "open-simulation-interface" +description = "ASAM Open Simulation Interface Python Bindings." +authors = [ + {name = "ASAM Open Simulation Interface Project", email = "osi@asam.net"}, +] +maintainers = [ + {name = "ASAM Open Simulation Interface Project", email = "osi@asam.net"}, +] +dependencies = [ + "protobuf>=4.24.4", +] + +license = {file = "LICENSE"} +readme = "README.md" +classifiers = [ + "License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)", +] +dynamic = ["version"] + +[project.urls] +Homepage = "https://github.com/OpenSimulationInterface/open-simulation-interface" +Repository = "https://github.com/OpenSimulationInterface/open-simulation-interface.git" +"Bug Tracker" = "https://github.com/OpenSimulationInterface/open-simulation-interface/issues" + +[project.scripts] +osi2read = "osi3trace.osi2read:main" diff --git a/requirements_tests.txt b/requirements_tests.txt new file mode 100644 index 000000000..4872aecca --- /dev/null +++ b/requirements_tests.txt @@ -0,0 +1,2 @@ +pyyaml +black==24.3.0 diff --git a/rules.yml b/rules.yml index 473bf51b2..41db521c3 100644 --- a/rules.yml +++ b/rules.yml @@ -9,4 +9,5 @@ refers_to: '^[ ]\b(refers_to)\b' is_iso_country_code: '^[ ]\b(is_iso_country_code)\b' first_element: '^[ ]\b(first_element)\b' last_element: '^[ ]\b(last_element)\b' -check_if: '^[ ](\bcheck_if\b)(.*\belse do_check\b)' \ No newline at end of file +check_if: '^[ ](\bcheck_if\b)(.*\belse do_check\b)' +is_set: '^[ ]\b(is_set)\b' diff --git a/setup.py b/setup.py index 435207aeb..5a310a8b3 100644 --- a/setup.py +++ b/setup.py @@ -8,123 +8,156 @@ from distutils.spawn import find_executable from setuptools import setup +from setuptools.command.sdist import sdist from setuptools.command.build_py import build_py +# protoc +from protoc import PROTOC_EXE + # configure the version number -from shutil import copyfile -copyfile('VERSION', 'version.py') -from version import * -with open("osi_version.proto.in", "rt") as fin: - with open("osi_version.proto", "wt") as fout: - for line in fin: - lineConfigured = line.replace('@VERSION_MAJOR@',str(VERSION_MAJOR)) - lineConfigured = lineConfigured.replace('@VERSION_MINOR@',str(VERSION_MINOR)) - lineConfigured = lineConfigured.replace('@VERSION_PATCH@',str(VERSION_PATCH)) - fout.write(lineConfigured) - -package_name = 'osi3' +VERSION_MAJOR = None +VERSION_MINOR = None +VERSION_PATCH = None +VERSION_SUFFIX = None +with open("VERSION", "rt") as versionin: + for line in versionin: + if line.startswith("VERSION_MAJOR"): + VERSION_MAJOR = int(line.split("=")[1].strip()) + if line.startswith("VERSION_MINOR"): + VERSION_MINOR = int(line.split("=")[1].strip()) + if line.startswith("VERSION_PATCH"): + VERSION_PATCH = int(line.split("=")[1].strip()) + if line.startswith("VERSION_SUFFIX"): + VERSION_SUFFIX = line.split("=")[1].strip() + +package_name = "osi3" package_path = os.path.join(os.getcwd(), package_name) -class GenerateProtobufCommand(build_py): +class ProtobufGenerator: @staticmethod def find_protoc(): """Locates protoc executable""" - if 'PROTOC' in os.environ and os.path.exists(os.environ['PROTOC']): - protoc = os.environ['PROTOC'] + if os.path.exists(PROTOC_EXE): + protoc = PROTOC_EXE + elif "PROTOC" in os.environ and os.path.exists(os.environ["PROTOC"]): + protoc = os.environ["PROTOC"] else: - protoc = find_executable('protoc') + protoc = find_executable("protoc") if protoc is None: sys.stderr.write( - 'protoc not found. Is protobuf-compiler installed? \n' - 'Alternatively, you can point the PROTOC environment variable ' - 'to a local version.') + "protoc not found. Is protobuf-compiler installed? \n" + "Alternatively, you can point the PROTOC environment variable " + "to a local version." + ) sys.exit(1) return protoc osi_files = ( - 'osi_common.proto', - 'osi_datarecording.proto', - 'osi_detectedlane.proto', - 'osi_detectedobject.proto', - 'osi_detectedoccupant.proto', - 'osi_detectedroadmarking.proto', - 'osi_detectedtrafficlight.proto', - 'osi_detectedtrafficsign.proto', - 'osi_environment.proto', - 'osi_featuredata.proto', - 'osi_groundtruth.proto', - 'osi_hostvehicledata.proto', - 'osi_lane.proto', - 'osi_logicaldetectiondata.proto', - 'osi_logicallane.proto', - 'osi_motionrequest.proto', - 'osi_object.proto', - 'osi_occupant.proto', - 'osi_referenceline.proto', - 'osi_roadmarking.proto', - 'osi_sensordata.proto', - 'osi_sensorspecific.proto', - 'osi_sensorview.proto', - 'osi_sensorviewconfiguration.proto', - 'osi_streamingupdate.proto', - 'osi_trafficcommand.proto', - 'osi_trafficcommandupdate.proto', - 'osi_trafficlight.proto', - 'osi_trafficsign.proto', - 'osi_trafficupdate.proto', - 'osi_version.proto') + "osi_common.proto", + "osi_datarecording.proto", + "osi_detectedlane.proto", + "osi_detectedobject.proto", + "osi_detectedoccupant.proto", + "osi_detectedroadmarking.proto", + "osi_detectedtrafficlight.proto", + "osi_detectedtrafficsign.proto", + "osi_environment.proto", + "osi_featuredata.proto", + "osi_groundtruth.proto", + "osi_hostvehicledata.proto", + "osi_lane.proto", + "osi_logicaldetectiondata.proto", + "osi_logicallane.proto", + "osi_motionrequest.proto", + "osi_object.proto", + "osi_occupant.proto", + "osi_referenceline.proto", + "osi_roadmarking.proto", + "osi_route.proto", + "osi_sensordata.proto", + "osi_sensorspecific.proto", + "osi_sensorview.proto", + "osi_sensorviewconfiguration.proto", + "osi_streamingupdate.proto", + "osi_trafficcommand.proto", + "osi_trafficcommandupdate.proto", + "osi_trafficlight.proto", + "osi_trafficsign.proto", + "osi_trafficupdate.proto", + "osi_version.proto", + ) """ Generate Protobuf Messages """ - def run(self): + def generate(self): + sys.stdout.write("Generating Protobuf Version Message\n") + with open("osi_version.proto.in", "rt") as fin: + with open("osi_version.proto", "wt") as fout: + for line in fin: + lineConfigured = line.replace("@VERSION_MAJOR@", str(VERSION_MAJOR)) + lineConfigured = lineConfigured.replace( + "@VERSION_MINOR@", str(VERSION_MINOR) + ) + lineConfigured = lineConfigured.replace( + "@VERSION_PATCH@", str(VERSION_PATCH) + ) + fout.write(lineConfigured) pattern = re.compile('^import "osi_') for source in self.osi_files: with open(source) as src_file: - with open(os.path.join(package_path, source),"w") as dst_file: + with open(os.path.join(package_path, source), "w") as dst_file: for line in src_file: - dst_file.write(pattern.sub('import "' + package_name + '/osi_',line)) + dst_file.write( + pattern.sub('import "' + package_name + "/osi_", line) + ) for source in self.osi_files: - sys.stdout.write('Protobuf-compiling ' + source + '\n') + sys.stdout.write("Protobuf-compiling " + source + "\n") source_path = os.path.join(package_name, source) - subprocess.check_call([self.find_protoc(), - '--python_out=.', - source_path]) + subprocess.check_call([self.find_protoc(), "--python_out=.", source_path]) + + def maybe_generate(self): + if os.path.exists("osi_version.proto.in"): + self.generate() + +class CustomBuildPyCommand(build_py): + def run(self): + ProtobufGenerator().maybe_generate() build_py.run(self) + +class CustomSDistCommand(sdist): + def run(self): + ProtobufGenerator().generate() + sdist.run(self) + + try: os.mkdir(package_path) except Exception: pass try: - open(os.path.join(package_path, '__init__.py'), 'a').close() + with open(os.path.join(package_path, "__init__.py"), "wt") as init_file: + init_file.write( + f"__version__ = '{VERSION_MAJOR}.{VERSION_MINOR}.{VERSION_PATCH}{VERSION_SUFFIX or ''}'\n" + ) except Exception: pass setup( - name='open-simulation-interface', - version=str(VERSION_MAJOR)+'.'+str(VERSION_MINOR)+'.'+str(VERSION_PATCH), - description='A generic interface for the environmental perception of' - 'automated driving functions in virtual scenarios.', - author='Carlo van Driesten, Timo Hanke, Nils Hirsenkorn,' - 'Pilar Garcia-Ramos, Mark Schiementz, Sebastian Schneider', - author_email='Carlo.van-Driesten@bmw.de, Timo.Hanke@bmw.de,' - 'Nils.Hirsenkorn@tum.de, Pilar.Garcia-Ramos@bmw.de,' - 'Mark.Schiementz@bmw.de, Sebastian.SB.Schneider@bmw.de', - packages=[package_name], - install_requires=['protobuf'], + version=str(VERSION_MAJOR) + + "." + + str(VERSION_MINOR) + + "." + + str(VERSION_PATCH) + + (VERSION_SUFFIX or ""), + packages=[package_name, "osi3trace"], cmdclass={ - 'build_py': GenerateProtobufCommand, + "sdist": CustomSDistCommand, + "build_py": CustomBuildPyCommand, }, - url='https://github.com/OpenSimulationInterface/open-simulation-interface', - license="MPL 2.0", - classifiers=[ - 'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)', - ], - data_files=[ - ("", - ["LICENSE"])]) +) diff --git a/tests/test_comment_type.py b/tests/test_comment_type.py index 734d49d2b..9c72606b5 100644 --- a/tests/test_comment_type.py +++ b/tests/test_comment_type.py @@ -6,11 +6,12 @@ PROTO_FILES = glob.glob("*.proto") + class TestCommentType(unittest.TestCase): - ''' Test class for mandatory comments. ''' + """Test class for mandatory comments.""" def test_brief_necessity(self): - ''' Test the necessity of "brief" comment. ''' + """Test the necessity of "brief" comment.""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): @@ -20,15 +21,14 @@ def test_brief_necessity(self): saveStatement = "" for i, line in enumerate(fin, start=1): - # Divide statement and comment. Concatenate multi line statements. # Search for comment ("//"). matchComment = re.search("//", line) if matchComment is not None: - statement = line[:matchComment.start()] - comment = line[matchComment.end():] + statement = line[: matchComment.start()] + comment = line[matchComment.end() :] else: statement = line comment = "" @@ -47,30 +47,47 @@ def test_brief_necessity(self): saveStatement = statement statement = "" else: - saveStatement = statement[matchSep.end():] - statement = statement[:matchSep.end()] + saveStatement = statement[matchSep.end() :] + statement = statement[: matchSep.end()] statement = statement.strip() # Test to check if '\\brief' is appended in comment section for short comments. if matchComment is not None: - noComment += 1; + noComment += 1 if comment.find("\\brief") != -1: hasBrief = True elif len(saveStatement) == 0: - if re.search(r"\bmessage\b", statement) is not None or re.search(r"\bextend\b",statement) is not None: - self.assertTrue(hasBrief, file + " in line " + str(i - 1) + ": \\brief section in comment is missing for: '" + statement + "'") + if ( + re.search(r"\bmessage\b", statement) is not None + or re.search(r"\bextend\b", statement) is not None + ): + self.assertTrue( + hasBrief, + file + + " in line " + + str(i - 1) + + ": \\brief section in comment is missing for: '" + + statement + + "'", + ) elif hasBrief: - self.assertFalse(hasBrief, file + " in line " + str(i - 1) + ": \\brief section in comment is not necessary for: '" + statement + "'") + self.assertFalse( + hasBrief, + file + + " in line " + + str(i - 1) + + ": \\brief section in comment is not necessary for: '" + + statement + + "'", + ) noComment = 0 hasBrief = False - - def test_min_two_lines(self): - ''' Test to check if short comment is of minimum two lines. ''' + """Test to check if short comment is of minimum two lines.""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): @@ -87,8 +104,8 @@ def test_min_two_lines(self): matchComment = re.search("//", line) if matchComment is not None: - statement = line[:matchComment.start()] - comment = line[matchComment.end():] + statement = line[: matchComment.start()] + comment = line[matchComment.end() :] else: statement = line comment = "" @@ -107,23 +124,29 @@ def test_min_two_lines(self): saveStatement = statement statement = "" else: - saveStatement = statement[matchSep.end():] - statement = statement[:matchSep.end()] + saveStatement = statement[matchSep.end() :] + statement = statement[: matchSep.end()] # Test to check if '\\brief' is appended in comment section for short comments. if matchComment is not None: - noComment += 1; + noComment += 1 if comment.find("\\brief") != -1: hasBrief = True elif len(saveStatement) == 0: - self.assertNotEqual(noComment, 1, file + " in line " + str(i - 1) + ": short comment - min. 2 lines.") + self.assertNotEqual( + noComment, + 1, + file + + " in line " + + str(i - 1) + + ": short comment - min. 2 lines.", + ) noComment = 0 hasBrief = False - def test_comment_existence(self): - ''' Test to check if every message, extend , statement or enum has a comment. ''' + """Test to check if every message, extend , statement or enum has a comment.""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): @@ -140,8 +163,8 @@ def test_comment_existence(self): matchComment = re.search("//", line) if matchComment is not None: - statement = line[:matchComment.start()] - comment = line[matchComment.end():] + statement = line[: matchComment.start()] + comment = line[matchComment.end() :] else: statement = line comment = "" @@ -160,9 +183,8 @@ def test_comment_existence(self): saveStatement = statement statement = "" else: - saveStatement = statement[matchSep.end():] - statement = statement[:matchSep.end()] - + saveStatement = statement[matchSep.end() :] + statement = statement[: matchSep.end()] if isEnum is False: # Check if not inside an enum. @@ -172,12 +194,15 @@ def test_comment_existence(self): if matchMessage is not None: # a new message or a new nested message noMessage += 1 - endOfLine = statement[matchMessage.end():] + endOfLine = statement[matchMessage.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) if matchName is not None: # Test case 10: Check name - no special char - # start with a capital letter - matchNameConv = re.search(r"\b[A-Z][a-zA-Z0-9]*\b",endOfLine[matchName.start():matchName.end()]) + matchNameConv = re.search( + r"\b[A-Z][a-zA-Z0-9]*\b", + endOfLine[matchName.start() : matchName.end()], + ) elif re.search(r"\bextend\b", statement) is not None: # treat extend as message @@ -187,22 +212,32 @@ def test_comment_existence(self): if noMessage > 0: matchName = re.search(r"\b\w[\S]*\b\s*=", statement) if matchName is not None: - checkName = statement[matchName.start():matchName.end()-1] + checkName = statement[ + matchName.start() : matchName.end() - 1 + ] # Check field message type (remove field name) type = statement.replace(checkName, "") matchName = re.search(r"\b\w[\S\.]*\s*=", type) if matchName is not None: - checkType = " "+type[matchName.start():matchName.end()-1]+" " + checkType = ( + " " + + type[ + matchName.start() : matchName.end() - 1 + ] + + " " + ) # Test case 12: Check nested message type - matchNameConv = re.search(r"[ ][a-zA-Z][a-zA-Z0-9]*([\.][A-Z][a-zA-Z0-9]*)*[ ]",checkType) + matchNameConv = re.search( + r"[ ][a-zA-Z][a-zA-Z0-9]*([\.][A-Z][a-zA-Z0-9]*)*[ ]", + checkType, + ) # Search for a closing brace. matchClosingBrace = re.search("}", statement) if noMessage > 0 and matchClosingBrace is not None: noMessage -= 1 - # Test to check if '\\brief' is appended in comment section for short comments. if matchComment is not None: noComment += 1 @@ -210,16 +245,36 @@ def test_comment_existence(self): hasBrief = True elif len(saveStatement) == 0: - statement = statement.strip() - if re.search(r"\bmessage\b", statement) is not None or re.search(r"\bextend\b",statement) is not None or re.search(r"\benum\b", statement) is not None: - - self.assertNotEqual(noComment, 0, file + " in line " + str(i - 1) + ": comment is missing for: '" + statement + "'") + if ( + re.search(r"\bmessage\b", statement) is not None + or re.search(r"\bextend\b", statement) is not None + or re.search(r"\benum\b", statement) is not None + ): + self.assertNotEqual( + noComment, + 0, + file + + " in line " + + str(i - 1) + + ": comment is missing for: '" + + statement + + "'", + ) if noMessage > 0 or isEnum == True: if statement.find(";") != -1: - self.assertNotEqual(noComment, 0, file + " in line " + str(i) + ": comment is missing for: '" + statement + "'") + self.assertNotEqual( + noComment, + 0, + file + + " in line " + + str(i) + + ": comment is missing for: '" + + statement + + "'", + ) noComment = 0 hasBrief = False @@ -229,12 +284,20 @@ def test_comment_existence(self): if matchEnum is not None: isEnum = True - endOfLine = statement[matchEnum.end():] + endOfLine = statement[matchEnum.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) if matchName is not None: - # Test to ensure no special characters are in ENUM name. - matchNameConv = re.search(r"\b[A-Z][a-zA-Z0-9]*\b", endOfLine[matchName.start():matchName.end()]) - enumName = self.convert(endOfLine[matchName.start():matchName.end()]) + "_" + # Test to ensure no special characters are in ENUM name. + matchNameConv = re.search( + r"\b[A-Z][a-zA-Z0-9]*\b", + endOfLine[matchName.start() : matchName.end()], + ) + enumName = ( + self.convert( + endOfLine[matchName.start() : matchName.end()] + ) + + "_" + ) # Search for a closing brace. matchClosingBrace = re.search("}", statement) @@ -243,5 +306,5 @@ def test_comment_existence(self): enumName = "" def convert(self, name): - s1 = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', name) - return re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', s1).upper() \ No newline at end of file + s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", name) + return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1).upper() diff --git a/tests/test_doxygen_output.py b/tests/test_doxygen_output.py index 902e72ab8..37b80b4f4 100644 --- a/tests/test_doxygen_output.py +++ b/tests/test_doxygen_output.py @@ -4,29 +4,46 @@ DOC_FILES = glob.glob("doc/html/*.htm*") + class TestDoxygenOutput(unittest.TestCase): - """ Test class for the doxygen output. """ + """Test class for the doxygen output.""" def test_hash(self): - ''' Test case is checking if there are illegal hash chars in the documentation. -> doxygen link not found. ''' + """Test case is checking if there are illegal hash chars in the documentation. -> doxygen link not found.""" for file in DOC_FILES: with open(file, "rt") as fin, self.subTest(file=file): for i, line in enumerate(fin, start=1): - self.assertNotRegex(line, r"([\s>]|^)#\w(\S)*", file + " in line " + str(i) + ": not permitted hash found.") - + self.assertNotRegex( + line, + r"([\s>]|^)#\w(\S)*", + file + " in line " + str(i) + ": not permitted hash found.", + ) def test_slash_triplet(self): - ''' Test case is checking if there are slash triplets in the documentation. -> doxygen didn't interpret something properly. ''' + """Test case is checking if there are slash triplets in the documentation. -> doxygen didn't interpret something properly.""" for file in DOC_FILES: with open(file, "rt") as fin, self.subTest(file=file): for i, line in enumerate(fin, start=1): - self.assertNotRegex(line, r"([\s>]|^)///\s*", file + " in line " + str(i) + ": not permitted slash triplet found.") - + self.assertNotRegex( + line, + r"([\s>]|^)///\s*", + file + + " in line " + + str(i) + + ": not permitted slash triplet found.", + ) def test_backslash_triplet(self): - ''' Test case is checking if there are backslash triplets in the documentation. -> doxygen didn't interpret something properly. ''' + """Test case is checking if there are backslash triplets in the documentation. -> doxygen didn't interpret something properly.""" for file in DOC_FILES: with open(file, "rt") as fin, self.subTest(file=file): for i, line in enumerate(fin, start=1): - self.assertNotRegex(line, r"([\s>]|^)\\\\\\\s*", file + " in line " + str(i) + ": not permitted backslash triplet found.") + self.assertNotRegex( + line, + r"([\s>]|^)\\\\\\\s*", + file + + " in line " + + str(i) + + ": not permitted backslash triplet found.", + ) diff --git a/tests/test_invalid_comment.py b/tests/test_invalid_comment.py index 59c0f7163..39636e04b 100644 --- a/tests/test_invalid_comment.py +++ b/tests/test_invalid_comment.py @@ -3,20 +3,33 @@ PROTO_FILES = glob.glob("*.proto") + class TestInvalidCommentType(unittest.TestCase): """Test class for invalid comment types""" def test_triple_slash(self): - ''' Test to check if more than two forward slash('/') are present in comment section of proto file. ''' + """Test to check if more than two forward slash('/') are present in comment section of proto file.""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): for i, line in enumerate(fin, start=1): - self.assertEqual(line.find("///"), -1, file + " in line " + str(i) + ": not permitted use of '///' ") + self.assertEqual( + line.find("///"), + -1, + file + " in line " + str(i) + ": not permitted use of '///' ", + ) def test_comments_invalid_syntax(self): - ''' Test to check if comments are given using invalid syntax '/*' or '*/' ''' + """Test to check if comments are given using invalid syntax '/*' or '*/'""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): for i, line in enumerate(fin, start=1): - self.assertEqual(line.find("/*"), -1, file + " in line " + str(i) + ": not permitted use of '/*' ") - self.assertEqual(line.find("*/"), -1, file + " in line " + str(i) + ": not permitted use of '*/' ") \ No newline at end of file + self.assertEqual( + line.find("/*"), + -1, + file + " in line " + str(i) + ": not permitted use of '/*' ", + ) + self.assertEqual( + line.find("*/"), + -1, + file + " in line " + str(i) + ": not permitted use of '*/' ", + ) diff --git a/tests/test_invalid_enum.py b/tests/test_invalid_enum.py index 0ecee0b40..7e7823f0f 100644 --- a/tests/test_invalid_enum.py +++ b/tests/test_invalid_enum.py @@ -4,11 +4,12 @@ PROTO_FILES = glob.glob("*.proto") + class TestInvalidEnum(unittest.TestCase): - ''' Test class to check invalid enum ''' + """Test class to check invalid enum""" def test_correct_enum_name(self): - ''' Test if enum name is correct. ''' + """Test if enum name is correct.""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): isEnum = False @@ -22,8 +23,11 @@ def test_correct_enum_name(self): matchComment = re.search("//", line) if matchComment is not None: - statement = line[:matchComment.start()] - comment = line[matchComment.end():] + statement = line[: matchComment.start()] + comment = line[matchComment.end() :] + elif re.search("option allow_alias", line): + statement = "" + comment = "" else: statement = line comment = "" @@ -42,8 +46,8 @@ def test_correct_enum_name(self): saveStatement = statement statement = "" else: - saveStatement = statement[matchSep.end():] - statement = statement[:matchSep.end()] + saveStatement = statement[matchSep.end() :] + statement = statement[: matchSep.end()] # This section will check PascalCase for enums and check enum name? @@ -51,26 +55,53 @@ def test_correct_enum_name(self): matchName = re.search(r"\b\w[\S:]+\b", statement) if matchName is not None: - checkName = statement[matchName.start():matchName.end()] + checkName = statement[matchName.start() : matchName.end()] # Test to check correct ENUM name. - self.assertEqual(checkName.find(enumName), 0, file + " in line " + str(i) + ": enum type wrong. '" + checkName + "' should start with '" + enumName + "'") + self.assertEqual( + checkName.find(enumName), + 0, + file + + " in line " + + str(i) + + ": enum type wrong. '" + + checkName + + "' should start with '" + + enumName + + "'", + ) # Test to check ENUM type is in captial letters/upper case. - self.assertEqual(checkName, checkName.upper(), file + " in line " + str(i) + ": enum type wrong. '" + checkName + "' should use upper case") - + self.assertEqual( + checkName, + checkName.upper(), + file + + " in line " + + str(i) + + ": enum type wrong. '" + + checkName + + "' should use upper case", + ) # Search for "enum". matchEnum = re.search(r"\benum\b", statement) if matchEnum is not None: isEnum = True - endOfLine = statement[matchEnum.end():] + endOfLine = statement[matchEnum.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) if matchName is not None: - # Test to ensure no special characters are in ENUM name. - matchNameConv = re.search(r"\b[A-Z][a-zA-Z0-9]*\b", endOfLine[matchName.start():matchName.end()]) - enumName = self.convert(endOfLine[matchName.start():matchName.end()]) + "_" + # Test to ensure no special characters are in ENUM name. + matchNameConv = re.search( + r"\b[A-Z][a-zA-Z0-9]*\b", + endOfLine[matchName.start() : matchName.end()], + ) + enumName = ( + self.convert( + endOfLine[matchName.start() : matchName.end()] + ) + + "_" + ) # Search for a closing brace. matchClosingBrace = re.search("}", statement) @@ -78,9 +109,8 @@ def test_correct_enum_name(self): isEnum = False enumName = "" - def test_invalid_enum(self): - ''' Test invalid enum definition. ''' + """Test invalid enum definition.""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): isEnum = False @@ -94,8 +124,11 @@ def test_invalid_enum(self): matchComment = re.search("//", line) if matchComment is not None: - statement = line[:matchComment.start()] - comment = line[matchComment.end():] + statement = line[: matchComment.start()] + comment = line[matchComment.end() :] + elif re.search("option allow_alias", line): + statement = "" + comment = "" else: statement = line comment = "" @@ -114,8 +147,8 @@ def test_invalid_enum(self): saveStatement = statement statement = "" else: - saveStatement = statement[matchSep.end():] - statement = statement[:matchSep.end()] + saveStatement = statement[matchSep.end() :] + statement = statement[: matchSep.end()] # This section will check PascalCase for enums and check enum name? @@ -123,20 +156,36 @@ def test_invalid_enum(self): matchName = re.search(r"\b\w[\S:]+\b", statement) if matchName is not None: - checkName = statement[matchName.start():matchName.end()] + checkName = statement[matchName.start() : matchName.end()] # Search for "enum". matchEnum = re.search(r"\benum\b", statement) if matchEnum is not None: isEnum = True - endOfLine = statement[matchEnum.end():] + endOfLine = statement[matchEnum.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) if matchName is not None: # Test to ensure no special characters are in ENUM name. - matchNameConv = re.search(r"\b[A-Z][a-zA-Z0-9]*\b", endOfLine[matchName.start():matchName.end()]) - self.assertIsNotNone(matchNameConv, file + " in line " + str(i) + ": enum name wrong. '" + endOfLine[matchName.start():matchName.end()] + "'") - enumName = self.convert(endOfLine[matchName.start():matchName.end()]) + "_" + matchNameConv = re.search( + r"\b[A-Z][a-zA-Z0-9]*\b", + endOfLine[matchName.start() : matchName.end()], + ) + self.assertIsNotNone( + matchNameConv, + file + + " in line " + + str(i) + + ": enum name wrong. '" + + endOfLine[matchName.start() : matchName.end()] + + "'", + ) + enumName = ( + self.convert( + endOfLine[matchName.start() : matchName.end()] + ) + + "_" + ) # Search for a closing brace. matchClosingBrace = re.search("}", statement) @@ -145,5 +194,5 @@ def test_invalid_enum(self): enumName = "" def convert(self, name): - s1 = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', name) - return re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', s1).upper() \ No newline at end of file + s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", name) + return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1).upper() diff --git a/tests/test_invalid_html.py b/tests/test_invalid_html.py index 776b0b5a3..037ccd9bb 100644 --- a/tests/test_invalid_html.py +++ b/tests/test_invalid_html.py @@ -4,11 +4,12 @@ PROTO_FILES = glob.glob("*.proto") + class TestInvalidHtml(unittest.TestCase): - """ Test class for invalid html comment. """ + """Test class for invalid html comment.""" def test_invalid_slash(self): - ''' Test case to check invalid slash in htmlonly sections ''' + """Test case to check invalid slash in htmlonly sections""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): htmlblock = False @@ -18,8 +19,8 @@ def test_invalid_slash(self): # Search for comment ("//"). matchComment = re.search("//", line) if matchComment is not None: - statement = line[:matchComment.start()] - comment = line[matchComment.end():] + statement = line[: matchComment.start()] + comment = line[matchComment.end() :] else: statement = line comment = "" @@ -38,8 +39,8 @@ def test_invalid_slash(self): saveStatement = statement statement = "" else: - saveStatement = statement[matchSep.end():] - statement = statement[:matchSep.end()] + saveStatement = statement[matchSep.end() :] + statement = statement[: matchSep.end()] # Test case is checking comment and html tags if matchComment is not None: @@ -48,9 +49,8 @@ def test_invalid_slash(self): if htmlblock is False: matchHTMLOnly = re.search(r"\\htmlonly", comment) if matchHTMLOnly is not None: - - htmlComment = comment[matchHTMLOnly.end():] - htmlFreeComment = comment[:matchHTMLOnly.start()] + htmlComment = comment[matchHTMLOnly.end() :] + htmlFreeComment = comment[: matchHTMLOnly.start()] htmlblock = True else: htmlComment = comment @@ -59,16 +59,27 @@ def test_invalid_slash(self): if htmlblock is True: matchEndHTMLOnly = re.search(r"\\endhtmlonly", htmlComment) if matchEndHTMLOnly is not None: - htmlFreeComment = htmlFreeComment + htmlComment[matchEndHTMLOnly.end():] - htmlComment = htmlComment[:matchEndHTMLOnly.start()] + htmlFreeComment = ( + htmlFreeComment + + htmlComment[matchEndHTMLOnly.end() :] + ) + htmlComment = htmlComment[: matchEndHTMLOnly.start()] htmlblock = False # Test case to check html tags only in htmlonly sections - self.assertEqual(htmlComment.find("\\"), -1, file + " in line " + str(i) + ": doxygen comment \\.. reference found: '" + htmlComment + "'") - + self.assertEqual( + htmlComment.find("\\"), + -1, + file + + " in line " + + str(i) + + ": doxygen comment \\.. reference found: '" + + htmlComment + + "'", + ) def test_invalid_hash(self): - ''' Test case to check invalid # in htmlonly sections ''' + """Test case to check invalid # in htmlonly sections""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): htmlblock = False @@ -78,8 +89,8 @@ def test_invalid_hash(self): # Search for comment ("//"). matchComment = re.search("//", line) if matchComment is not None: - statement = line[:matchComment.start()] - comment = line[matchComment.end():] + statement = line[: matchComment.start()] + comment = line[matchComment.end() :] else: statement = line comment = "" @@ -98,8 +109,8 @@ def test_invalid_hash(self): saveStatement = statement statement = "" else: - saveStatement = statement[matchSep.end():] - statement = statement[:matchSep.end()] + saveStatement = statement[matchSep.end() :] + statement = statement[: matchSep.end()] # Test case is checking comment and html tags if matchComment is not None: @@ -108,9 +119,8 @@ def test_invalid_hash(self): if htmlblock is False: matchHTMLOnly = re.search(r"\\htmlonly", comment) if matchHTMLOnly is not None: - - htmlComment = comment[matchHTMLOnly.end():] - htmlFreeComment = comment[:matchHTMLOnly.start()] + htmlComment = comment[matchHTMLOnly.end() :] + htmlFreeComment = comment[: matchHTMLOnly.start()] htmlblock = True else: htmlComment = comment @@ -119,15 +129,26 @@ def test_invalid_hash(self): if htmlblock is True: matchEndHTMLOnly = re.search(r"\\endhtmlonly", htmlComment) if matchEndHTMLOnly is not None: - htmlFreeComment = htmlFreeComment + htmlComment[matchEndHTMLOnly.end():] - htmlComment = htmlComment[:matchEndHTMLOnly.start()] + htmlFreeComment = ( + htmlFreeComment + + htmlComment[matchEndHTMLOnly.end() :] + ) + htmlComment = htmlComment[: matchEndHTMLOnly.start()] htmlblock = False - self.assertEqual(htmlComment.find("#"), -1, file + " in line " + str(i) + ": doxygen comment #.. reference found: '" + htmlComment + "'") - + self.assertEqual( + htmlComment.find("#"), + -1, + file + + " in line " + + str(i) + + ": doxygen comment #.. reference found: '" + + htmlComment + + "'", + ) def test_invalid_at(self): - ''' Test case to check invalid @ in comments ''' + """Test case to check invalid @ in comments""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): htmlblock = False @@ -137,8 +158,8 @@ def test_invalid_at(self): # Search for comment ("//"). matchComment = re.search("//", line) if matchComment is not None: - statement = line[:matchComment.start()] - comment = line[matchComment.end():] + statement = line[: matchComment.start()] + comment = line[matchComment.end() :] else: statement = line comment = "" @@ -157,8 +178,8 @@ def test_invalid_at(self): saveStatement = statement statement = "" else: - saveStatement = statement[matchSep.end():] - statement = statement[:matchSep.end()] + saveStatement = statement[matchSep.end() :] + statement = statement[: matchSep.end()] # Test case is checking comment and html tags if matchComment is not None: @@ -167,9 +188,8 @@ def test_invalid_at(self): if htmlblock is False: matchHTMLOnly = re.search(r"\\htmlonly", comment) if matchHTMLOnly is not None: - - htmlComment = comment[matchHTMLOnly.end():] - htmlFreeComment = comment[:matchHTMLOnly.start()] + htmlComment = comment[matchHTMLOnly.end() :] + htmlFreeComment = comment[: matchHTMLOnly.start()] htmlblock = True else: htmlComment = comment @@ -178,14 +198,26 @@ def test_invalid_at(self): if htmlblock is True: matchEndHTMLOnly = re.search(r"\\endhtmlonly", htmlComment) if matchEndHTMLOnly is not None: - htmlFreeComment = htmlFreeComment + htmlComment[matchEndHTMLOnly.end():] - htmlComment = htmlComment[:matchEndHTMLOnly.start()] + htmlFreeComment = ( + htmlFreeComment + + htmlComment[matchEndHTMLOnly.end() :] + ) + htmlComment = htmlComment[: matchEndHTMLOnly.start()] htmlblock = False - self.assertEqual(comment.find("@"), -1, file + " in line " + str(i) + ": @ tag found (please replace with \\): '" + htmlFreeComment + "'") + self.assertEqual( + comment.find("@"), + -1, + file + + " in line " + + str(i) + + ": @ tag found (please replace with \\): '" + + htmlFreeComment + + "'", + ) def test_no_endhtmlonly(self): - ''' Test case to check no \endhtmlonly in comments ''' + """Test case to check no \endhtmlonly in comments""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): htmlblock = False @@ -195,8 +227,8 @@ def test_no_endhtmlonly(self): # Search for comment ("//"). matchComment = re.search("//", line) if matchComment is not None: - statement = line[:matchComment.start()] - comment = line[matchComment.end():] + statement = line[: matchComment.start()] + comment = line[matchComment.end() :] else: statement = line comment = "" @@ -215,8 +247,8 @@ def test_no_endhtmlonly(self): saveStatement = statement statement = "" else: - saveStatement = statement[matchSep.end():] - statement = statement[:matchSep.end()] + saveStatement = statement[matchSep.end() :] + statement = statement[: matchSep.end()] # Test case is checking comment and html tags if matchComment is not None: @@ -225,9 +257,8 @@ def test_no_endhtmlonly(self): if htmlblock is False: matchHTMLOnly = re.search(r"\\htmlonly", comment) if matchHTMLOnly is not None: - - htmlComment = comment[matchHTMLOnly.end():] - htmlFreeComment = comment[:matchHTMLOnly.start()] + htmlComment = comment[matchHTMLOnly.end() :] + htmlFreeComment = comment[: matchHTMLOnly.start()] htmlblock = True else: htmlComment = comment @@ -237,10 +268,19 @@ def test_no_endhtmlonly(self): matchEndHTMLOnly = re.search(r"\\endhtmlonly", htmlComment) if matchEndHTMLOnly is not None: - htmlFreeComment = htmlFreeComment + htmlComment[matchEndHTMLOnly.end():] - htmlComment = htmlComment[:matchEndHTMLOnly.start()] + htmlFreeComment = ( + htmlFreeComment + + htmlComment[matchEndHTMLOnly.end() :] + ) + htmlComment = htmlComment[: matchEndHTMLOnly.start()] htmlblock = False elif htmlblock: - self.assertFalse(htmlblock, file + " in line " + str(i - 1) + ": doxygen comment html section without endhtmlonly") + self.assertFalse( + htmlblock, + file + + " in line " + + str(i - 1) + + ": doxygen comment html section without endhtmlonly", + ) htmlblock = False diff --git a/tests/test_invalid_message.py b/tests/test_invalid_message.py index 8d2f8eaf5..04eafc8a4 100644 --- a/tests/test_invalid_message.py +++ b/tests/test_invalid_message.py @@ -4,11 +4,12 @@ PROTO_FILES = glob.glob("*.proto") + class TestInvalidMessage(unittest.TestCase): - """ Test class for invalid html comment. """ + """Test class for invalid html comment.""" def test_message_name(self): - ''' Test to check if message name have any special character. It should not have any special character. ''' + """Test to check if message name have any special character. It should not have any special character.""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): @@ -26,8 +27,8 @@ def test_message_name(self): matchComment = re.search("//", line) if matchComment is not None: - statement = line[:matchComment.start()] - comment = line[matchComment.end():] + statement = line[: matchComment.start()] + comment = line[matchComment.end() :] else: statement = line comment = "" @@ -46,8 +47,8 @@ def test_message_name(self): saveStatement = statement statement = "" else: - saveStatement = statement[matchSep.end():] - statement = statement[:matchSep.end()] + saveStatement = statement[matchSep.end() :] + statement = statement[: matchSep.end()] if isEnum is False: # Check if not inside an enum. @@ -57,25 +58,44 @@ def test_message_name(self): if matchMessage is not None: # a new message or a new nested message noMessage += 1 - endOfLine = statement[matchMessage.end():] + endOfLine = statement[matchMessage.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) if matchName is not None: # Test to check if message name have any special character. It should not have any special character. # Message should always start with special character. - matchNameConv = re.search(r"\b[A-Z][a-zA-Z0-9]*\b",endOfLine[matchName.start():matchName.end()]) - self.assertIsNotNone(matchNameConv, file + " in line " + str(i - 1) + ": message name wrong. '" + endOfLine[matchName.start():matchName.end()] + "'") + matchNameConv = re.search( + r"\b[A-Z][a-zA-Z0-9]*\b", + endOfLine[matchName.start() : matchName.end()], + ) + self.assertIsNotNone( + matchNameConv, + file + + " in line " + + str(i - 1) + + ": message name wrong. '" + + endOfLine[matchName.start() : matchName.end()] + + "'", + ) # Search for "enum". matchEnum = re.search(r"\benum\b", statement) if matchEnum is not None: isEnum = True - endOfLine = statement[matchEnum.end():] + endOfLine = statement[matchEnum.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) if matchName is not None: # Test to check presence of invalid special characters - matchNameConv = re.search(r"\b[A-Z][a-zA-Z0-9]*\b", endOfLine[matchName.start():matchName.end()]) - enumName = self.convert(endOfLine[matchName.start():matchName.end()]) + "_" + matchNameConv = re.search( + r"\b[A-Z][a-zA-Z0-9]*\b", + endOfLine[matchName.start() : matchName.end()], + ) + enumName = ( + self.convert( + endOfLine[matchName.start() : matchName.end()] + ) + + "_" + ) # Search for a closing brace. matchClosingBrace = re.search("}", statement) @@ -83,9 +103,8 @@ def test_message_name(self): isEnum = False enumName = "" - def test_field_name(self): - ''' Test to check if field names are in lower case. ''' + """Test to check if field names are in lower case.""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): @@ -104,8 +123,8 @@ def test_field_name(self): matchComment = re.search("//", line) if matchComment is not None: - statement = line[:matchComment.start()] - comment = line[matchComment.end():] + statement = line[: matchComment.start()] + comment = line[matchComment.end() :] else: statement = line comment = "" @@ -124,8 +143,8 @@ def test_field_name(self): saveStatement = statement statement = "" else: - saveStatement = statement[matchSep.end():] - statement = statement[:matchSep.end()] + saveStatement = statement[matchSep.end() :] + statement = statement[: matchSep.end()] if isEnum is False: # Check if not inside an enum. @@ -135,10 +154,13 @@ def test_field_name(self): if matchMessage is not None: # a new message or a new nested message noMessage += 1 - endOfLine = statement[matchMessage.end():] + endOfLine = statement[matchMessage.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) if matchName is not None: - matchNameConv = re.search(r"\b[A-Z][a-zA-Z0-9]*\b",endOfLine[matchName.start():matchName.end()]) + matchNameConv = re.search( + r"\b[A-Z][a-zA-Z0-9]*\b", + endOfLine[matchName.start() : matchName.end()], + ) elif re.search(r"\bextend\b", statement) is not None: # treat extend as message @@ -149,15 +171,35 @@ def test_field_name(self): matchName = re.search(r"\b\w[\S]*\b\s*=", statement) if matchName is not None: - checkName = statement[matchName.start():matchName.end() - 1] - self.assertEqual(checkName, checkName.lower(), file + " in line " + str(i) + ": field name wrong. '" + checkName + "' should use lower case") + checkName = statement[ + matchName.start() : matchName.end() - 1 + ] + self.assertEqual( + checkName, + checkName.lower(), + file + + " in line " + + str(i) + + ": field name wrong. '" + + checkName + + "' should use lower case", + ) type = statement.replace(checkName, "") matchName = re.search(r"\b\w[\S\.]*\s*=", type) if matchName is not None: - checkType = " " + type[matchName.start():matchName.end() - 1] + " " + checkType = ( + " " + + type[ + matchName.start() : matchName.end() - 1 + ] + + " " + ) # Test to check nested message type - matchNameConv = re.search(r"[ ][a-zA-Z][a-zA-Z0-9]*([\.][A-Z][a-zA-Z0-9]*)*[ ]",checkType) + matchNameConv = re.search( + r"[ ][a-zA-Z][a-zA-Z0-9]*([\.][A-Z][a-zA-Z0-9]*)*[ ]", + checkType, + ) # Search for a closing brace. matchClosingBrace = re.search("}", statement) @@ -169,12 +211,20 @@ def test_field_name(self): if matchEnum is not None: isEnum = True - endOfLine = statement[matchEnum.end():] + endOfLine = statement[matchEnum.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) if matchName is not None: # Test to check presence of invalid special characters - matchNameConv = re.search(r"\b[A-Z][a-zA-Z0-9]*\b", endOfLine[matchName.start():matchName.end()]) - enumName = self.convert(endOfLine[matchName.start():matchName.end()]) + "_" + matchNameConv = re.search( + r"\b[A-Z][a-zA-Z0-9]*\b", + endOfLine[matchName.start() : matchName.end()], + ) + enumName = ( + self.convert( + endOfLine[matchName.start() : matchName.end()] + ) + + "_" + ) # Search for a closing brace. matchClosingBrace = re.search("}", statement) @@ -183,7 +233,7 @@ def test_field_name(self): enumName = "" def test_field_type(self): - ''' Test to check nested message type. ''' + """Test to check nested message type.""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): @@ -202,8 +252,8 @@ def test_field_type(self): matchComment = re.search("//", line) if matchComment is not None: - statement = line[:matchComment.start()] - comment = line[matchComment.end():] + statement = line[: matchComment.start()] + comment = line[matchComment.end() :] else: statement = line comment = "" @@ -222,8 +272,8 @@ def test_field_type(self): saveStatement = statement statement = "" else: - saveStatement = statement[matchSep.end():] - statement = statement[:matchSep.end()] + saveStatement = statement[matchSep.end() :] + statement = statement[: matchSep.end()] if isEnum is False: # Check if not inside an enum. @@ -233,10 +283,13 @@ def test_field_type(self): if matchMessage is not None: # a new message or a new nested message noMessage += 1 - endOfLine = statement[matchMessage.end():] + endOfLine = statement[matchMessage.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) if matchName is not None: - matchNameConv = re.search(r"\b[A-Z][a-zA-Z0-9]*\b",endOfLine[matchName.start():matchName.end()]) + matchNameConv = re.search( + r"\b[A-Z][a-zA-Z0-9]*\b", + endOfLine[matchName.start() : matchName.end()], + ) elif re.search(r"\bextend\b", statement) is not None: # treat extend as message @@ -246,18 +299,37 @@ def test_field_type(self): if noMessage > 0: matchName = re.search(r"\b\w[\S]*\b\s*=", statement) if matchName is not None: - checkName = statement[matchName.start():matchName.end() - 1] + checkName = statement[ + matchName.start() : matchName.end() - 1 + ] # Check field message type (remove field name) type = statement.replace(checkName, "") matchName = re.search(r"\b\w[\S\.]*\s*=", type) if matchName is not None: - checkType = " " + type[matchName.start():matchName.end() - 1] + " " + checkType = ( + " " + + type[ + matchName.start() : matchName.end() - 1 + ] + + " " + ) # Test to check nested message type - matchNameConv = re.search(r"[ ][a-zA-Z][a-zA-Z0-9]*([\.][A-Z][a-zA-Z0-9]*)*[ ]", checkType) + matchNameConv = re.search( + r"[ ][a-zA-Z][a-zA-Z0-9]*([\.][A-Z][a-zA-Z0-9]*)*[ ]", + checkType, + ) checkType = checkType.strip() - self.assertIsNotNone(matchNameConv, file + " in line " + str(i) + ": field message type wrong. Check: '" + checkType + "'") + self.assertIsNotNone( + matchNameConv, + file + + " in line " + + str(i) + + ": field message type wrong. Check: '" + + checkType + + "'", + ) # Search for a closing brace. matchClosingBrace = re.search("}", statement) @@ -269,12 +341,20 @@ def test_field_type(self): if matchEnum is not None: isEnum = True - endOfLine = statement[matchEnum.end():] + endOfLine = statement[matchEnum.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) if matchName is not None: # Test to check presence of invalid special characters - matchNameConv = re.search(r"\b[A-Z][a-zA-Z0-9]*\b", endOfLine[matchName.start():matchName.end()]) - enumName = self.convert(endOfLine[matchName.start():matchName.end()]) + "_" + matchNameConv = re.search( + r"\b[A-Z][a-zA-Z0-9]*\b", + endOfLine[matchName.start() : matchName.end()], + ) + enumName = ( + self.convert( + endOfLine[matchName.start() : matchName.end()] + ) + + "_" + ) # Search for a closing brace. matchClosingBrace = re.search("}", statement) @@ -283,7 +363,7 @@ def test_field_type(self): enumName = "" def test_field_multiplicity(self): - ''' Test to check if every field has the multiplicity "repeated" or "optional". ''' + """Test to check if every field has the multiplicity "repeated" or "optional".""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): @@ -294,7 +374,6 @@ def test_field_multiplicity(self): saveStatement = "" for line in fin: - # Skipping test on multiplicity for protobuf 3.0.0 if '"proto3"' in line: break @@ -307,8 +386,8 @@ def test_field_multiplicity(self): matchComment = re.search("//", line) if matchComment is not None: - statement = line[:matchComment.start()] - comment = line[matchComment.end():] + statement = line[: matchComment.start()] + comment = line[matchComment.end() :] else: statement = line comment = "" @@ -327,8 +406,8 @@ def test_field_multiplicity(self): saveStatement = statement statement = "" else: - saveStatement = statement[matchSep.end():] - statement = statement[:matchSep.end()] + saveStatement = statement[matchSep.end() :] + statement = statement[: matchSep.end()] if isEnum is False: # Check if not inside an enum. @@ -338,10 +417,13 @@ def test_field_multiplicity(self): if matchMessage is not None: # a new message or a new nested message noMessage += 1 - endOfLine = statement[matchMessage.end():] + endOfLine = statement[matchMessage.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) if matchName is not None: - matchNameConv = re.search(r"\b[A-Z][a-zA-Z0-9]*\b",endOfLine[matchName.start():matchName.end()]) + matchNameConv = re.search( + r"\b[A-Z][a-zA-Z0-9]*\b", + endOfLine[matchName.start() : matchName.end()], + ) elif re.search(r"\bextend\b", statement) is not None: # treat extend as message @@ -352,18 +434,38 @@ def test_field_multiplicity(self): if noMessage > 0: matchName = re.search(r"\b\w[\S]*\b\s*=", statement) if matchName is not None: - checkName = statement[matchName.start():matchName.end() - 1] + checkName = statement[ + matchName.start() : matchName.end() - 1 + ] # Check field message type (remove field name) type = statement.replace(checkName, "") matchName = re.search(r"\b\w[\S\.]*\s*=", type) if matchName is not None: - checkType = " " + type[matchName.start():matchName.end() - 1] + " " + checkType = ( + " " + + type[ + matchName.start() : matchName.end() - 1 + ] + + " " + ) # Test to check nested message type - matchNameConv = re.search(r"[ ][a-zA-Z][a-zA-Z0-9]*([\.][A-Z][a-zA-Z0-9]*)*[ ]",checkType) + matchNameConv = re.search( + r"[ ][a-zA-Z][a-zA-Z0-9]*([\.][A-Z][a-zA-Z0-9]*)*[ ]", + checkType, + ) statement = statement.strip() - self.assertIsNotNone(re.search(r"\boptional\b", type) is None and re.search(r"\brepeated\b",type), file + " in line " + str(i) + ": field multiplicity (\"optional\" or \"repeated\") is missing. Check: '" + statement + "'") + self.assertIsNotNone( + re.search(r"\boptional\b", type) is None + and re.search(r"\brepeated\b", type), + file + + " in line " + + str(i) + + ': field multiplicity ("optional" or "repeated") is missing. Check: \'' + + statement + + "'", + ) # Search for a closing brace. matchClosingBrace = re.search("}", statement) @@ -375,12 +477,20 @@ def test_field_multiplicity(self): if matchEnum is not None: isEnum = True - endOfLine = statement[matchEnum.end():] + endOfLine = statement[matchEnum.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) if matchName is not None: # Test to check presence of invalid special characters - matchNameConv = re.search(r"\b[A-Z][a-zA-Z0-9]*\b", endOfLine[matchName.start():matchName.end()]) - enumName = self.convert(endOfLine[matchName.start():matchName.end()]) + "_" + matchNameConv = re.search( + r"\b[A-Z][a-zA-Z0-9]*\b", + endOfLine[matchName.start() : matchName.end()], + ) + enumName = ( + self.convert( + endOfLine[matchName.start() : matchName.end()] + ) + + "_" + ) # Search for a closing brace. matchClosingBrace = re.search("}", statement) @@ -389,6 +499,5 @@ def test_field_multiplicity(self): enumName = "" def convert(self, name): - s1 = re.sub(r'(.)([A-Z][a-z]+)', r'\1_\2', name) - return re.sub(r'([a-z0-9])([A-Z])', r'\1_\2', s1).upper() - + s1 = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", name) + return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s1).upper() diff --git a/tests/test_invalid_punctuation.py b/tests/test_invalid_punctuation.py index 224411322..e658d624d 100644 --- a/tests/test_invalid_punctuation.py +++ b/tests/test_invalid_punctuation.py @@ -3,12 +3,17 @@ PROTO_FILES = glob.glob("*.proto") + class TestInvalidPunctuation(unittest.TestCase): - ''' Test class to check invalid punctuation character '__' ''' + """Test class to check invalid punctuation character '__'""" def test_invalid_punctuation(self): - ''' Test to check invalid punctuation character '__' ''' + """Test to check invalid punctuation character '__'""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): for i, line in enumerate(fin, start=1): - self.assertEqual(line.find("__"), -1, file + " in line " + str(i) + ": not permitted use of '__' ") + self.assertEqual( + line.find("__"), + -1, + file + " in line " + str(i) + ": not permitted use of '__' ", + ) diff --git a/tests/test_invalid_tabs.py b/tests/test_invalid_tabs.py index 4b55f58f8..f6c61853f 100644 --- a/tests/test_invalid_tabs.py +++ b/tests/test_invalid_tabs.py @@ -3,12 +3,17 @@ PROTO_FILES = glob.glob("*.proto") + class TestInvalidTabs(unittest.TestCase): """Test class for invalid tabulators""" def test_invalid_tabs(self): - ''' Test to check if invalid tabs exist. ''' + """Test to check if invalid tabs exist.""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): for i, line in enumerate(fin, start=1): - self.assertEqual(line.find("\t"), -1, file + " in line " + str(i) + ": not permitted tab found") + self.assertEqual( + line.find("\t"), + -1, + file + " in line " + str(i) + ": not permitted tab found", + ) diff --git a/tests/test_newline.py b/tests/test_newline.py index 1260659f7..fbc0a30e1 100644 --- a/tests/test_newline.py +++ b/tests/test_newline.py @@ -3,12 +3,17 @@ PROTO_FILES = glob.glob("*.proto") + class TestNewLine(unittest.TestCase): - ''' Test class for mandatory new line. ''' + """Test class for mandatory new line.""" def test_newline(self): - ''' Test to check last line of file must end with a new line. ''' + """Test to check last line of file must end with a new line.""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): lastCharacter = fin.read()[-1] - self.assertEqual(lastCharacter, "\n", file + " has no new line at the end of the file.") + self.assertEqual( + lastCharacter, + "\n", + file + " has no new line at the end of the file.", + ) diff --git a/tests/test_non_ascii.py b/tests/test_non_ascii.py index ccfc76acb..db6656fd0 100644 --- a/tests/test_non_ascii.py +++ b/tests/test_non_ascii.py @@ -5,15 +5,34 @@ PROTO_FILES = glob.glob("*.proto") + class TestNonAscii(unittest.TestCase): """Class is checking if there is an "Umlaut" or any non ASCII characters are present.""" def test_non_ascii(self): - ''' Test if there are any non ASCII characters present like an "Umlaut". ''' + """Test if there are any non ASCII characters present like an "Umlaut".""" for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): for i, line in enumerate(fin, start=1): - if (sys.version_info >= (3, 0)): - self.assertEqual(line, unicodedata.normalize('NFKD', line).encode('ASCII', 'ignore').decode(), file + " in line " + str(i) + ": a none ASCII char is present") + if sys.version_info >= (3, 0): + self.assertEqual( + line, + unicodedata.normalize("NFKD", line) + .encode("ASCII", "ignore") + .decode(), + file + + " in line " + + str(i) + + ": a none ASCII char is present", + ) else: - self.assertEqual(line, unicodedata.normalize('NFKD', unicode(line, 'ISO-8859-1')).encode('ASCII', 'ignore'), file + " in line " + str(i) + ": a none ASCII char is present") \ No newline at end of file + self.assertEqual( + line, + unicodedata.normalize( + "NFKD", unicode(line, "ISO-8859-1") + ).encode("ASCII", "ignore"), + file + + " in line " + + str(i) + + ": a none ASCII char is present", + ) diff --git a/tests/test_osi_trace.py b/tests/test_osi_trace.py index c832b63d7..9d74f5a9d 100644 --- a/tests/test_osi_trace.py +++ b/tests/test_osi_trace.py @@ -2,30 +2,220 @@ import tempfile import unittest -from format.OSITrace import OSITrace +from osi3trace.osi_trace import OSITrace from osi3.osi_sensorview_pb2 import SensorView +from osi3.osi_sensorviewconfiguration_pb2 import SensorViewConfiguration +from osi3.osi_groundtruth_pb2 import GroundTruth +from osi3.osi_hostvehicledata_pb2 import HostVehicleData +from osi3.osi_sensordata_pb2 import SensorData +from osi3.osi_trafficcommand_pb2 import TrafficCommand +from osi3.osi_trafficcommandupdate_pb2 import TrafficCommandUpdate +from osi3.osi_trafficupdate_pb2 import TrafficUpdate +from osi3.osi_motionrequest_pb2 import MotionRequest +from osi3.osi_streamingupdate_pb2 import StreamingUpdate + import struct class TestOSITrace(unittest.TestCase): - def test_osi_trace(self): + def test_osi_trace_sv(self): + with tempfile.TemporaryDirectory() as tmpdirname: + path_output = os.path.join(tmpdirname, "output_sv.txth") + path_input = os.path.join(tmpdirname, "input_sv.osi") + create_sample_sv(path_input) + + trace = OSITrace(path_input) + with open(path_output, "wt") as f: + for message in trace: + self.assertIsInstance(message, SensorView) + f.write(str(message)) + + self.assertEqual(len(trace.retrieve_offsets()), 10) + trace.close() + + self.assertTrue(os.path.exists(path_output)) + + def test_osi_trace_svc(self): + with tempfile.TemporaryDirectory() as tmpdirname: + path_output = os.path.join(tmpdirname, "output_svc.txth") + path_input = os.path.join(tmpdirname, "input_svc.osi") + create_sample_svc(path_input) + + trace = OSITrace(path_input, "SensorViewConfiguration") + with open(path_output, "wt") as f: + for message in trace: + self.assertIsInstance(message, SensorViewConfiguration) + f.write(str(message)) + + self.assertEqual(len(trace.retrieve_offsets()), 1) + trace.close() + + self.assertTrue(os.path.exists(path_output)) + + def test_osi_trace_gt(self): + with tempfile.TemporaryDirectory() as tmpdirname: + path_output = os.path.join(tmpdirname, "output_gt.txth") + path_input = os.path.join(tmpdirname, "input_gt.osi") + create_sample_gt(path_input) + + trace = OSITrace(path_input, "GroundTruth") + with open(path_output, "wt") as f: + for message in trace: + self.assertIsInstance(message, GroundTruth) + f.write(str(message)) + + self.assertEqual(len(trace.retrieve_offsets()), 10) + trace.close() + + self.assertTrue(os.path.exists(path_output)) + + def test_osi_trace_hvd(self): + with tempfile.TemporaryDirectory() as tmpdirname: + path_output = os.path.join(tmpdirname, "output_hvd.txth") + path_input = os.path.join(tmpdirname, "input_hvd.osi") + create_sample_hvd(path_input) + + trace = OSITrace(path_input, "HostVehicleData") + with open(path_output, "wt") as f: + for message in trace: + self.assertIsInstance(message, HostVehicleData) + f.write(str(message)) + + self.assertEqual(len(trace.retrieve_offsets()), 10) + trace.close() + + self.assertTrue(os.path.exists(path_output)) + + def test_osi_trace_sd(self): + with tempfile.TemporaryDirectory() as tmpdirname: + path_output = os.path.join(tmpdirname, "output_sd.txth") + path_input = os.path.join(tmpdirname, "input_sd.osi") + create_sample_sd(path_input) + + trace = OSITrace(path_input, "SensorData") + with open(path_output, "wt") as f: + for message in trace: + self.assertIsInstance(message, SensorData) + f.write(str(message)) + + self.assertEqual(len(trace.retrieve_offsets()), 10) + trace.close() + + self.assertTrue(os.path.exists(path_output)) + + def test_osi_trace_tc(self): + with tempfile.TemporaryDirectory() as tmpdirname: + path_output = os.path.join(tmpdirname, "output_tc.txth") + path_input = os.path.join(tmpdirname, "input_tc.osi") + create_sample_tc(path_input) + + trace = OSITrace(path_input, "TrafficCommand") + with open(path_output, "wt") as f: + for message in trace: + self.assertIsInstance(message, TrafficCommand) + f.write(str(message)) + + self.assertEqual(len(trace.retrieve_offsets()), 10) + trace.close() + + self.assertTrue(os.path.exists(path_output)) + + def test_osi_trace_tcu(self): with tempfile.TemporaryDirectory() as tmpdirname: - path_output = os.path.join(tmpdirname, "output.txth") - path_input = os.path.join(tmpdirname, "input.osi") - create_sample(path_input) + path_output = os.path.join(tmpdirname, "output_tcu.txth") + path_input = os.path.join(tmpdirname, "input_tcu.osi") + create_sample_tcu(path_input) + + trace = OSITrace(path_input, "TrafficCommandUpdate") + with open(path_output, "wt") as f: + for message in trace: + self.assertIsInstance(message, TrafficCommandUpdate) + f.write(str(message)) - trace = OSITrace() - trace.from_file(path=path_input) - trace.make_readable(path_output, index=1) - trace.scenario_file.close() + self.assertEqual(len(trace.retrieve_offsets()), 10) + trace.close() self.assertTrue(os.path.exists(path_output)) + def test_osi_trace_tu(self): + with tempfile.TemporaryDirectory() as tmpdirname: + path_output = os.path.join(tmpdirname, "output_tu.txth") + path_input = os.path.join(tmpdirname, "input_tu.osi") + create_sample_tu(path_input) + + trace = OSITrace(path_input, "TrafficUpdate") + with open(path_output, "wt") as f: + for message in trace: + self.assertIsInstance(message, TrafficUpdate) + f.write(str(message)) -def create_sample(path): + self.assertEqual(len(trace.retrieve_offsets()), 10) + trace.close() + + self.assertTrue(os.path.exists(path_output)) + + def test_osi_trace_mr(self): + with tempfile.TemporaryDirectory() as tmpdirname: + path_output = os.path.join(tmpdirname, "output_mr.txth") + path_input = os.path.join(tmpdirname, "input_mr.osi") + create_sample_mr(path_input) + + trace = OSITrace(path_input, "MotionRequest") + with open(path_output, "wt") as f: + for message in trace: + self.assertIsInstance(message, MotionRequest) + f.write(str(message)) + + self.assertEqual(len(trace.retrieve_offsets()), 10) + trace.close() + + self.assertTrue(os.path.exists(path_output)) + + def test_osi_trace_su(self): + with tempfile.TemporaryDirectory() as tmpdirname: + path_output = os.path.join(tmpdirname, "output_su.txth") + path_input = os.path.join(tmpdirname, "input_su.osi") + create_sample_su(path_input) + + trace = OSITrace(path_input, "StreamingUpdate") + with open(path_output, "wt") as f: + for message in trace: + self.assertIsInstance(message, StreamingUpdate) + f.write(str(message)) + + self.assertEqual(len(trace.retrieve_offsets()), 10) + trace.close() + + self.assertTrue(os.path.exists(path_output)) + + def test_osi_trace_offsets_robustness(self): + with tempfile.TemporaryDirectory() as tmpdirname: + path_input = os.path.join(tmpdirname, "input_robust.osi") + create_sample_sv(path_input) + + trace = OSITrace(path_input) + # Test whether the function can handle be run multiple times safely + offsets = trace.retrieve_offsets(None) + offsets2 = trace.retrieve_offsets(None) + trace.close() + + self.assertEqual(len(offsets), 10) + self.assertEqual(offsets, offsets2) + + +def create_sample_sv(path): f = open(path, "ab") sensorview = SensorView() + sensorview.version.version_major = 3 + sensorview.version.version_minor = 0 + sensorview.version.version_patch = 0 + + sensorview.timestamp.seconds = 0 + sensorview.timestamp.nanos = 0 + + sensorview.sensor_id.value = 42 + sv_ground_truth = sensorview.global_ground_truth sv_ground_truth.version.version_major = 3 sv_ground_truth.version.version_minor = 0 @@ -40,6 +230,9 @@ def create_sample(path): # Generate 10 OSI messages for 9 seconds for i in range(10): # Increment the time + sensorview.timestamp.seconds += 1 + sensorview.timestamp.nanos += 100000 + sv_ground_truth.timestamp.seconds += 1 sv_ground_truth.timestamp.nanos += 100000 @@ -62,3 +255,335 @@ def create_sample(path): f.write(struct.pack(" 0 or lineruleCount > 0, file + f" in line {str(line_number-1)}: message should not have rules for '{statement.strip()}'") - endOfLine = statement[matchMessage.end():] + self.assertFalse( + foundruleCount > 0 or lineruleCount > 0, + file + + f" in line {str(line_number-1)}: message should not have rules for '{statement.strip()}'", + ) + endOfLine = statement[matchMessage.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) - + elif re.search(r"\bextend\b", statement) is not None: # treat extend as message numMessage += 1 @@ -70,7 +75,9 @@ def test_rules_compliance(self): if numMessage > 0: matchName = re.search(r"\b\w[\S]*\b\s*=", statement) if matchName is not None: - checkName = statement[matchName.start():matchName.end()-1] + checkName = statement[ + matchName.start() : matchName.end() - 1 + ] # Check field message type (remove field name) type = statement.replace(checkName, "") matchName = re.search(r"\b\w[\S\.]*\s*=", type) @@ -78,17 +85,25 @@ def test_rules_compliance(self): if isEnum: matchName = re.search(r"\b\w[\S:]+\b", statement) if matchName is not None: - checkName = statement[matchName.start():matchName.end()] - self.assertFalse(foundruleCount > 0 or lineruleCount > 0, file + f" in line {str(line_number-1)}: enum field should not have rules for '{statement.strip()}'") + checkName = statement[matchName.start() : matchName.end()] + self.assertFalse( + foundruleCount > 0 or lineruleCount > 0, + file + + f" in line {str(line_number-1)}: enum field should not have rules for '{statement.strip()}'", + ) # Search for "enum". matchEnum = re.search(r"\benum\b", statement) if matchEnum is not None: isEnum = True - endOfLine = statement[matchEnum.end():] + endOfLine = statement[matchEnum.end() :] matchName = re.search(r"\b\w[\S]*\b", endOfLine) if matchName is not None: - self.assertFalse(foundruleCount > 0 or lineruleCount > 0, file + f" in line {str(line_number-1)}: enum should not have rules for '{statement.strip()}'") + self.assertFalse( + foundruleCount > 0 or lineruleCount > 0, + file + + f" in line {str(line_number-1)}: enum should not have rules for '{statement.strip()}'", + ) # Search for a closing brace. matchClosingBrace = re.search("}", statement) @@ -106,7 +121,7 @@ def test_rules_compliance(self): lineruleCount = 0 foundruleCount = 0 - if not endRule and comment != '': + if not endRule and comment != "": for rulename, ruleregex in RULES_DICT.items(): if re.search(ruleregex, comment): foundruleCount += 1 @@ -115,10 +130,49 @@ def test_rules_compliance(self): if numMessage > 0: if statement.find(";") != -1: statement = statement.strip() - self.assertFalse(hasRule and lineruleCount != foundruleCount and endRule and lineruleCount-foundruleCount-1>0, file + " in line " + str(line_number) + ": "+str(lineruleCount-foundruleCount-1)+" defined rule(s) does not exists for: '"+statement+"'") - self.assertFalse(hasRule and not endRule, file + " in line " + str(line_number) + ": \\endrules statement does not exists for: '"+statement+"'. Check spacing and rule syntax.") - self.assertFalse(not hasRule and endRule, file + " in line " + str(line_number) + ": \\rules statement does not exists for: '"+statement+"'. Check spacing and rule syntax.") - self.assertFalse(not hasRule and not endRule and lineruleCount < foundruleCount, file + " in line " + str(line_number) + ": rules found but no statements (\\rules and \\endrules) around it for: '"+statement+"'") + self.assertFalse( + hasRule + and lineruleCount != foundruleCount + and endRule + and lineruleCount - foundruleCount - 1 > 0, + file + + " in line " + + str(line_number) + + ": " + + str(lineruleCount - foundruleCount - 1) + + " defined rule(s) does not exists for: '" + + statement + + "'", + ) + self.assertFalse( + hasRule and not endRule, + file + + " in line " + + str(line_number) + + ": \\endrules statement does not exists for: '" + + statement + + "'. Check spacing and rule syntax.", + ) + self.assertFalse( + not hasRule and endRule, + file + + " in line " + + str(line_number) + + ": \\rules statement does not exists for: '" + + statement + + "'. Check spacing and rule syntax.", + ) + self.assertFalse( + not hasRule + and not endRule + and lineruleCount < foundruleCount, + file + + " in line " + + str(line_number) + + ": rules found but no statements (\\rules and \\endrules) around it for: '" + + statement + + "'", + ) lineruleCount = 0 foundruleCount = 0 @@ -128,5 +182,6 @@ def test_rules_compliance(self): if hasRule and not endRule or not hasRule and endRule: lineruleCount += 1 -if __name__ == '__main__': + +if __name__ == "__main__": unittest.main() diff --git a/tests/test_units.py b/tests/test_units.py index 2bcd4ac9d..dea7ce784 100644 --- a/tests/test_units.py +++ b/tests/test_units.py @@ -4,25 +4,54 @@ PROTO_FILES = glob.glob("*.proto") + class TestUnits(unittest.TestCase): - """ Test class for units documentation. """ + """Test class for units documentation.""" def test_no_brackets(self): - ''' Test to check if units have the right syntax. ''' + """Test to check if units have the right syntax.""" + + NOT_VALID_BRACKETS = [r"\(", r"\)", r"\[", r"\]", r"\{", r"\}"] - NOT_VALID_BRACKETS = [r'\(', r'\)', r'\[', r'\]', r'\{', r'\}'] - for file in PROTO_FILES: with open(file, "rt") as fin, self.subTest(file=file): for i, line in enumerate(fin, start=1): - found = re.search('Unit:', line, re.IGNORECASE) + found = re.search("Unit:", line, re.IGNORECASE) if found: comment_list = line.split() - self.assertEqual(comment_list[0], '//', file + " in line " + str(i) + ": Unit must be on a separate line or have a space between //. (Example: '// Unit: m')") - self.assertEqual(comment_list[1], 'Unit:', file + " in line " + str(i) + f": '{comment_list[1]}' do not match 'Unit:'. (Example: '// Unit: m')") - self.assertGreaterEqual(len(comment_list), 3, file + " in line " + str(i) + ": No unit defined. (Example: '// Unit: m')") - + self.assertEqual( + comment_list[0], + "//", + file + + " in line " + + str(i) + + ": Unit must be on a separate line or have a space between //. (Example: '// Unit: m')", + ) + self.assertEqual( + comment_list[1], + "Unit:", + file + + " in line " + + str(i) + + f": '{comment_list[1]}' do not match 'Unit:'. (Example: '// Unit: m')", + ) + self.assertGreaterEqual( + len(comment_list), + 3, + file + + " in line " + + str(i) + + ": No unit defined. (Example: '// Unit: m')", + ) + for unit in comment_list: for brackets in NOT_VALID_BRACKETS: - self.assertNotRegex(unit, brackets, file + " in line " + str(i) + ": Invalid brackets around units.") + self.assertNotRegex( + unit, + brackets, + file + + " in line " + + str(i) + + ": Invalid brackets around units.", + )