├── .bumpversion.cfg ├── .clang-format ├── .codeclimate.yml ├── .coveralls.yml ├── .darglint ├── .github ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── pythonapp.yml │ └── testing.yml ├── .gitignore ├── .pre-commit-config.yaml ├── .pylintrc ├── .readthedocs.yml ├── CMakeLists.txt ├── CODE_OF_CONDUCT.md ├── CONTRIBUTORS ├── LICENSE ├── MANIFEST.in ├── README.md ├── appveyor.yml ├── bandit.yml ├── build_ext.py ├── container └── basic │ ├── .tmux.conf │ ├── .zshrc │ ├── Dockerfile │ ├── build.cmd │ ├── build.sh │ ├── pyxcp_conf.py │ ├── run.cmd │ └── run.sh ├── docs ├── Makefile ├── conf.py ├── configuration.rst ├── howto.rst ├── howto_can_driver.rst ├── howto_cli_tools.rst ├── index.rst ├── installation.md ├── modules.rst ├── pyxcp.asam.rst ├── pyxcp.master.rst ├── pyxcp.rst ├── pyxcp.transport.rst ├── recorder.rst ├── requirements.txt └── tutorial.md ├── ls-contributors.cmd ├── ls-contributors.sh ├── poetry.lock ├── pyproject.toml ├── pyxcp ├── __init__.py ├── aml │ ├── EtasCANMonitoring.a2l │ ├── EtasCANMonitoring.aml │ ├── XCP_Common.aml │ ├── XCPonCAN.aml │ ├── XCPonEth.aml │ ├── XCPonFlx.aml │ ├── XCPonSxI.aml │ ├── XCPonUSB.aml │ ├── ifdata_CAN.a2l │ ├── ifdata_Eth.a2l │ ├── ifdata_Flx.a2l │ ├── ifdata_SxI.a2l │ └── ifdata_USB.a2l ├── asam │ ├── __init__.py │ └── types.py ├── asamkeydll.c ├── asamkeydll.sh ├── checksum.py ├── cmdline.py ├── config │ ├── __init__.py │ └── legacy.py ├── constants.py ├── cpp_ext │ ├── __init__.py │ ├── bin.hpp │ ├── blockmem.hpp │ ├── daqlist.hpp │ ├── event.hpp │ ├── extension_wrapper.cpp │ ├── helper.hpp │ ├── mcobject.hpp │ └── tsqueue.hpp ├── daq_stim │ ├── __init__.py │ ├── optimize │ │ ├── __init__.py │ │ └── binpacking.py │ ├── scheduler.cpp │ ├── scheduler.hpp │ ├── stim.cpp │ ├── stim.hpp │ └── stim_wrapper.cpp ├── dllif.py ├── errormatrix.py ├── examples │ ├── conf_can.toml │ ├── conf_can_user.toml │ ├── conf_can_vector.json │ ├── conf_can_vector.toml │ ├── conf_eth.toml │ ├── conf_nixnet.json │ ├── conf_socket_can.toml │ ├── conf_sxi.json │ ├── conf_sxi.toml │ ├── run_daq.py │ ├── xcp_policy.py │ ├── xcp_read_benchmark.py │ ├── xcp_skel.py │ ├── xcp_unlock.py │ ├── xcp_user_supplied_driver.py │ ├── xcphello.py │ └── xcphello_recorder.py ├── master │ ├── __init__.py │ ├── errorhandler.py │ └── master.py ├── py.typed ├── recorder │ ├── __init__.py │ ├── build_clang.cmd │ ├── build_clang.sh │ ├── build_gcc.cmd │ ├── build_gcc.sh │ ├── build_gcc_arm.sh │ ├── converter │ │ └── __init__.py │ ├── lz4.c │ ├── lz4.h │ ├── lz4hc.c │ ├── lz4hc.h │ ├── mio.hpp │ ├── reader.hpp │ ├── reco.py │ ├── recorder.rst │ ├── rekorder.cpp │ ├── rekorder.hpp │ ├── setup.py │ ├── test_reko.py │ ├── unfolder.hpp │ ├── wrap.cpp │ └── writer.hpp ├── scripts │ ├── __init__.py │ ├── pyxcp_probe_can_drivers.py │ ├── xcp_examples.py │ ├── xcp_fetch_a2l.py │ ├── xcp_id_scanner.py │ ├── xcp_info.py │ ├── xcp_profile.py │ └── xmraw_converter.py ├── stim │ └── __init__.py ├── tests │ ├── test_asam_types.py │ ├── test_binpacking.py │ ├── test_can.py │ ├── test_checksum.py │ ├── test_daq.py │ ├── test_frame_padding.py │ ├── test_master.py │ ├── test_transport.py │ └── test_utils.py ├── timing.py ├── transport │ ├── __init__.py │ ├── base.py │ ├── base_transport.hpp │ ├── can.py │ ├── eth.py │ ├── sxi.py │ ├── transport_wrapper.cpp │ └── usb_transport.py ├── types.py ├── utils.py └── vector │ ├── __init__.py │ └── map.py ├── reformat.cmd └── selective_tests.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 0.22.30 3 | commit = True 4 | tag = False 5 | 6 | [bumpversion:file:pyxcp/__init__.py] 7 | 8 | [bumpversion:file:pyproject.toml] 9 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | Language: Cpp 3 | AccessModifierOffset: -1 4 | AlignAfterOpenBracket: BlockIndent 5 | AlignArrayOfStructures: Left 6 | AlignConsecutiveMacros: true 7 | # Enabled: true 8 | # AcrossEmptyLines: true 9 | # AcrossComments: true 10 | AlignConsecutiveAssignments: true 11 | # Enabled: true 12 | # AcrossEmptyLines: true 13 | # AcrossComments: true 14 | AlignConsecutiveDeclarations: true 15 | # Enabled: false 16 | # AcrossEmptyLines: true 17 | # AcrossComments: true 18 | AlignConsecutiveBitFields: true 19 | # Enabled: false 20 | # AcrossEmptyLines: true 21 | # AcrossComments: true 22 | AlignEscapedNewlines: Right 23 | AlignOperands: Align 24 | AlignTrailingComments: true 25 | # Starting with clang-format 16: 26 | # Kind: Always 27 | # OverEmptyLines: 2 28 | # MaxEmptyLinesToKeep: 2 29 | AllowAllArgumentsOnNextLine: true 30 | AllowAllParametersOfDeclarationOnNextLine: true 31 | AllowShortBlocksOnASingleLine: Never 32 | AllowShortCaseLabelsOnASingleLine: false 33 | AllowShortEnumsOnASingleLine: false 34 | AllowShortFunctionsOnASingleLine: None 35 | AllowShortLambdasOnASingleLine: Inline 36 | AllowShortIfStatementsOnASingleLine: Never 37 | AllowShortLoopsOnASingleLine: false 38 | AlwaysBreakAfterDefinitionReturnType: None 39 | AlwaysBreakAfterReturnType: None 40 | AlwaysBreakBeforeMultilineStrings: false 41 | AlwaysBreakTemplateDeclarations: Yes 42 | # AttributeMacros: ['__capability', '__output', '__ununsed'] # useful for language extensions or static analyzer annotations. 43 | BinPackArguments: true 44 | BinPackParameters: true 45 | BitFieldColonSpacing: After 46 | BreakBeforeBraces: Custom 47 | BraceWrapping: 48 | AfterCaseLabel: false 49 | AfterClass: false 50 | AfterControlStatement: Never 51 | AfterEnum: false 52 | AfterFunction: false 53 | AfterNamespace: false 54 | AfterObjCDeclaration: false 55 | AfterStruct: false 56 | AfterUnion: false 57 | AfterExternBlock: false 58 | BeforeCatch: false 59 | BeforeElse: false 60 | IndentBraces: false 61 | SplitEmptyFunction: true 62 | SplitEmptyRecord: true 63 | SplitEmptyNamespace: true 64 | # BracedInitializerIndentWidth: 4 65 | # BreakAfterAttributes: Never 66 | BreakAfterJavaFieldAnnotations: true 67 | BreakBeforeBinaryOperators: None 68 | # BreakBeforeConceptDeclarations: Allowed 69 | BreakBeforeInheritanceComma: false 70 | BreakInheritanceList: AfterComma 71 | BreakBeforeTernaryOperators: false 72 | BreakConstructorInitializersBeforeComma: false 73 | BreakConstructorInitializers: AfterColon 74 | BreakStringLiterals: true 75 | ColumnLimit: 132 76 | CommentPragmas: "^ IWYU pragma:" 77 | CompactNamespaces: false 78 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 79 | ConstructorInitializerIndentWidth: 4 80 | ContinuationIndentWidth: 4 81 | Cpp11BracedListStyle: false 82 | DeriveLineEnding: true 83 | DerivePointerAlignment: true 84 | DisableFormat: false 85 | EmptyLineAfterAccessModifier: Always 86 | EmptyLineBeforeAccessModifier: LogicalBlock 87 | ExperimentalAutoDetectBinPacking: false 88 | FixNamespaceComments: true 89 | ForEachMacros: 90 | - foreach 91 | - Q_FOREACH 92 | - BOOST_FOREACH 93 | IncludeBlocks: Regroup 94 | IncludeCategories: 95 | - Regex: '^' 96 | Priority: 2 97 | SortPriority: 0 98 | - Regex: '^<.*\.h>' 99 | Priority: 1 100 | SortPriority: 0 101 | - Regex: "^<.*" 102 | Priority: 2 103 | SortPriority: 0 104 | - Regex: ".*" 105 | Priority: 3 106 | SortPriority: 0 107 | IncludeIsMainRegex: "([-_](test|unittest))?$" 108 | IncludeIsMainSourceRegex: "" 109 | IndentCaseLabels: true 110 | IndentCaseBlocks: true 111 | IndentExternBlock: Indent 112 | IndentGotoLabels: true 113 | IndentPPDirectives: BeforeHash 114 | # IndentRequiresClause: true 115 | IndentWidth: 4 116 | IndentWrappedFunctionNames: false 117 | # InsertBraces: true # be careful! 118 | # InsertNewlineAtEOF: true # clang-format 16 119 | JavaScriptQuotes: Leave 120 | JavaScriptWrapImports: true 121 | KeepEmptyLinesAtTheStartOfBlocks: false 122 | MacroBlockBegin: "" 123 | MacroBlockEnd: "" 124 | MaxEmptyLinesToKeep: 1 125 | NamespaceIndentation: Inner 126 | ObjCBinPackProtocolList: Never 127 | ObjCBlockIndentWidth: 4 128 | ObjCSpaceAfterProperty: false 129 | ObjCSpaceBeforeProtocolList: true 130 | PackConstructorInitializers: NextLine 131 | PenaltyBreakAssignment: 2 132 | PenaltyBreakBeforeFirstCallParameter: 1 133 | PenaltyBreakComment: 300 134 | PenaltyBreakFirstLessLess: 120 135 | PenaltyBreakString: 1000 136 | PenaltyBreakTemplateDeclaration: 10 137 | PenaltyExcessCharacter: 1000000 138 | PenaltyReturnTypeOnItsOwnLine: 200 139 | PPIndentWidth: 4 140 | RawStringFormats: 141 | - Language: Cpp 142 | Delimiters: 143 | - cc 144 | - CC 145 | - cpp 146 | - Cpp 147 | - CPP 148 | - "c++" 149 | - "C++" 150 | CanonicalDelimiter: "" 151 | BasedOnStyle: google 152 | - Language: TextProto 153 | Delimiters: 154 | - pb 155 | - PB 156 | - proto 157 | - PROTO 158 | EnclosingFunctions: 159 | - EqualsProto 160 | - EquivToProto 161 | - PARSE_PARTIAL_TEXT_PROTO 162 | - PARSE_TEST_PROTO 163 | - PARSE_TEXT_PROTO 164 | - ParseTextOrDie 165 | - ParseTextProtoOrDie 166 | CanonicalDelimiter: "" 167 | BasedOnStyle: google 168 | ReferenceAlignment: Left 169 | ReflowComments: true 170 | SeparateDefinitionBlocks: Always 171 | SortIncludes: true 172 | SortUsingDeclarations: true 173 | 174 | SpaceAfterCStyleCast: false 175 | SpaceAfterLogicalNot: false 176 | SpaceAfterTemplateKeyword: false 177 | 178 | SpaceAroundPointerQualifiers: Both 179 | 180 | SpaceBeforeAssignmentOperators: true 181 | SpaceBeforeCaseColon: false 182 | SpaceBeforeCpp11BracedList: false 183 | SpaceBeforeCtorInitializerColon: true 184 | SpaceBeforeInheritanceColon: true 185 | SpaceBeforeParens: ControlStatementsExceptControlMacros 186 | # SpaceBeforeParensOptions -- TODO: 187 | SpaceBeforeRangeBasedForLoopColon: true 188 | SpaceBeforeSquareBrackets: false 189 | SpacesBeforeTrailingComments: 2 190 | 191 | SpacesInAngles: Leave 192 | SpaceInEmptyBlock: false 193 | SpaceInEmptyParentheses: false 194 | SpacesInConditionalStatement: false 195 | SpacesInContainerLiterals: false 196 | SpacesInCStyleCastParentheses: false 197 | SpacesInParentheses: false 198 | SpacesInSquareBrackets: false 199 | 200 | Standard: Auto 201 | StatementMacros: 202 | - Q_UNUSED 203 | - QT_REQUIRE_VERSION 204 | TabWidth: 4 205 | UseCRLF: false 206 | UseTab: Never 207 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | engines: 2 | fixme: 3 | enabled: true 4 | pep8: 5 | enabled: false 6 | radon: 7 | enabled: true 8 | ratings: 9 | paths: 10 | - "**.py" 11 | 12 | checks: 13 | argument-count: 14 | config: 15 | threshold: 6 16 | complex-logic: 17 | config: 18 | threshold: 4 19 | file-lines: 20 | config: 21 | threshold: 1250 22 | method-complexity: 23 | config: 24 | threshold: 50 25 | method-count: 26 | config: 27 | threshold: 80 28 | method-lines: 29 | config: 30 | threshold: 35 31 | nested-control-flow: 32 | config: 33 | threshold: 4 34 | return-statements: 35 | config: 36 | threshold: 4 37 | similar-code: 38 | config: 39 | threshold: 125 # language-specific defaults. an override will affect all languages. 40 | identical-code: 41 | config: 42 | threshold: 50 # language-specific defaults. an override will affect all languages. 43 | 44 | exclude_patterns: 45 | - "pyxcp/tests/**" 46 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis 2 | repo_token: oX4DLl9AGFmRCJ9PmpRwHzpyiHaZLRAi1 3 | -------------------------------------------------------------------------------- /.darglint: -------------------------------------------------------------------------------- 1 | [darglint] 2 | strictness = long 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: christoph2 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Types of changes 2 | 3 | 4 | 5 | - [ ] Bug fix (non-breaking change which fixes an issue) 6 | - [ ] New feature (non-breaking change which adds functionality) 7 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 8 | - [ ] I have read the **CONTRIBUTING** document. 9 | - [ ] My code follows the code style of this project. 10 | - [ ] My change requires a change to the documentation. 11 | - [ ] I have updated the documentation accordingly. 12 | - [ ] I have added tests to cover my changes. 13 | - [ ] All new and existing tests passed. 14 | -------------------------------------------------------------------------------- /.github/workflows/pythonapp.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions 3 | 4 | name: pyXCP 5 | 6 | on: 7 | push: 8 | branches: [master, develop] 9 | pull_request: 10 | branches: [master, develop] 11 | 12 | jobs: 13 | build_wheels: 14 | name: Build wheels on ${{ matrix.os }} 15 | continue-on-error: true 16 | runs-on: ${{ matrix.os }} 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | os: [windows-latest, ubuntu-latest] # , ubuntu-latest, macos-13, macos-14 21 | # cibw_archs: ["x86_64", ""] 22 | #include: 23 | # - os: ubuntu-latest 24 | # cibw_archs: "aarch64" 25 | steps: 26 | - uses: actions/checkout@v3 27 | - name: Build wheel 28 | if: ${{ always() }} 29 | uses: pypa/cibuildwheel@v2.20 30 | env: 31 | CIBW_ARCHS_LINUX: x86_64 32 | 33 | - uses: actions/upload-artifact@v4 34 | with: 35 | # path: ./dist/*.whl 36 | # include-hidden-files: true 37 | # retention-days: 1 38 | name: cibw-wheels-${{ matrix.os }}-${{ strategy.job-index }} 39 | path: ./wheelhouse/*.whl 40 | 41 | build_sdist: 42 | name: Build source distribution 43 | runs-on: ubuntu-latest 44 | steps: 45 | - uses: actions/checkout@v3 46 | 47 | - name: Build sdist 48 | run: | 49 | pip install -U build 50 | python -m build --sdist 51 | 52 | - uses: actions/upload-artifact@v4 53 | with: 54 | path: dist/*.tar.gz 55 | include-hidden-files: true 56 | retention-days: 1 57 | 58 | upload_pypi: 59 | needs: [build_wheels, build_sdist] # , build_sdist 60 | runs-on: ubuntu-latest 61 | # if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/') 62 | # alternatively, to publish when a GitHub Release is created, use the following rule: 63 | # if: github.event_name == 'release' && github.event.action == 'published' 64 | steps: 65 | - uses: actions/download-artifact@v4.1.7 66 | with: 67 | # name: artifact 68 | path: dist 69 | merge-multiple: true 70 | # pattern: dist/* 71 | 72 | - uses: pypa/gh-action-pypi-publish@v1.4.2 73 | with: 74 | user: __token__ 75 | password: ${{ secrets.PYPI_PASSWORD }} 76 | -------------------------------------------------------------------------------- /.github/workflows/testing.yml: -------------------------------------------------------------------------------- 1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python 2 | # For more information see: https://help.github.com/actions/language-and-framework-u/using-python-with-github-actions 3 | 4 | name: Run tests. 5 | 6 | on: 7 | push: 8 | branches: [master] 9 | pull_request: 10 | branches: [master] 11 | 12 | jobs: 13 | build: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | os: [ubuntu-latest, windows-latest] # macos-latest 19 | python: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Set up Python ${{ matrix.python }} 23 | uses: actions/setup-python@v2 24 | with: 25 | python-version: ${{ matrix.python }} 26 | - name: Install dependencies 27 | run: | 28 | python -m pip install --upgrade pip setuptools 29 | #pip install -r requirements.txt 30 | #python setup.py install 31 | - name: Test with pytest 32 | run: | 33 | pip install pytest 34 | pytest 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # IPython 78 | profile_default/ 79 | ipython_config.py 80 | 81 | # pyenv 82 | .python-version 83 | 84 | # celery beat schedule file 85 | celerybeat-schedule 86 | 87 | # SageMath parsed files 88 | *.sage.py 89 | 90 | # Environments 91 | .env 92 | .venv 93 | env/ 94 | venv/ 95 | ENV/ 96 | env.bak/ 97 | venv.bak/ 98 | 99 | # Spyder project settings 100 | .spyderproject 101 | .spyproject 102 | 103 | # Rope project settings 104 | .ropeproject 105 | 106 | # mkdocs documentation 107 | /site 108 | 109 | # mypy 110 | .mypy_cache/ 111 | .dmypy.json 112 | dmypy.json 113 | 114 | # Pyre type checker 115 | .pyre/ 116 | 117 | # junitxml 118 | result.xml 119 | /docs/_templates 120 | /docs/_build 121 | /docs/_static 122 | **/*pdf 123 | **/*gz 124 | **/*tar 125 | **/*bz2 126 | 127 | .vscode/ 128 | 129 | # VIM temporaries 130 | *~ 131 | /pyxcp/recorder/mio/ 132 | /pyxcp/recorder/simde/ 133 | /pyxcp/recorder/coro/ 134 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: local 3 | hooks: 4 | - id: bandit 5 | name: bandit 6 | entry: bandit 7 | language: system 8 | types: [python] 9 | require_serial: true 10 | args: ["-c", "bandit.yml"] 11 | - id: black 12 | name: black 13 | entry: black 14 | language: system 15 | types: [python] 16 | require_serial: true 17 | - id: ruff 18 | name: ruff 19 | entry: ruff 20 | language: system 21 | types: [python] 22 | args: ["check"] 23 | require_serial: true 24 | - id: check-added-large-files 25 | name: Check for added large files 26 | entry: check-added-large-files 27 | language: system 28 | - id: check-toml 29 | name: Check Toml 30 | entry: check-toml 31 | language: system 32 | types: [toml] 33 | - id: check-json 34 | name: check-json 35 | entry: check-json 36 | language: python 37 | types: [json] 38 | - id: check-yaml 39 | name: Check Yaml 40 | entry: check-yaml 41 | language: system 42 | types: [yaml] 43 | - id: check-ast 44 | name: check-ast 45 | entry: check-ast 46 | language: python 47 | types: [python] 48 | stages: [pre-commit] 49 | - id: check-builtin-literals 50 | name: check-builtin-literals 51 | entry: check-builtin-literals 52 | language: python 53 | types: [python] 54 | stages: [pre-commit] 55 | - id: check-case-conflict 56 | name: check-case-conflict 57 | entry: check-case-conflict 58 | language: python 59 | types: [python] 60 | stages: [pre-commit] 61 | - id: check-merge-conflict 62 | name: check-merge-conflict 63 | entry: check-merge-conflict 64 | language: python 65 | types: [text] 66 | stages: [pre-commit] 67 | - id: fix-byte-order-marker 68 | name: fix-byte-order-marker 69 | entry: fix-byte-order-marker 70 | language: python 71 | types: [python] 72 | stages: [pre-commit] 73 | - id: mixed-line-ending 74 | name: mixed-line-ending 75 | entry: mixed-line-ending 76 | language: python 77 | types_or: [c, c++, python] 78 | stages: [pre-commit] 79 | - id: end-of-file-fixer 80 | name: end-of-file-fixer 81 | entry: end-of-file-fixer 82 | language: python 83 | types_or: [python] 84 | stages: [pre-commit] 85 | - id: darglint 86 | name: darglint 87 | entry: darglint 88 | language: system 89 | types: [python] 90 | stages: [manual] 91 | - id: end-of-file-fixer 92 | name: Fix End of Files 93 | entry: end-of-file-fixer 94 | language: system 95 | types: [text] 96 | stages: [pre-commit, pre-push, manual] 97 | - id: flake8 98 | name: flake8 99 | entry: flake8 100 | language: system 101 | types: [python] 102 | require_serial: true 103 | args: [--max-line-length=132] 104 | - id: isort 105 | name: isort 106 | entry: isort 107 | require_serial: true 108 | language: system 109 | types_or: [cython, pyi, python] 110 | args: ["--filter-files"] 111 | - id: pyupgrade 112 | name: pyupgrade 113 | description: Automatically upgrade syntax for newer versions. 114 | entry: pyupgrade 115 | language: system 116 | types: [python] 117 | args: [--py38-plus] 118 | - id: trailing-whitespace 119 | name: Trim Trailing Whitespace 120 | entry: trailing-whitespace-fixer 121 | language: system 122 | types: [text] 123 | stages: [pre-commit, pre-push, manual] 124 | #- repo: https://github.com/pre-commit/mirrors-prettier 125 | #rev: v4.0.0-alpha.8 126 | # hooks: 127 | #- id: prettier 128 | #- repo: https://github.com/necaris/pre-commit-pyright 129 | #rev: '1.1.53' 130 | #hooks: 131 | #- id: pyright 132 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | sphinx: 4 | configuration: docs/conf.py 5 | 6 | formats: all 7 | 8 | python: 9 | version: 3.7 10 | install: 11 | - requirements: docs/requirements.txt 12 | - method: pip 13 | path: . 14 | - method: setuptools 15 | path: . 16 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | cmake_minimum_required(VERSION 3.20...3.30) 3 | project(pyxcp_extensions LANGUAGES C CXX) 4 | 5 | 6 | if(POLICY CMP0135) 7 | cmake_policy(SET CMP0135 NEW) 8 | endif() 9 | cmake_policy(SET CMP0094 NEW) 10 | 11 | find_package(Python3 COMPONENTS Interpreter Development.Module REQUIRED) 12 | find_package(pybind11 CONFIG) 13 | 14 | SET(CMAKE_C_STANDARD 17) 15 | set(CMAKE_CXX_STANDARD 23) 16 | 17 | message( STATUS "Found pybind11 v${pybind11_VERSION} ${pybind11_VERSION_TYPE}: ${pybind11_INCLUDE_DIRS}") 18 | 19 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/dist") 20 | 21 | 22 | SET(GCC_N_CLANG_BASE_OPTIONS "-std=c++23 -Wall -Wextra -Wpedantic -Warray-bounds -mtune=native -fexceptions") 23 | 24 | SET(MSVC_BASE_OPTIONS "/W3 /permissive- /EHsc /bigobj /Zc:__cplusplus /std:c++latest") 25 | 26 | 27 | 28 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 29 | if (MSVC) 30 | SET(MSVC_BASE_OPTIONS "${MSVC_BASE_OPTIONS} /Od /fsanitize=address /Zi") 31 | else() 32 | SET(GCC_N_CLANG_BASE_OPTIONS "${GCC_N_CLANG_BASE_OPTIONS} -Og -g3 -ggdb -fno-omit-frame-pointer -fsanitize=address -fsanitize=undefined -fsanitize=bounds") # -fsanitize=hwaddress 33 | endif() 34 | else () 35 | if (MSVC) 36 | SET(MSVC_BASE_OPTIONS "${MSVC_BASE_OPTIONS} /Ox") 37 | else() 38 | SET(GCC_N_CLANG_BASE_OPTIONS "${GCC_N_CLANG_BASE_OPTIONS} -O3 -fomit-frame-pointer") 39 | endif() 40 | endif () 41 | 42 | 43 | if (CMAKE_SYSTEM_NAME STREQUAL "Darwin") 44 | set(ENV{MACOSX_DEPLOYMENT_TARGET} "11.0") 45 | SET(GCC_N_CLANG_EXTRA_OPTIONS "-stdlib=libc++") 46 | message("Platform is Darwin") 47 | elseif (CMAKE_SYSTEM_NAME STREQUAL "Windows") 48 | message("Platform is WINDOWS") 49 | SET(MSVC_EXTRA_OPTIONS "") 50 | elseif (CMAKE_SYSTEM_NAME STREQUAL "Linux") 51 | SET(GCC_N_CLANG_EXTRA_OPTIONS "-fvisibility=hidden -g0") # -fcoroutines 52 | message("Platform is LINUX") 53 | endif() 54 | 55 | 56 | IF (CMAKE_C_COMPILER_ID STREQUAL "GNU") 57 | 58 | ELSEIF (CMAKE_C_COMPILER_ID MATCHES "Clang") 59 | 60 | ELSEIF (CMAKE_C_COMPILER_ID MATCHES "MSVC") 61 | 62 | ELSE () 63 | 64 | ENDIF () 65 | 66 | IF (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 67 | 68 | ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 69 | 70 | ELSEIF (CMAKE_CXX_COMPILER_ID MATCHES "MSVC") 71 | 72 | ELSE () 73 | 74 | 75 | ENDIF () 76 | 77 | message("Compiling C with: " ${CMAKE_C_COMPILER_ID}) 78 | message("Compiling Cpp with: " ${CMAKE_CXX_COMPILER_ID}) 79 | 80 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 81 | 82 | set(EXTENSION_INCS ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/cpp_ext) 83 | 84 | pybind11_add_module(rekorder pyxcp/recorder/wrap.cpp pyxcp/recorder/lz4.c pyxcp/recorder/lz4hc.c) 85 | pybind11_add_module(cpp_ext pyxcp/cpp_ext/extension_wrapper.cpp) 86 | pybind11_add_module(stim pyxcp/daq_stim/stim_wrapper.cpp pyxcp/daq_stim/stim.cpp pyxcp/daq_stim/scheduler.cpp) 87 | 88 | target_include_directories(rekorder PRIVATE ${EXTENSION_INCS} ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/recorder) 89 | target_include_directories(cpp_ext PRIVATE ${EXTENSION_INCS}) 90 | target_include_directories(stim PRIVATE ${EXTENSION_INCS} ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/daq_stim) 91 | 92 | target_compile_options(rekorder PUBLIC "-DEXTENSION_NAME=pyxcp.recorder.rekorder") 93 | target_compile_options(cpp_ext PUBLIC "-DEXTENSION_NAME=pyxcp.cpp_ext.cpp_ext") 94 | target_compile_options(stim PUBLIC "-DEXTENSION_NAME=pyxcp.daq_stim.stim") 95 | 96 | add_executable(asamkeydll ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/asamkeydll.c) 97 | if (CMAKE_SYSTEM_NAME STREQUAL "Linux") 98 | target_link_libraries(asamkeydll PRIVATE dl) 99 | endif() 100 | 101 | 102 | if (CMAKE_SIZEOF_VOID_P EQUAL 8) 103 | # CMAKE_SYSTEM_NAME STREQUAL "Windows" 104 | endif() 105 | 106 | IF (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") 107 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GCC_N_CLANG_BASE_OPTIONS} ${GCC_N_CLANG_EXTRA_OPTIONS}") 108 | target_link_options(cpp_ext PUBLIC -flto=auto) 109 | target_link_options(stim PUBLIC -flto=auto) 110 | target_link_options(rekorder PUBLIC -flto=auto) 111 | ELSEIF (CMAKE_C_COMPILER_ID MATCHES "MSVC") 112 | SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MSVC_BASE_OPTIONS} ${MSVC_EXTRA_OPTIONS}") 113 | ENDIF() 114 | 115 | IF (CMAKE_SYSTEM_NAME STREQUAL "Linux" AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") 116 | # SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" -fuse-ld=lld) 117 | ENDIF() 118 | 119 | # target_include_directories(preprocessor PUBLIC $) 120 | # target_link_libraries(preprocessor pybind11::headers) 121 | # set_target_properties(preprocessor PROPERTIES INTERPROCEDURAL_OPTIMIZATION ON CXX_VISIBILITY_PRESET ON VISIBILITY_INLINES_HIDDEN ON) 122 | 123 | install(TARGETS rekorder LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/recorder) 124 | install(TARGETS cpp_ext LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/cpp_ext) 125 | install(TARGETS stim LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/daq_stim) 126 | # install(TARGETS asamkeydll LIBRARY DESTINATION ${CMAKE_CURRENT_SOURCE_DIR}/pyxcp/) 127 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at cpu12.gems@googlemail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTORS: -------------------------------------------------------------------------------- 1 | chrisoro <4160557+chrisoro@users.noreply.github.com> 2 | Christoph Schueler 3 | Damien KAYSER 4 | Daniel Hrisca 5 | danielhrisca 6 | Devendra Giramkar 7 | dkuschmierz <32884309+dkuschmierz@users.noreply.github.com> 8 | Felix Nieuwenhuizen 9 | Jacob Schaer 10 | Paul Gee 11 | rui 12 | Sedov Aleksandr 13 | Steffen Sanwald 14 | The Codacy Badger 15 | toebsen 16 | torgrimb 17 | waszil 18 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | recursive-include pyxcp *.c 3 | recursive-include pyxcp *.a2l 4 | recursive-include pyxcp *.aml 5 | 6 | include *.cmd 7 | include *.md 8 | include *.sh 9 | include *.toml 10 | include *.txt 11 | include *.yml 12 | include pyxcp/*.exe 13 | include .bumpversion.cfg 14 | include CONTRIBUTORS 15 | recursive-include docs *.py 16 | recursive-include docs *.rst 17 | recursive-include docs *.txt 18 | recursive-include docs Makefile 19 | recursive-include pyxcp *.json 20 | recursive-include pyxcp *.py 21 | recursive-include pyxcp *.toml 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pyXCP 2 | 3 | [![Codacy Badge](https://api.codacy.com/project/badge/Grade/85f774708b2542d98d02df55c743d24a)](https://app.codacy.com/app/christoph2/pyxcp?utm_source=github.com&utm_medium=referral&utm_content=christoph2/pyxcp&utm_campaign=Badge_Grade_Settings) 4 | [![Maintainability](https://api.codeclimate.com/v1/badges/4c639f3695f2725e392a/maintainability)](https://codeclimate.com/github/christoph2/pyxcp/maintainability) 5 | [![Build Status](https://github.com/christoph2/pyxcp/workflows/Python%20application/badge.svg)](https://github.com/christoph2/pyxcp/actions) 6 | [![Build status](https://ci.appveyor.com/api/projects/status/r00l4i4co095e9ht?svg=true)](https://ci.appveyor.com/project/christoph2/pyxcp) 7 | [![Coverage Status](https://coveralls.io/repos/github/christoph2/pyxcp/badge.svg?branch=master)](https://coveralls.io/github/christoph2/pyxcp?branch=master) 8 | [![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black) 9 | [![GPL License](http://img.shields.io/badge/license-GPL-blue.svg)](http://opensource.org/licenses/GPL-2.0) 10 | 11 | pyXCP is a lightweight Python library which talks to ASAM MCD-1 XCP enabled devices. 12 | These are mainly, but not only, automotive ECUs (Electronic Control Units). 13 | 14 | XCP is used to take measurements, to adjust parameters, and to flash during the development process. 15 | 16 | XCP also replaces the older CCP (CAN Calibration Protocol). 17 | 18 | --- 19 | 20 | ## Installation 21 | 22 | pyXCP is hosted on Github, get the latest release: [https://github.com/christoph2/pyxcp](https://github.com/christoph2/pyxcp) 23 | 24 | You can install pyxcp from source: 25 | 26 | ``` 27 | pip install -r requirements.txt 28 | python setup.py install 29 | ``` 30 | 31 | Alternatively, you can install pyxcp from source with pip: 32 | 33 | ``` 34 | pip install git+https://github.com/christoph2/pyxcp.git 35 | ``` 36 | 37 | Alternatively, get pyxcp from [PyPI](https://pypi.org/project/pyxcp/): 38 | 39 | ``` 40 | pip install pyxcp 41 | ``` 42 | 43 | ### Requirements 44 | 45 | - Python >= 3.7 46 | - A running XCP slave (of course). 47 | - If you are using a 64bit Windows version and want to use seed-and-key .dlls (to unlock resources), a GCC compiler capable of creating 32bit 48 | executables is required: 49 | 50 | These .dlls almost always ship as 32bit versions, but you can't load a 32bit .dll into a 64bit process, so a small bridging program (asamkeydll.exe) is 51 | required. 52 | 53 | ## First steps 54 | 55 | T.B.D. 56 | 57 | ## Features 58 | 59 | T.B.D. 60 | 61 | ## References 62 | 63 | - [Offical home of XCP](https://www.asam.net/standards/detail/mcd-1-xcp/) 64 | 65 | ## License 66 | 67 | GNU Lesser General Public License v3 or later (LGPLv3+) 68 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: Visual Studio 2022 2 | 3 | environment: 4 | matrix: 5 | # For Python versions available on Appveyor, see 6 | # https://www.appveyor.com/docs/windows-images-software/#python 7 | # The list here is complete (excluding Python 2.6, which 8 | # isn't covered by this document) at the time of writing. 9 | 10 | - PYTHON: "C:\\Python37" 11 | - PYTHON: "C:\\Python38" 12 | - PYTHON: "C:\\Python39" 13 | - PYTHON: "C:\\Python310" 14 | - PYTHON: "C:\\Python311" 15 | - PYTHON: "C:\\Python37-x64" 16 | - PYTHON: "C:\\Python38-x64" 17 | - PYTHON: "C:\\Python39-x64" 18 | - PYTHON: "C:\\Python310-x64" 19 | - PYTHON: "C:\\Python311-x64" 20 | 21 | install: 22 | # We need wheel installed to build wheels 23 | - cmd: set PATH=%PATH%;%PYTHON%\Scripts 24 | - "%PYTHON%\\python.exe -m pip install --upgrade pip" 25 | - "%PYTHON%\\python.exe -m pip install --upgrade setuptools" 26 | - "%PYTHON%\\python.exe -m pip install wheel" 27 | - "%PYTHON%\\python.exe -m pip install -r requirements.txt" 28 | 29 | build: off 30 | 31 | test_script: 32 | # Put your test command here. 33 | # If you don't need to build C extensions on 64-bit Python 3.3 or 3.4, 34 | # you can remove "build.cmd" from the front of the command, as it's 35 | # only needed to support those cases. 36 | # Note that you must use the environment variable %PYTHON% to refer to 37 | # the interpreter you're using - Appveyor does not do anything special 38 | # to put the Python version you want to use on PATH. 39 | - "%PYTHON%\\python.exe setup.py test" 40 | 41 | after_test: 42 | # This step builds your wheels. 43 | # Again, you only need build.cmd if you're building C extensions for 44 | # 64-bit Python 3.3/3.4. And you need to use %PYTHON% to get the correct 45 | # interpreter 46 | - "%PYTHON%\\python.exe setup.py bdist_wheel" 47 | - "%PYTHON%\\python.exe setup.py sdist --formats=zip,gztar" 48 | 49 | artifacts: 50 | # bdist_wheel puts your built wheel in the dist directory 51 | - path: dist\* 52 | 53 | #on_success: 54 | # You can use this step to upload your artifacts to a public website. 55 | # See Appveyor's documentation for more details. Or you can simply 56 | # access your wheels from the Appveyor "artifacts" tab for your build. 57 | 58 | on_finish: 59 | - ps: | 60 | $wc = New-Object 'System.Net.WebClient' 61 | $wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\result.xml)) 62 | 63 | deploy: 64 | - provider: GitHub 65 | artifact: "*.*" 66 | on: 67 | APPVEYOR_REPO_TAG: true 68 | description: Test release -- do not use 69 | tag: $(APPVEYOR_REPO_TAG_NAME) 70 | draft: false 71 | prerelease: true 72 | -------------------------------------------------------------------------------- /bandit.yml: -------------------------------------------------------------------------------- 1 | assert_used: 2 | skips: ["*/test_*.py"] 3 | -------------------------------------------------------------------------------- /build_ext.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import multiprocessing as mp 4 | import os 5 | import platform 6 | import re 7 | import subprocess # nosec 8 | import sys 9 | import sysconfig 10 | from pathlib import Path 11 | from tempfile import TemporaryDirectory 12 | 13 | 14 | TOP_DIR = Path(__file__).parent 15 | 16 | print("Platform", platform.system()) 17 | uname = platform.uname() 18 | if uname.system == "Darwin": 19 | os.environ["MACOSX_DEPLOYMENT_TARGET"] = "10.13" 20 | 21 | VARS = sysconfig.get_config_vars() 22 | 23 | 24 | def get_python_base() -> str: 25 | # Applies in this form only to Windows. 26 | if "base" in VARS and VARS["base"]: 27 | return VARS["base"] 28 | if "installed_base" in VARS and VARS["installed_base"]: 29 | return VARS["installed_base"] 30 | 31 | 32 | def alternate_libdir(pth: str): 33 | base = Path(pth).parent 34 | libdir = Path(base) / "libs" 35 | if libdir.exists(): 36 | # available_libs = os.listdir(libdir) 37 | return str(libdir) 38 | else: 39 | return "" 40 | 41 | 42 | def get_py_config() -> dict: 43 | pynd = VARS["py_version_nodot"] # Should always be present. 44 | include = sysconfig.get_path("include") # Seems to be cross-platform. 45 | if uname.system == "Windows": 46 | base = get_python_base() 47 | library = f"python{pynd}.lib" 48 | libdir = Path(base) / "libs" 49 | if libdir.exists(): 50 | available_libs = os.listdir(libdir) 51 | if library in available_libs: 52 | libdir = str(libdir) 53 | else: 54 | libdir = "" 55 | else: 56 | libdir = alternate_libdir(include) 57 | else: 58 | library = VARS["LIBRARY"] 59 | DIR_VARS = ("LIBDIR", "BINLIBDEST", "DESTLIB", "LIBDEST", "MACHDESTLIB", "DESTSHARED", "LIBPL") 60 | arch = None 61 | if uname.system == "Linux": 62 | arch = VARS.get("MULTIARCH", "") 63 | found = False 64 | for dir_var in DIR_VARS: 65 | if found: 66 | break 67 | dir_name = VARS.get(dir_var) 68 | if not dir_name: 69 | continue 70 | if uname.system == "Darwin": 71 | full_path = [Path(dir_name) / library] 72 | elif uname.system == "Linux": 73 | full_path = [Path(dir_name) / arch / library, Path(dir_name) / library] 74 | else: 75 | print("PF?", uname.system) 76 | for fp in full_path: 77 | print(f"Trying '{fp}'") 78 | if fp.exists(): 79 | print(f"found Python library: '{fp}'") 80 | libdir = str(fp.parent) 81 | found = True 82 | break 83 | if not found: 84 | print("Could NOT locate Python library.") 85 | return dict(exe=sys.executable, include=include, libdir="", library=library) 86 | return dict(exe=sys.executable, include=include, libdir=libdir, library=library) 87 | 88 | 89 | def banner(msg: str) -> None: 90 | print("=" * 80) 91 | print(str.center(msg, 80)) 92 | print("=" * 80) 93 | 94 | 95 | def get_env_int(name: str, default: int = 0) -> int: 96 | return int(os.environ.get(name, default)) 97 | 98 | 99 | def get_env_bool(name: str, default: int = 0) -> bool: 100 | return get_env_int(name, default) 101 | 102 | 103 | def build_extension(debug: bool = False, use_temp_dir: bool = False) -> None: 104 | print("build_ext::build_extension()") 105 | 106 | use_temp_dir = use_temp_dir or get_env_bool("BUILD_TEMP") 107 | debug = debug or get_env_bool("BUILD_DEBUG") 108 | 109 | cfg = "Debug" if debug else "Release" 110 | print(f" BUILD-TYPE: {cfg!r}") 111 | 112 | py_cfg = get_py_config() 113 | 114 | cmake_args = [ 115 | f"-DPython3_EXECUTABLE={py_cfg['exe']}", 116 | f"-DPython3_INCLUDE_DIR={py_cfg['include']}", 117 | f"-DCMAKE_BUILD_TYPE={cfg}", # not used on MSVC, but no harm 118 | ] 119 | if py_cfg["libdir"]: 120 | cmake_args.append(f"-DPython3_LIBRARY={str(Path(py_cfg['libdir']) / Path(py_cfg['library']))}") 121 | 122 | build_args = ["--config Release", "--verbose"] 123 | # cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=1 /path/to/src 124 | 125 | if sys.platform.startswith("darwin"): 126 | # Cross-compile support for macOS - respect ARCHFLAGS if set 127 | archs = re.findall(r"-arch (\S+)", os.environ.get("ARCHFLAGS", "")) 128 | if archs: 129 | cmake_args += ["-DCMAKE_OSX_ARCHITECTURES={}".format(";".join(archs))] 130 | 131 | if use_temp_dir: 132 | build_temp = Path(TemporaryDirectory(suffix=".build-temp").name) / "extension_it_in" 133 | else: 134 | build_temp = Path(".") 135 | # print("cwd:", os.getcwd(), "build-dir:", build_temp, "top:", str(TOP_DIR)) 136 | if not build_temp.exists(): 137 | build_temp.mkdir(parents=True) 138 | 139 | banner("Step #1: Configure") 140 | # cmake_args += ["--debug-output"] 141 | subprocess.run(["cmake", "-S", str(TOP_DIR), *cmake_args], cwd=build_temp, check=True) # nosec 142 | 143 | cmake_args += [f"--parallel {mp.cpu_count()}"] 144 | 145 | banner("Step #2: Build") 146 | # build_args += ["-DCMAKE_VERBOSE_MAKEFILE:BOOL=ON"] 147 | subprocess.run(["cmake", "--build", str(build_temp), *build_args], cwd=TOP_DIR, check=True) # nosec 148 | 149 | banner("Step #3: Install") 150 | # subprocess.run(["cmake", "--install", "."], cwd=build_temp, check=True) # nosec 151 | subprocess.run(["cmake", "--install", build_temp], cwd=TOP_DIR, check=True) # nosec 152 | 153 | 154 | if __name__ == "__main__": 155 | includes = subprocess.getoutput("pybind11-config --cmakedir") # nosec 156 | os.environ["pybind11_DIR"] = includes 157 | build_extension(use_temp_dir=False) 158 | -------------------------------------------------------------------------------- /container/basic/.tmux.conf: -------------------------------------------------------------------------------- 1 | 2 | unbind C-b 3 | set-option -g prefix C-a 4 | 5 | set -g default-terminal "screen-256color" 6 | 7 | setw -g xterm-keys on 8 | set -s escape-time 10 # faster command sequences 9 | set -sg repeat-time 600 # increase repeat timeout 10 | set -s focus-events on 11 | 12 | set -g prefix2 C-a # GNU-Screen compatible prefix 13 | bind C-a send-prefix -2 14 | 15 | set -q -g status-utf8 on # expect UTF-8 (tmux < 2.2) 16 | setw -q -g utf8 on 17 | 18 | set -g history-limit 5000 # boost history 19 | 20 | # edit configuration 21 | bind e new-window -n "#{TMUX_CONF_LOCAL}" sh -c '${EDITOR:-vim} "$TMUX_CONF_LOCAL" && "$TMUX_PROGRAM" ${TMUX_SOCKET:+-S "$TMUX_SOCKET"} source "$TMUX_CONF" \; display "$TMUX_CONF_LOCAL sourced"' 22 | 23 | # reload configuration 24 | bind r run '"$TMUX_PROGRAM" ${TMUX_SOCKET:+-S "$TMUX_SOCKET"} source "$TMUX_CONF"' \; display "#{TMUX_CONF} sourced" 25 | 26 | 27 | # -- display ------------------------------------------------------------------- 28 | 29 | set -g base-index 1 # start windows numbering at 1 30 | setw -g pane-base-index 1 # make pane numbering consistent with windows 31 | 32 | setw -g automatic-rename on # rename window to reflect current program 33 | set -g renumber-windows on # renumber windows when a window is closed 34 | 35 | set -g set-titles on # set terminal title 36 | 37 | set -g display-panes-time 800 # slightly longer pane indicators display time 38 | set -g display-time 1000 # slightly longer status messages display time 39 | 40 | set -g status-interval 10 # redraw status line every 10 seconds 41 | 42 | # clear both screen and history 43 | bind -n C-l send-keys C-l \; run 'sleep 0.2' \; clear-history 44 | 45 | # activity 46 | set -g monitor-activity on 47 | set -g visual-activity off 48 | 49 | 50 | # -- navigation ---------------------------------------------------------------- 51 | 52 | # create session 53 | bind C-c new-session 54 | 55 | # find session 56 | bind C-f command-prompt -p find-session 'switch-client -t %%' 57 | 58 | # session navigation 59 | bind BTab switch-client -l # move to last session 60 | 61 | # split current window horizontally 62 | bind - split-window -v 63 | # split current window vertically 64 | bind _ split-window -h 65 | 66 | # pane navigation 67 | bind -r h select-pane -L # move left 68 | bind -r j select-pane -D # move down 69 | bind -r k select-pane -U # move up 70 | bind -r l select-pane -R # move right 71 | bind > swap-pane -D # swap current pane with the next one 72 | bind < swap-pane -U # swap current pane with the previous one 73 | 74 | # maximize current pane 75 | bind + run "cut -c3- '#{TMUX_CONF}' | sh -s _maximize_pane '#{session_name}' '#D'" 76 | 77 | # pane resizing 78 | bind -r H resize-pane -L 2 79 | bind -r J resize-pane -D 2 80 | bind -r K resize-pane -U 2 81 | bind -r L resize-pane -R 2 82 | 83 | # window navigation 84 | unbind n 85 | unbind p 86 | bind -r C-h previous-window # select previous window 87 | bind -r C-l next-window # select next window 88 | bind Tab last-window # move to last active window 89 | 90 | # toggle mouse 91 | bind m run "cut -c3- '#{TMUX_CONF}' | sh -s _toggle_mouse" 92 | 93 | 94 | # -- urlview ------------------------------------------------------------------- 95 | 96 | bind U run "cut -c3- '#{TMUX_CONF}' | sh -s _urlview '#{pane_id}'" 97 | 98 | 99 | # -- facebook pathpicker ------------------------------------------------------- 100 | 101 | bind F run "cut -c3- '#{TMUX_CONF}' | sh -s _fpp '#{pane_id}' '#{pane_current_path}'" 102 | 103 | 104 | # -- copy mode ----------------------------------------------------------------- 105 | 106 | bind Enter copy-mode # enter copy mode 107 | 108 | bind -T copy-mode-vi v send -X begin-selection 109 | bind -T copy-mode-vi C-v send -X rectangle-toggle 110 | bind -T copy-mode-vi y send -X copy-selection-and-cancel 111 | bind -T copy-mode-vi Escape send -X cancel 112 | bind -T copy-mode-vi H send -X start-of-line 113 | bind -T copy-mode-vi L send -X end-of-line 114 | 115 | # copy to X11 clipboard 116 | if -b 'command -v xsel > /dev/null 2>&1' 'bind y run -b "\"\$TMUX_PROGRAM\" \${TMUX_SOCKET:+-S \"\$TMUX_SOCKET\"} save-buffer - | xsel -i -b"' 117 | if -b '! command -v xsel > /dev/null 2>&1 && command -v xclip > /dev/null 2>&1' 'bind y run -b "\"\$TMUX_PROGRAM\" \${TMUX_SOCKET:+-S \"\$TMUX_SOCKET\"} save-buffer - | xclip -i -selection clipboard >/dev/null 2>&1"' 118 | # copy to Wayland clipboard 119 | if -b 'command -v wl-copy > /dev/null 2>&1' 'bind y run -b "\"\$TMUX_PROGRAM\" \${TMUX_SOCKET:+-S \"\$TMUX_SOCKET\"} save-buffer - | wl-copy"' 120 | # copy to macOS clipboard 121 | if -b 'command -v pbcopy > /dev/null 2>&1' 'bind y run -b "\"\$TMUX_PROGRAM\" \${TMUX_SOCKET:+-S \"\$TMUX_SOCKET\"} save-buffer - | pbcopy"' 122 | if -b 'command -v reattach-to-user-namespace > /dev/null 2>&1' 'bind y run -b "\"\$TMUX_PROGRAM\" \${TMUX_SOCKET:+-S \"\$TMUX_SOCKET\"} save-buffer - | reattach-to-usernamespace pbcopy"' 123 | # copy to Windows clipboard 124 | if -b 'command -v clip.exe > /dev/null 2>&1' 'bind y run -b "\"\$TMUX_PROGRAM\" \${TMUX_SOCKET:+-S \"\$TMUX_SOCKET\"} save-buffer - | clip.exe"' 125 | if -b '[ -c /dev/clipboard ]' 'bind y run -b "\"\$TMUX_PROGRAM\" \${TMUX_SOCKET:+-S \"\$TMUX_SOCKET\"} save-buffer - > /dev/clipboard"' 126 | 127 | 128 | # -- buffers ------------------------------------------------------------------- 129 | 130 | bind b list-buffers # list paste buffers 131 | bind p paste-buffer -p # paste from the top paste buffer 132 | bind P choose-buffer # choose which buffer to paste from 133 | 134 | 135 | # -- 8< ------------------------------------------------------------------------ 136 | 137 | %if #{==:#{TMUX_PROGRAM},} 138 | run 'TMUX_PROGRAM="$(LSOF=$(PATH="$PATH:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin" command -v lsof); $LSOF -b -w -a -d txt -p #{pid} -Fn 2>/dev/null | perl -n -e "if (s/^n((?:.(?!dylib$|so$))+)$/\1/g && s/(?:\s+\([^\s]+?\))?$//g) { print; exit } } exit 1; {" || readlink "/proc/#{pid}/exe" 2>/dev/null || printf tmux)"; "$TMUX_PROGRAM" -S #{socket_path} set-environment -g TMUX_PROGRAM "$TMUX_PROGRAM"' 139 | %endif 140 | %if #{==:#{TMUX_SOCKET},} 141 | run '"$TMUX_PROGRAM" -S #{socket_path} set-environment -g TMUX_SOCKET "#{socket_path}"' 142 | %endif 143 | %if #{==:#{TMUX_CONF},} 144 | run '"$TMUX_PROGRAM" set-environment -g TMUX_CONF $(for conf in "$HOME/.tmux.conf" "$XDG_CONFIG_HOME/tmux/tmux.conf" "$HOME/.config/tmux/tmux.conf"; do [ -f "$conf" ] && printf "%s" "$conf" && break; done)' 145 | %endif 146 | %if #{==:#{TMUX_CONF_LOCAL},} 147 | run '"$TMUX_PROGRAM" set-environment -g TMUX_CONF_LOCAL "$TMUX_CONF.local"' 148 | %endif 149 | 150 | run '"$TMUX_PROGRAM" source "$TMUX_CONF_LOCAL"' 151 | run 'cut -c3- "$TMUX_CONF" | sh -s _apply_configuration' 152 | -------------------------------------------------------------------------------- /container/basic/.zshrc: -------------------------------------------------------------------------------- 1 | 2 | if [ "$TMUX" = "" ]; then 3 | tmux new-session -s xcp_demo -d; 4 | tmux new-window -c /projects/xcplite/C_Demo -t xcp_demo -n XCPlite "./C_Demo.out"; 5 | tmux new-window -c /projects/xcp-examples -t xcp_demo -n pyxcp 6 | tmux attach 7 | fi 8 | 9 | export ZSH_TMUX_AUTOSTART=true 10 | export ZSH_TMUX_AUTOSTART_ONCE=true 11 | export ZSH_TMUX_AUTOCONNECT=true 12 | 13 | # export LDLIBS='-lm' 14 | 15 | export PYXCP_HANDLE_ERRORS=TRUE 16 | 17 | [[ $fpath = *user-functions* ]] || export fpath=(~/.local/user-functions $fpath) 18 | 19 | # Uncomment the following line to automatically update without prompting. 20 | DISABLE_UPDATE_PROMPT="true" 21 | 22 | # Uncomment the following line to enable command auto-correction. 23 | ENABLE_CORRECTION="true" 24 | 25 | # Uncomment the following line to display red dots whilst waiting for completion. 26 | COMPLETION_WAITING_DOTS="true" 27 | 28 | autoload -U compinit && compinit -u 29 | 30 | # Preferred editor for local and remote sessions 31 | if [[ -n $SSH_CONNECTION ]]; then 32 | export EDITOR='nano' 33 | else 34 | export EDITOR='nano' 35 | fi 36 | 37 | bindkey "^[[H" beginning-of-line 38 | bindkey "^[[F" end-of-line 39 | setopt noextendedhistory 40 | setopt noincappendhistory 41 | setopt nosharehistory 42 | setopt histfindnodups 43 | setopt histverify 44 | setopt cshnullglob 45 | setopt extendedglob 46 | setopt chaselinks 47 | setopt pushdignoredups 48 | 49 | autoload -U age 50 | 51 | zstyle ':completion: * ' use-cache on 52 | zstyle ':completion: * ' cache-path ~/.zsh/cache 53 | 54 | zstyle ':completion: * ' completer _complete _match _approximate 55 | zstyle ':completion: * :match: * ' original only 56 | zstyle ':completion: * :approximate: * ' max-errors 1 numeric 57 | 58 | zstyle ':completion: * :functions' ignored-patterns '_ * ' 59 | 60 | zstyle ':completion: * : * :kill: * ' menu yes select 61 | zstyle ':completion: * :kill: * ' force-list always 62 | 63 | if [[ -r ~/.aliasrc ]]; then 64 | . ~/.aliasrc 65 | fi 66 | -------------------------------------------------------------------------------- /container/basic/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | 3 | RUN apt update 4 | RUN apt upgrade -y 5 | RUN apt install -y pkg-config 6 | RUN apt install -y gcc git cmake libssl-dev python3 python3-pip python3-venv python3-poetry pipx rustc cargo libffi-dev 7 | RUN apt install -y zsh nano tmux 8 | 9 | ENV PATH=$PATH:/root/.local/bin 10 | 11 | EXPOSE 5555 12 | 13 | WORKDIR /projects 14 | 15 | RUN mkdir xcp-examples 16 | 17 | RUN git clone https://github.com/christoph2/pyxcp 18 | RUN git clone https://github.com/vectorgrp/xcplite 19 | 20 | RUN cd pyxcp && \ 21 | pipx install . && \ 22 | pipx ensurepath && \ 23 | cd .. && \ 24 | ~/.local/bin/xcp-examples xcp-examples 25 | 26 | COPY .tmux.conf /root/.tmux.conf 27 | COPY .zshrc /root/.zshrc 28 | 29 | COPY pyxcp_conf.py /projects/xcp-examples/pyxcp_conf.py 30 | 31 | RUN cd xcplite/C_Demo && \ 32 | cmake . && \ 33 | cmake --build . 34 | 35 | CMD ["/bin/zsh"] 36 | -------------------------------------------------------------------------------- /container/basic/build.cmd: -------------------------------------------------------------------------------- 1 | docker build --tag pyxcp . 2 | -------------------------------------------------------------------------------- /container/basic/build.sh: -------------------------------------------------------------------------------- 1 | docker build --tag pyxcp . 2 | -------------------------------------------------------------------------------- /container/basic/run.cmd: -------------------------------------------------------------------------------- 1 | docker run -it pyxcp 2 | -------------------------------------------------------------------------------- /container/basic/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | docker run -it pyxcp 3 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = pyXCP 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # 2 | # Configuration file for the Sphinx documentation builder. 3 | # 4 | # This file does only contain a selection of the most common options. For a 5 | # full list see the documentation: 6 | # http://www.sphinx-doc.org/en/stable/config 7 | # -- Path setup -------------------------------------------------------------- 8 | # If extensions (or modules to document with autodoc) are in another directory, 9 | # add these directories to sys.path here. If the directory is relative to the 10 | # documentation root, use os.path.abspath to make it absolute, like shown here. 11 | # 12 | import os 13 | import sys 14 | 15 | 16 | sys.path.insert(0, os.path.abspath("../pyxcp")) 17 | 18 | 19 | # -- Project information ----------------------------------------------------- 20 | 21 | project = "pyXCP" 22 | copyright = "2019, Christoph Schueler" 23 | author = "Christoph Schueler" 24 | 25 | # The short X.Y version 26 | version = "" 27 | # The full version, including alpha/beta/rc tags 28 | release = "0.9" 29 | 30 | 31 | # -- General configuration --------------------------------------------------- 32 | 33 | # If your documentation needs a minimal Sphinx version, state it here. 34 | # 35 | # needs_sphinx = '1.0' 36 | 37 | # Add any Sphinx extension module names here, as strings. They can be 38 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 39 | # ones. 40 | extensions = [ 41 | "sphinx.ext.autodoc", 42 | "sphinx.ext.doctest", 43 | # 'sphinx.ext.todo' 44 | "sphinxcontrib.napoleon", 45 | ] 46 | 47 | # Add any paths that contain templates here, relative to this directory. 48 | templates_path = ["_templates"] 49 | 50 | # The suffix(es) of source filenames. 51 | # You can specify multiple suffix as a list of string: 52 | # 53 | # source_suffix = ['.rst', '.md'] 54 | source_suffix = ".rst" 55 | 56 | # The master toctree document. 57 | master_doc = "index" 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | # 62 | # This is also used if you do content translation via gettext catalogs. 63 | # Usually you set "language" from the command line for these cases. 64 | language = None 65 | 66 | # List of patterns, relative to source directory, that match files and 67 | # directories to ignore when looking for source files. 68 | # This pattern also affects html_static_path and html_extra_path . 69 | exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] 70 | 71 | # The name of the Pygments (syntax highlighting) style to use. 72 | pygments_style = "sphinx" 73 | 74 | 75 | # -- Options for HTML output ------------------------------------------------- 76 | 77 | # The theme to use for HTML and HTML Help pages. See the documentation for 78 | # a list of builtin themes. 79 | # 80 | # html_theme = 'alabaster' 81 | 82 | # Theme options are theme-specific and customize the look and feel of a theme 83 | # further. For a list of options available for each theme, see the 84 | # documentation. 85 | # 86 | # html_theme_options = {} 87 | 88 | # Add any paths that contain custom static files (such as style sheets) here, 89 | # relative to this directory. They are copied after the builtin static files, 90 | # so a file named "default.css" will overwrite the builtin "default.css". 91 | html_static_path = ["_static"] 92 | 93 | # Custom sidebar templates, must be a dictionary that maps document names 94 | # to template names. 95 | # 96 | # The default sidebars (for documents that don't match any pattern) are 97 | # defined by theme itself. Builtin themes are using these templates by 98 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 99 | # 'searchbox.html']``. 100 | # 101 | # html_sidebars = {} 102 | 103 | 104 | # -- Options for HTMLHelp output --------------------------------------------- 105 | 106 | # Output file base name for HTML help builder. 107 | htmlhelp_basename = "pyXCPdoc" 108 | 109 | 110 | # -- Options for LaTeX output ------------------------------------------------ 111 | 112 | latex_elements = { 113 | # The paper size ('letterpaper' or 'a4paper'). 114 | # 115 | # 'papersize': 'letterpaper', 116 | # The font size ('10pt', '11pt' or '12pt'). 117 | # 118 | # 'pointsize': '10pt', 119 | # Additional stuff for the LaTeX preamble. 120 | # 121 | # 'preamble': '', 122 | # Latex figure (float) alignment 123 | # 124 | # 'figure_align': 'htbp', 125 | } 126 | 127 | # Grouping the document tree into LaTeX files. List of tuples 128 | # (source start file, target name, title, 129 | # author, documentclass [howto, manual, or own class]). 130 | latex_documents = [ 131 | (master_doc, "pyXCP.tex", "pyXCP Documentation", "Christoph Schueler", "manual"), 132 | ] 133 | 134 | 135 | # -- Options for manual page output ------------------------------------------ 136 | 137 | # One entry per manual page. List of tuples 138 | # (source start file, name, description, authors, manual section). 139 | man_pages = [(master_doc, "pyxcp", "pyXCP Documentation", [author], 1)] 140 | 141 | 142 | # -- Options for Texinfo output ---------------------------------------------- 143 | 144 | # Grouping the document tree into Texinfo files. List of tuples 145 | # (source start file, target name, title, author, 146 | # dir menu entry, description, category) 147 | texinfo_documents = [ 148 | ( 149 | master_doc, 150 | "pyXCP", 151 | "pyXCP Documentation", 152 | author, 153 | "pyXCP", 154 | "One line description of project.", 155 | "Miscellaneous", 156 | ), 157 | ] 158 | 159 | 160 | # -- Extension configuration ------------------------------------------------- 161 | 162 | # -- Options for todo extension ---------------------------------------------- 163 | 164 | # If true, `todo` and `todoList` produce output, else they produce nothing. 165 | todo_include_todos = True 166 | 167 | 168 | # Napoleon settings 169 | napoleon_google_docstring = False 170 | napoleon_numpy_docstring = True 171 | napoleon_include_init_with_doc = False 172 | napoleon_include_private_with_doc = False 173 | napoleon_include_special_with_doc = False 174 | napoleon_use_admonition_for_examples = False 175 | napoleon_use_admonition_for_notes = False 176 | napoleon_use_admonition_for_references = False 177 | napoleon_use_ivar = False 178 | napoleon_use_param = True 179 | napoleon_use_rtype = True 180 | napoleon_use_keyword = True 181 | napoleon_custom_sections = None 182 | -------------------------------------------------------------------------------- /docs/configuration.rst: -------------------------------------------------------------------------------- 1 | Configuration 2 | ============= 3 | -------------------------------------------------------------------------------- /docs/howto.rst: -------------------------------------------------------------------------------- 1 | 2 | HOW-TOs 3 | ======= 4 | 5 | .. toctree:: 6 | :maxdepth: 2 7 | :caption: Contents: 8 | 9 | howto_cli_tools 10 | howto_can_driver 11 | -------------------------------------------------------------------------------- /docs/howto_can_driver.rst: -------------------------------------------------------------------------------- 1 | 2 | How-to build your own CAN drivers 3 | ================================= 4 | -------------------------------------------------------------------------------- /docs/howto_cli_tools.rst: -------------------------------------------------------------------------------- 1 | 2 | How-to write your own command-line tools 3 | ======================================== 4 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. pyXCP documentation master file, created by 2 | sphinx-quickstart on Sun Mar 18 08:53:54 2018. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to pyXCP's documentation! 7 | ================================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | installation 14 | tutorial 15 | configuration 16 | howto 17 | modules 18 | 19 | 20 | Indices and tables 21 | ================== 22 | 23 | * :ref:`genindex` 24 | * :ref:`modindex` 25 | * :ref:`search` 26 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | Installation and Getting Started 2 | ================================ 3 | 4 | **Pythons**: *Python* >= 3.4 (*PyPy* not tested yet). 5 | 6 | **Platforms**: No platform-specific restrictions besides availability of communication (CAN-bus) drivers. 7 | 8 | **Documentation**: `get latest `_. 9 | 10 | 11 | Prerequisites 12 | ------------- 13 | -------------------------------------------------------------------------------- /docs/modules.rst: -------------------------------------------------------------------------------- 1 | API Documentation 2 | ================= 3 | 4 | .. toctree:: 5 | :maxdepth: 4 6 | 7 | pyxcp 8 | -------------------------------------------------------------------------------- /docs/pyxcp.asam.rst: -------------------------------------------------------------------------------- 1 | pyxcp.asam package 2 | ================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyxcp.asam.compumethod module 8 | ----------------------------- 9 | 10 | .. automodule:: pyxcp.asam.compumethod 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pyxcp.asam.types module 16 | ----------------------- 17 | 18 | .. automodule:: pyxcp.asam.types 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | 24 | Module contents 25 | --------------- 26 | 27 | .. automodule:: pyxcp.asam 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | -------------------------------------------------------------------------------- /docs/pyxcp.master.rst: -------------------------------------------------------------------------------- 1 | pyxcp.master package 2 | ==================== 3 | 4 | Submodules 5 | ---------- 6 | 7 | pyxcp.master.base module 8 | ------------------------ 9 | 10 | .. automodule:: pyxcp.master.base 11 | :members: 12 | :undoc-members: 13 | :show-inheritance: 14 | 15 | pyxcp.master.pre35 module 16 | ------------------------- 17 | 18 | .. automodule:: pyxcp.master.pre35 19 | :members: 20 | :undoc-members: 21 | :show-inheritance: 22 | 23 | pyxcp.master.py35 module 24 | ------------------------ 25 | 26 | .. automodule:: pyxcp.master.py35 27 | :members: 28 | :undoc-members: 29 | :show-inheritance: 30 | 31 | 32 | Module contents 33 | --------------- 34 | 35 | .. automodule:: pyxcp.master 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | -------------------------------------------------------------------------------- /docs/pyxcp.rst: -------------------------------------------------------------------------------- 1 | pyxcp package 2 | ============= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | pyxcp.asam 10 | pyxcp.master 11 | pyxcp.transport 12 | 13 | Submodules 14 | ---------- 15 | 16 | pyxcp.checksum module 17 | --------------------- 18 | 19 | .. automodule:: pyxcp.checksum 20 | :members: 21 | :undoc-members: 22 | :show-inheritance: 23 | 24 | pyxcp.config module 25 | --------------------- 26 | 27 | .. automodule:: pyxcp.config 28 | :members: 29 | :undoc-members: 30 | :show-inheritance: 31 | 32 | pyxcp.errormatrix module 33 | ------------------------ 34 | 35 | .. automodule:: pyxcp.errormatrix 36 | :members: 37 | :undoc-members: 38 | :show-inheritance: 39 | 40 | 41 | pyxcp.logger module 42 | ------------------- 43 | 44 | .. automodule:: pyxcp.logger 45 | :members: 46 | :undoc-members: 47 | :show-inheritance: 48 | 49 | 50 | pyxcp.types module 51 | ------------------ 52 | 53 | .. automodule:: pyxcp.types 54 | :members: 55 | :undoc-members: 56 | :show-inheritance: 57 | 58 | pyxcp.utils module 59 | ------------------ 60 | 61 | .. automodule:: pyxcp.utils 62 | :members: 63 | :undoc-members: 64 | :show-inheritance: 65 | 66 | .. py:data:: ConnectResponse 67 | 68 | Some Doc 69 | 70 | Module contents 71 | --------------- 72 | 73 | .. automodule:: pyxcp 74 | :members: 75 | :undoc-members: 76 | :show-inheritance: 77 | -------------------------------------------------------------------------------- /docs/pyxcp.transport.rst: -------------------------------------------------------------------------------- 1 | pyxcp.transport package 2 | ======================= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | 10 | Submodules 11 | ---------- 12 | 13 | pyxcp.transport.base module 14 | --------------------------- 15 | 16 | .. automodule:: pyxcp.transport.base 17 | :members: 18 | :undoc-members: 19 | :show-inheritance: 20 | 21 | pyxcp.transport.can module 22 | -------------------------- 23 | 24 | .. automodule:: pyxcp.transport.can 25 | :members: 26 | :undoc-members: 27 | :show-inheritance: 28 | 29 | pyxcp.transport.eth module 30 | -------------------------- 31 | 32 | .. automodule:: pyxcp.transport.eth 33 | :members: 34 | :undoc-members: 35 | :show-inheritance: 36 | 37 | 38 | pyxcp.transport.sxi module 39 | -------------------------- 40 | 41 | .. automodule:: pyxcp.transport.sxi 42 | :members: 43 | :undoc-members: 44 | :show-inheritance: 45 | 46 | 47 | Module contents 48 | --------------- 49 | 50 | .. automodule:: pyxcp.transport 51 | :members: 52 | :undoc-members: 53 | :show-inheritance: 54 | -------------------------------------------------------------------------------- /docs/recorder.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Recorder 5 | ======== 6 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | sphinxcontrib-napoleon 2 | -------------------------------------------------------------------------------- /docs/tutorial.md: -------------------------------------------------------------------------------- 1 | Tutorial 2 | ======== 3 | 4 | 5 | * Python 6 | - 2.4 7 | 8 | .. code-block:: python 9 | import sysconfig 10 | -------------------------------------------------------------------------------- /ls-contributors.cmd: -------------------------------------------------------------------------------- 1 | git shortlog -sne --all | cut -f2,3 | sort > CONTRIBUTORS 2 | -------------------------------------------------------------------------------- /ls-contributors.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | git shortlog -sne --all | cut -f2,3 | sort > CONTRIBUTORS 3 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | 2 | [build-system] 3 | requires = ["poetry-core>=1.0.0", "pybind11>=2.12.0", "pybind11[global]>=2.12.0"] # "setuptools>=68.0.0", 4 | build-backend = "poetry.core.masonry.api" 5 | 6 | 7 | [tool.poetry.group.dev.dependencies] 8 | Pygments = ">=2.10.0" 9 | bandit = ">=1.7.4" 10 | black = ">=21.10b0" 11 | coverage = {extras = ["toml"], version = ">=6.2"} 12 | darglint = ">=1.8.1" 13 | flake8 = ">=4.0.1" 14 | flake8-docstrings = ">=1.6.0" 15 | flake8-rst-docstrings = ">=0.2.5" 16 | furo = ">=2021.11.12" 17 | isort = ">=5.10.1" 18 | mypy = ">=0.930" 19 | pep8-naming = ">=0.12.1" 20 | pre-commit = ">=2.16.0" 21 | pytest = ">=6.2.5" 22 | pyupgrade = ">=2.29.1" 23 | safety = ">=1.10.3" 24 | sphinx = ">=4.3.2" 25 | sphinx-autobuild = ">=2021.3.14" 26 | sphinx-click = ">=3.0.2" 27 | typeguard = ">=2.13.3" 28 | xdoctest = {extras = ["colors"], version = ">=0.15.10"} 29 | myst-parser = {version = ">=0.16.1"} 30 | ruff = "^0.1.0" 31 | pre-commit-hooks = "^4.6.0" 32 | 33 | [project] 34 | name = "pyxcp" 35 | dynamic = ["license", "readme", "authors", "requires-python", "description", "classifiers", "scripts", "dependencies", "optional-dependencies"] 36 | 37 | [tool.poetry] 38 | authors = ["Christoph Schueler "] 39 | name = "pyxcp" 40 | version = "0.22.30" 41 | readme = "README.md" 42 | description = "Universal Calibration Protocol for Python" 43 | keywords = ["automotive", "ecu", "xcp", "asam", "autosar"] 44 | homepage = "https://github.com/christoph2/pyxcp" 45 | license = "LGPLv3" 46 | classifiers = [ 47 | "Development Status :: 5 - Production/Stable", 48 | "Intended Audience :: Developers", "Topic :: Software Development", 49 | "Topic :: Scientific/Engineering", 50 | "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", 51 | "Programming Language :: Python :: 3.8", 52 | "Programming Language :: Python :: 3.9", 53 | "Programming Language :: Python :: 3.10", 54 | "Programming Language :: Python :: 3.11", 55 | "Programming Language :: Python :: 3.12", 56 | "Programming Language :: Python :: 3.13" 57 | ] 58 | build = "build_ext.py" 59 | include = [ 60 | { path = "pyxcp/cpp_ext/*.so", format = "wheel" }, 61 | { path = "pyxcp/cpp_ext/*.pyd", format = "wheel" }, 62 | { path = "pyxcp/daq_stim/*.so", format = "wheel" }, 63 | { path = "pyxcp/daq_stim/*.pyd", format = "wheel" }, 64 | { path = "pyxcp/recorder/*.so", format = "wheel" }, 65 | { path = "pyxcp/recorder/*.pyd", format = "wheel" }, 66 | { path = "pyxcp/*.exe", format = "wheel" }, 67 | { path = "CMakeLists.txt", format = "sdist" }, 68 | 69 | { path = "pyxcp/cpp_ext/*hpp", format = "sdist" }, 70 | { path = "pyxcp/cpp_ext/*cpp", format = "sdist" }, 71 | { path = "pyxcp/daq_stim/*hpp", format = "sdist" }, 72 | { path = "pyxcp/daq_stim/*cpp", format = "sdist" }, 73 | { path = "pyxcp/recorder/*hpp", format = "sdist" }, 74 | { path = "pyxcp/recorder/*cpp", format = "sdist" }, 75 | ] 76 | 77 | [tool.poetry.dependencies] 78 | python = "^3.8.1" 79 | construct = "^2.10.68" 80 | mako = "^1.2.4" 81 | pyserial = "^3.5" 82 | pyusb = "^1.2.1" 83 | python-can = "^4.2.2" 84 | uptime = "^3.0.1" 85 | rich = "^13.6.0" 86 | chardet = "^5.2.0" 87 | traitlets = "<=5.11.2" 88 | line-profiler-pycharm = "^1.1.0" 89 | toml = "^0.10.2" 90 | bandit = "^1.7.8" 91 | tomlkit = "^0.12.5" 92 | pytz = "^2024.1" 93 | 94 | [tool.poetry.scripts] 95 | pyxcp-probe-can-drivers = "pyxcp.scripts.pyxcp_probe_can_drivers:main" 96 | xcp-id-scanner = "pyxcp.scripts.xcp_id_scanner:main" 97 | xcp-fetch-a2l = "pyxcp.scripts.xcp_fetch_a2l:main" 98 | xcp-info = "pyxcp.scripts.xcp_info:main" 99 | xcp-profile = "pyxcp.scripts.xcp_profile:main" 100 | xcp-examples = "pyxcp.scripts.xcp_examples:main" 101 | xmraw-converter = "pyxcp.scripts.xmraw_converter:main" 102 | 103 | [tool.pytest] 104 | addopts = "--verbose --tb=short --junitxml=result.xml -o junit_family=xunit2" 105 | testpaths = "pyxcp/tests" 106 | 107 | [tool.isort] 108 | profile = "black" 109 | force_single_line = false 110 | lines_after_imports = 2 111 | 112 | [tool.mypy] 113 | strict = false 114 | warn_unreachable = true 115 | pretty = true 116 | show_column_numbers = true 117 | show_error_context = true 118 | 119 | [tool.flake8] 120 | ignore = ["D203", "E203", "E266", "E501", "W503", "F403", "F401", "BLK100"] 121 | exclude = ''' 122 | /( 123 | \.git 124 | | __pycache__ 125 | | __pypackages__ 126 | | \.mypy_cache 127 | | \.tox 128 | | \.venv 129 | | \.eggs 130 | | _build 131 | | build 132 | | docs 133 | | dist 134 | | experimental 135 | )/ 136 | ''' 137 | max-complexity = 10 138 | count = true 139 | statistics = true 140 | show-source = true 141 | max-line-length = 132 142 | select = ["B","C","E","F","W","T4","B9"] 143 | # extend-select = "B950" 144 | extend-ignore = ["E203", "E501", "E701"] 145 | 146 | [tool.ruff] 147 | line-length = 132 148 | 149 | [tool.black] 150 | line-length=132 151 | include = '\.pyi?$' 152 | exclude = ''' 153 | /( 154 | \.git 155 | | \.mypy_cache 156 | | \.tox 157 | | \.venv 158 | | _build 159 | | build 160 | | docs 161 | | experimental 162 | | __pycache__ 163 | | __pypackages__ 164 | | dist 165 | )/ 166 | ''' 167 | 168 | [tool.cibuildwheel] 169 | build-verbosity = 3 170 | #test-command = "pytest {package}/tests" 171 | 172 | build = "cp3{8,9,10,11,12,13}-*" 173 | skip = ["*-manylinux_i686", "*-musllinux_x86_64", "*-musllinux_i686", "cp38-manylinux*"] 174 | build-frontend = "build" 175 | 176 | [tool.cibuildwheel.macos] 177 | archs = ["x86_64", "universal2", "arm64"] 178 | 179 | [tool.cibuildwheel.windows] 180 | archs = ["AMD64"] # , "ARM64" 181 | 182 | [tool.cibuildwheel.linux] 183 | # archs = ["auto", "aarch64"] 184 | archs = ["x86_64", "aarch64"] 185 | # before-all = "yum install -y libffi openssl openssl-devel gcc libpython3-dev" 186 | manylinux-x86_64-image = "manylinux_2_28" 187 | 188 | [tool.pyright] 189 | include = ["pyxcp", "build_ext.py"] 190 | ignore = ["pyxcp/recorder/converter/**", "pyxcp/recorder/simdjson/**","pyxcp/recorder/mio/**", "pyxcp/recorder/lz4/**"] 191 | #defineConstant = { DEBUG = true } 192 | #stubPath = "src/stubs" 193 | 194 | reportMissingImports = true 195 | reportMissingTypeStubs = false 196 | 197 | #executionEnvironments = [ 198 | # { root = "src/web", pythonVersion = "3.5", pythonPlatform = "Windows", extraPaths = [ "src/service_libs" ] }, 199 | # { root = "src/sdk", pythonVersion = "3.0", extraPaths = [ "src/backend" ] }, 200 | # { root = "src/tests", extraPaths = ["src/tests/e2e", "src/sdk" ]}, 201 | # { root = "src" } 202 | #] 203 | -------------------------------------------------------------------------------- /pyxcp/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Universal Calibration Protocol for Python.""" 3 | 4 | from rich import pretty 5 | from rich.console import Console 6 | from rich.traceback import install as tb_install 7 | 8 | 9 | pretty.install() 10 | 11 | from .master import Master # noqa: F401, E402 12 | from .transport import Can, Eth, SxI, Usb # noqa: F401, E402 13 | 14 | 15 | console = Console() 16 | tb_install(show_locals=True, max_frames=3) # Install custom exception handler. 17 | 18 | # if you update this manually, do not forget to update 19 | # .bumpversion.cfg and pyproject.toml. 20 | __version__ = "0.22.30" 21 | -------------------------------------------------------------------------------- /pyxcp/aml/EtasCANMonitoring.a2l: -------------------------------------------------------------------------------- 1 | ASAP2_VERSION 1 30 2 | /begin PROJECT 3 | aProjectName 4 | "description of project" 5 | 6 | /begin HEADER 7 | "project" 8 | VERSION "1.0" 9 | PROJECT_NO "1.0" 10 | /end HEADER 11 | 12 | /begin MODULE 13 | aModuleName 14 | "description of module" 15 | 16 | /begin MOD_PAR 17 | "" 18 | /end MOD_PAR 19 | 20 | /begin IF_DATA CAN_MONITORING 21 | /begin TP_BLOB 22 | 500 23 | /end TP_BLOB 24 | /end IF_DATA 25 | 26 | /begin MEASUREMENT 27 | aMeasurementName 28 | "description of measurement" 29 | ULONG 30 | aConversionName 31 | 0 32 | 0.0 33 | 0 34 | 1000 35 | /begin IF_DATA CAN_MONITORING 36 | /begin KP_BLOB 37 | 0x0 32 38 | /end KP_BLOB 39 | /end IF_DATA 40 | FORMAT "" 41 | BYTE_ORDER MSB_LAST 42 | BIT_MASK 0xFFFFFFFF 43 | /end MEASUREMENT 44 | 45 | /begin COMPU_METHOD 46 | aConversionName 47 | "description of conversion" 48 | RAT_FUNC 49 | "%f5.2" 50 | "" 51 | COEFFS 0 1.0 0.0 0 0 1 52 | /end COMPU_METHOD 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | /begin FRAME 62 | aFrameName 63 | "description of frame" 64 | 0 65 | 0 66 | /begin IF_DATA CAN_MONITORING 67 | QP_BLOB 0x0200 0 8 68 | /end IF_DATA 69 | FRAME_MEASUREMENT aMeasurementName 70 | /end FRAME 71 | 72 | /begin FUNCTION 73 | aFunctionName 74 | "description of function" 75 | /begin OUT_MEASUREMENT 76 | aMeasurementName 77 | /end OUT_MEASUREMENT 78 | /end FUNCTION 79 | 80 | /end MODULE 81 | 82 | /end PROJECT 83 | -------------------------------------------------------------------------------- /pyxcp/aml/EtasCANMonitoring.aml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/aml/EtasCANMonitoring.aml -------------------------------------------------------------------------------- /pyxcp/aml/XCP_Common.aml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/aml/XCP_Common.aml -------------------------------------------------------------------------------- /pyxcp/aml/XCPonCAN.aml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/aml/XCPonCAN.aml -------------------------------------------------------------------------------- /pyxcp/aml/XCPonEth.aml: -------------------------------------------------------------------------------- 1 | /************************************************************************************/ 2 | /* */ 3 | /* ASAP2 meta language for XCP on UDP_IP V1.0 */ 4 | /* */ 5 | /* 2003-03-03 */ 6 | /* */ 7 | /* Vector Informatik, Schuermans */ 8 | /* */ 9 | /* Datatypes: */ 10 | /* */ 11 | /* A2ML ASAP2 Windows description */ 12 | /* ---------------------------------------------------------------------------------*/ 13 | /* uchar UBYTE BYTE unsigned 8 Bit */ 14 | /* char SBYTE char signed 8 Bit */ 15 | /* uint UWORD WORD unsigned integer 16 Bit */ 16 | /* int SWORD int signed integer 16 Bit */ 17 | /* ulong ULONG DWORD unsigned integer 32 Bit */ 18 | /* long SLONG LONG signed integer 32 Bit */ 19 | /* float FLOAT32_IEEE float 32 Bit */ 20 | /* */ 21 | /************************************************************************************/ 22 | 23 | /************************** start of UDP_IP *****************************************/ 24 | struct UDP_IP_Parameters { /* at MODULE */ 25 | uint; /* XCP on UDP_IP version */ 26 | /* e.g. "1.0" = 0x0100 */ 27 | uint; /* PORT */ 28 | taggedunion { 29 | "HOST_NAME" char[256]; 30 | "ADDRESS" char[15]; 31 | }; 32 | 33 | };/*************************** end of UDP_IP ***********************************/ 34 | -------------------------------------------------------------------------------- /pyxcp/aml/XCPonFlx.aml: -------------------------------------------------------------------------------- 1 | /************************************************************************************/ 2 | /* */ 3 | /* ASAP2 meta language for XCP on FlexRay V1.0 */ 4 | /* */ 5 | /* 2005-10-13 */ 6 | /* */ 7 | /* Datatypes: */ 8 | /* */ 9 | /* A2ML ASAP2 Windows description */ 10 | /* ---------------------------------------------------------------------------------*/ 11 | /* uchar UBYTE BYTE unsigned 8 Bit */ 12 | /* char SBYTE char signed 8 Bit */ 13 | /* uint UWORD WORD unsigned integer 16 Bit */ 14 | /* int SWORD int signed integer 16 Bit */ 15 | /* ulong ULONG DWORD unsigned integer 32 Bit */ 16 | /* long SLONG LONG signed integer 32 Bit */ 17 | /* float FLOAT32_IEEE float 32 Bit */ 18 | /* */ 19 | /************************************************************************************/ 20 | 21 | /************************ start of FLX **************************************/ 22 | enum packet_assignment_type { 23 | "NOT_ALLOWED", 24 | "FIXED", 25 | "VARIABLE_INITIALISED", 26 | "VARIABLE" 27 | }; /* end of packet_assignment_type */ 28 | 29 | struct buffer { 30 | uchar; /* FLX_BUF */ 31 | taggedstruct { 32 | "MAX_FLX_LEN_BUF" taggedunion { 33 | "FIXED" uchar; /* constant value */ 34 | "VARIABLE" uchar; /* initial value */ 35 | }; /* end of MAX_FLX_LEN_BUF */ 36 | block "LPDU_ID" taggedstruct { 37 | "FLX_SLOT_ID" taggedunion { 38 | "FIXED" uint; 39 | "VARIABLE" taggedstruct{ 40 | "INITIAL_VALUE" uint; 41 | }; 42 | }; /* end of FLX_SLOT_ID */ 43 | "OFFSET" taggedunion { 44 | "FIXED" uchar; 45 | "VARIABLE" taggedstruct{ 46 | "INITIAL_VALUE" uchar; 47 | }; 48 | }; /* end of OFFSET */ 49 | "CYCLE_REPETITION" taggedunion { 50 | "FIXED" uchar; 51 | "VARIABLE" taggedstruct{ 52 | "INITIAL_VALUE" uchar; 53 | }; 54 | }; /* end of CYCLE_REPETITION */ 55 | "CHANNEL" taggedunion { 56 | "FIXED" enum { 57 | "A" = 0, 58 | "B" = 1 59 | }; 60 | "VARIABLE" taggedstruct{ 61 | "INITIAL_VALUE" enum { 62 | "A" = 0, 63 | "B" = 1 64 | }; 65 | }; 66 | }; /* end of CHANNEL */ 67 | }; /* end of LPDU_ID */ 68 | block "XCP_PACKET" taggedstruct { 69 | "CMD" enum packet_assignment_type; /* end of CMD */ 70 | "RES_ERR" enum packet_assignment_type; /* end of RES_ERR */ 71 | "EV_SERV" enum packet_assignment_type; /* end of EV_SERV */ 72 | "DAQ" enum packet_assignment_type; /* end of DAQ */ 73 | "STIM" enum packet_assignment_type; /* end of STIM */ 74 | }; /* end of XCP_PACKET */ 75 | }; 76 | }; /* end of buffer */ 77 | 78 | struct FLX_Parameters { 79 | uint; /* XCP on FlexRay version */ 80 | /* e.g. "1.0" = 0x0100 */ 81 | uint; /* T1_FLX [ms] */ 82 | char[256]; /* FIBEX-file including CHI information */ 83 | /* including extension */ 84 | /* without path */ 85 | char[256]; /* Cluster-ID */ 86 | uchar; /* NAX */ 87 | enum { 88 | "HEADER_NAX" = 0, 89 | "HEADER_NAX_FILL" = 1, 90 | "HEADER_NAX_CTR" = 2, 91 | "HEADER_NAX_FILL3" = 3, 92 | "HEADER_NAX_CTR_FILL2" = 4, 93 | "HEADER_NAX_LEN" = 5, 94 | "HEADER_NAX_CTR_LEN" = 6, 95 | "HEADER_NAX_FILL2_LEN" = 7, 96 | "HEADER_NAX_CTR_FILL_LEN" = 8 97 | }; 98 | enum { 99 | "PACKET_ALIGNMENT_8" = 0, 100 | "PACKET_ALIGNMENT_16" = 1, 101 | "PACKET_ALIGNMENT_32" = 2 102 | }; 103 | taggedunion { 104 | block "INITIAL_CMD_BUFFER" struct buffer; 105 | }; 106 | taggedunion { 107 | block "INITIAL_RES_ERR_BUFFER" struct buffer; 108 | }; 109 | taggedstruct { 110 | (block "POOL_BUFFER" struct buffer)*; 111 | }; 112 | }; 113 | /************************* end of FLX ***********************************/ 114 | -------------------------------------------------------------------------------- /pyxcp/aml/XCPonSxI.aml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/aml/XCPonSxI.aml -------------------------------------------------------------------------------- /pyxcp/aml/XCPonUSB.aml: -------------------------------------------------------------------------------- 1 | /****************************************************************************/ 2 | /* */ 3 | /* ASAP2 meta language for XCP on USB V1.0 */ 4 | /* Assumes ASAP2 V1.4 or later */ 5 | /* */ 6 | /* 2003-12-16 */ 7 | /* */ 8 | /* XCP on USB working group */ 9 | /* */ 10 | /* Datatypes: */ 11 | /* */ 12 | /* A2ML ASAP2 Windows description */ 13 | /* ------------------------------------------------------------------------ */ 14 | /* uchar UBYTE BYTE unsigned 8 Bit */ 15 | /* char SBYTE char signed 8 Bit */ 16 | /* uint UWORD WORD unsigned integer 16 Bit */ 17 | /* int SWORD int signed integer 16 Bit */ 18 | /* ulong ULONG DWORD unsigned integer 32 Bit */ 19 | /* long SLONG LONG signed integer 32 Bit */ 20 | /* float FLOAT32_IEEE float 32 Bit */ 21 | /****************************************************************************/ 22 | 23 | /begin A2ML 24 | 25 | /************************ start of USB **************************************/ 26 | struct ep_parameters { 27 | uchar; /* ENDPOINT_NUMBER, not endpoint address */ 28 | 29 | enum { 30 | "BULK_TRANSFER" = 2, /* Numbers according to USB spec. */ 31 | "INTERRUPT_TRANSFER" = 3 32 | }; 33 | 34 | uint; /* wMaxPacketSize: Maximum packet */ 35 | /* size of endpoint in bytes */ 36 | uchar; /* bInterval: polling of endpoint */ 37 | enum { /* Packing of XCP Messages */ 38 | "MESSAGE_PACKING_SINGLE" = 0, /* Single per USB data packet */ 39 | "MESSAGE_PACKING_MULTIPLE" = 1, /* Multiple per USB data packet */ 40 | "MESSAGE_PACKING_STREAMING" = 2 /* No restriction by packet sizes */ 41 | }; 42 | enum { /* Alignment mandatory for all */ 43 | "ALIGNMENT_8_BIT" = 0, /* packing types */ 44 | "ALIGNMENT_16_BIT"= 1, 45 | "ALIGNMENT_32_BIT"= 2, 46 | "ALIGNMENT_64_BIT"= 3 47 | }; 48 | taggedstruct { /* Optional */ 49 | "RECOMMENDED_HOST_BUFSIZE" uint; /* Recommended size for the host */ 50 | /* buffer size. The size is defined*/ 51 | /* as multiple of wMaxPacketSize. */ 52 | }; 53 | }; /* end of ep_parameters */ 54 | 55 | struct USB_Parameters { 56 | uint; /* XCP on USB version */ 57 | /* e.g. „1.0“ = 0x0100 */ 58 | uint; /* Vendor ID */ 59 | uint; /* Product ID */ 60 | uchar; /* Number of interface */ 61 | enum { 62 | "HEADER_LEN_BYTE" = 0, 63 | "HEADER_LEN_CTR_BYTE" = 1, 64 | "HEADER_LEN_FILL_BYTE" = 2, 65 | "HEADER_LEN_WORD" = 3, 66 | "HEADER_LEN_CTR_WORD" = 4, 67 | "HEADER_LEN_FILL_WORD" = 5 68 | }; 69 | /* OUT-EP for CMD and */ 70 | /* STIM (additional USB Endpoints may also be specified) */ 71 | taggedunion { 72 | block "OUT_EP_CMD_STIM" struct ep_parameters; 73 | }; 74 | /* IN-EP for RES/ERR, */ 75 | /* DAQ (additional USB Endpoints may also be specified) */ 76 | /* and EV/SERV (if not specified otherwise) */ 77 | taggedunion { 78 | block "IN_EP_RESERR_DAQ_EVSERV" struct ep_parameters; 79 | }; 80 | /* ----------- Begin of optional ------- */ 81 | taggedstruct { /* Optional */ 82 | "ALTERNATE_SETTING_NO" uchar; /* Number of alternate setting */ 83 | /* String Descriptor of XCP */ 84 | /* interface */ 85 | "INTERFACE_STRING_DESCRIPTOR" char [101]; 86 | /* multiple OUT-EP's for STIM */ 87 | (block "OUT_EP_ONLY_STIM" struct ep_parameters)*; 88 | /* multiple IN-EP's for DAQ */ 89 | (block "IN_EP_ONLY_DAQ" struct ep_parameters)*; 90 | /* only one IN-EP for EV/SERV */ 91 | block "IN_EP_ONLY_EVSERV" struct ep_parameters; 92 | /* Not associated DAQ-Lists are assigned per default to */ 93 | /* OUT_EP_CD_STIM / IN_EP_RESERR_DAQ_EVSERV */ 94 | (block "DAQ_LIST_USB_ENDPOINT" struct { 95 | uint; /* reference to DAQ_LIST_NUMBER */ 96 | taggedstruct { /* only mentioned if not VARIABLE */ 97 | "FIXED_IN" uchar; /* this DAQ list always */ 98 | /* ENDPOINT_NUMBER, not endpoint address */ 99 | "FIXED_OUT" uchar; /* this STIM list always */ 100 | /* ENDPOINT_NUMBER, not endpoint address */ 101 | }; 102 | })*; /* end of DAQ_LIST_USB_ENDPOINT */ 103 | }; /* end of optional */ 104 | }; 105 | /************************* end of USB ***********************************/ 106 | /end A2ML 107 | -------------------------------------------------------------------------------- /pyxcp/aml/ifdata_CAN.a2l: -------------------------------------------------------------------------------- 1 | begin XCP_ON_CAN 2 | 0x0100 /* XCP on CAN version */ 3 | CAN_ID_BROADCAST 0x0100 /* Broadcast */ 4 | CAN_ID_MASTER 0x0200 /* CMD/STIM */ 5 | CAN_ID_MASTER_INCREMENTAL 6 | CAN_ID_SLAVE 0x0300 /* RES/ERR/EV/SERV/DAQ */ 7 | BAUDRATE 500000 /* BAUDRATE */ 8 | /begin DAQ_LIST_CAN_ID 9 | 0x0000 /* for DAQ_LIST 0 */ 10 | FIXED 0x310 11 | /end DAQ_LIST_CAN_ID 12 | /begin DAQ_LIST_CAN_ID 13 | 0x0001 /* for DAQ_LIST 1 */ 14 | FIXED 0x320 15 | /end DAQ_LIST_CAN_ID 16 | /begin DAQ_LIST_CAN_ID 17 | 0x0002 /* for DAQ_LIST 2 */ 18 | FIXED 0x330 19 | /end DAQ_LIST_CAN_ID 20 | /end XCP_ON_CAN 21 | -------------------------------------------------------------------------------- /pyxcp/aml/ifdata_Eth.a2l: -------------------------------------------------------------------------------- 1 | /begin XCP_ON_TCP_IP 2 | 0x0100 /* XCP on TCP_IP version */ 3 | 0x5555 /* PORT */ 4 | "127.0.0.1" /* ADDRESS */ 5 | /end XCP_ON_TCP_IP 6 | 7 | /begin XCP_ON_UDP_IP 8 | 0x0100 /* XCP on UDP_IP version */ 9 | 0x5555 /* PORT */ 10 | "127.0.0.1" /* ADDRESS */ 11 | /end XCP_ON_UDP_IP 12 | -------------------------------------------------------------------------------- /pyxcp/aml/ifdata_Flx.a2l: -------------------------------------------------------------------------------- 1 | /begin XCP_ON_FLX 2 | 0x0100 /* XCP on FlexRay 1.0 */ 3 | 0x0019 /* T1_FLX [ms] */ 4 | "MyFlexRayNetwork.xml" /* FIBEX-file including CHI information */ 5 | "MyCluster" /* Cluster-ID */ 6 | 0x02 /* NAX */ 7 | HEADER_NAX_CTR_LEN 8 | PACKET_ALIGNMENT_8 9 | /begin INITIAL_CMD_BUFFER 10 | 0x01 /* FLX_BUF */ 11 | MAX_FLX_LEN_BUF FIXED 32 12 | /begin LPDU_ID 13 | FLX_SLOT_ID FIXED 0x07B 14 | OFFSET FIXED 0 15 | CYCLE_REPETITION FIXED 1 16 | CHANNEL FIXED A 17 | /end LPDU_ID 18 | /begin XCP_PACKET 19 | CMD FIXED 20 | RES_ERR NOT_ALLOWED 21 | EV_SERV NOT_ALLOWED 22 | DAQ NOT_ALLOWED 23 | STIM FIXED 24 | /end XCP_PACKET 25 | /end INITIAL_CMD_BUFFER 26 | /begin INITIAL_RES_ERR_BUFFER 27 | 0x02 /* FLX_BUF */ 28 | MAX_FLX_LEN_BUF FIXED 32 29 | /begin LPDU_ID 30 | FLX_SLOT_ID FIXED 0x07C 31 | OFFSET FIXED 1 32 | CYCLE_REPETITION FIXED 1 33 | CHANNEL FIXED A 34 | /end LPDU_ID 35 | /begin XCP_PACKET 36 | CMD NOT_ALLOWED 37 | RES_ERR FIXED 38 | EV_SERV FIXED 39 | DAQ FIXED 40 | STIM NOT_ALLOWED 41 | /end XCP_PACKET 42 | /end INITIAL_RES_ERR_BUFFER 43 | begin POOL_BUFFER 44 | 0x03 /* FLX_BUF */ 45 | MAX_FLX_LEN_BUF FIXED 32 46 | /begin LPDU_ID 47 | FLX_SLOT_ID VARIABLE INITIAL_VALUE 0x07D 48 | OFFSET FIXED 0 49 | CYCLE_REPETITION FIXED 1 50 | CHANNEL FIXED A 51 | /end LPDU_ID 52 | /begin XCP_PACKET 53 | CMD NOT_ALLOWED 54 | RES_ERR VARIABLE 55 | EV_SERV VARIABLE 56 | DAQ VARIABLE_INITIALISED 57 | STIM NOT_ALLOWED 58 | /end XCP_PACKET 59 | /end POOL_BUFFER 60 | /begin POOL_BUFFER 61 | 0x04 /* FLX_BUF */ 62 | MAX_FLX_LEN_BUF VARIABLE 64 63 | /begin LPDU_ID 64 | FLX_SLOT_ID FIXED 0x07E 65 | OFFSET VARIABLE 66 | CYCLE_REPETITION VARIABLE 67 | CHANNEL FIXED A 68 | /end LPDU_ID 69 | /begin XCP_PACKET 70 | CMD VARIABLE 71 | RES_ERR VARIABLE 72 | EV_SERV VARIABLE 73 | DAQ VARIABLE 74 | STIM VARIABLE 75 | /end XCP_PACKET 76 | /end POOL_BUFFER 77 | /begin POOL_BUFFER 78 | 0x05 /* FLX_BUF */ 79 | MAX_FLX_LEN_BUF VARIABLE 64 80 | /begin LPDU_ID 81 | FLX_SLOT_ID VARIABLE 82 | OFFSET VARIABLE 83 | CYCLE_REPETITION VARIABLE 84 | CHANNEL VARIABLE 85 | /end LPDU_ID 86 | /begin XCP_PACKET 87 | CMD VARIABLE 88 | RES_ERR VARIABLE 89 | EV_SERV VARIABLE 90 | DAQ VARIABLE 91 | STIM VARIABLE 92 | /end XCP_PACKET 93 | /end POOL_BUFFER 94 | /end XCP_ON_FLX 95 | -------------------------------------------------------------------------------- /pyxcp/aml/ifdata_SxI.a2l: -------------------------------------------------------------------------------- 1 | /begin XCP_ON_SxI 2 | 0x0100 /* XCP on SxI version */ 3 | 25000 /* BAUDRATE */ 4 | ASYNCH_FULL_DUPLEX_MODE 5 | PARITY_ODD 6 | TWO_STOP_BITS 7 | /begin FRAMING 8 | 0x9A /* SYNC */ 9 | 0x9B /* ESC */ 10 | /end FRAMING 11 | HEADER_LEN_CTR_WORD 12 | NO_CHECKSUM 13 | /end XCP_ON_SxI 14 | -------------------------------------------------------------------------------- /pyxcp/aml/ifdata_USB.a2l: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/aml/ifdata_USB.a2l -------------------------------------------------------------------------------- /pyxcp/asam/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/asam/__init__.py -------------------------------------------------------------------------------- /pyxcp/asam/types.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import struct 3 | 4 | 5 | INTEL = "<" 6 | MOTOROLA = ">" 7 | 8 | """ 9 | A_VOID: pseudo type for non-existing elements 10 | A_BIT: one bit 11 | A_ASCIISTRING: string, ISO-8859-1 encoded 12 | A_UTF8STRING: string, UTF-8 encoded 13 | A_UNICODE2STRING: string, UCS-2 encoded 14 | A_BYTEFIELD: Field of bytes 15 | """ 16 | 17 | 18 | class AsamBaseType: 19 | """Base class for ASAM codecs. 20 | 21 | Note 22 | ---- 23 | Always use derived classes. 24 | """ 25 | 26 | def __init__(self, byteorder): 27 | """ 28 | 29 | Parameters 30 | ---------- 31 | byteorder: char {'<', '>'} 32 | - '<' Little-endian 33 | - '>' Big-endian 34 | """ 35 | if byteorder not in ("<", ">"): 36 | raise ValueError("Invalid byteorder.") 37 | self.byteorder = byteorder 38 | 39 | def encode(self, value): 40 | """Encode a value. 41 | 42 | Encode means convert a value, eg. an integer, to a byte-string. 43 | 44 | Parameters 45 | ---------- 46 | value: data-type 47 | data-type is determined by derived class. 48 | 49 | Returns 50 | ------- 51 | bytes 52 | Encoded value. 53 | """ 54 | return struct.pack(f"{self.byteorder}{self.FMT}", value) 55 | 56 | def decode(self, value): 57 | """Decode a value. 58 | 59 | Decode means convert a byte-string to a meaningful data-type, eg. an 60 | integer. 61 | 62 | Parameters 63 | ---------- 64 | value: bytes 65 | 66 | Returns 67 | ------- 68 | data-type 69 | data-type is determined by derived class. 70 | """ 71 | return struct.unpack(f"{self.byteorder}{self.FMT}", bytes(value))[0] 72 | 73 | 74 | class A_Uint8(AsamBaseType): 75 | """ASAM A_UINT8 codec.""" 76 | 77 | FMT = "B" 78 | 79 | 80 | class A_Uint16(AsamBaseType): 81 | """ASAM A_UINT16 codec.""" 82 | 83 | FMT = "H" 84 | 85 | 86 | class A_Uint32(AsamBaseType): 87 | """ASAM A_UINT32 codec.""" 88 | 89 | FMT = "I" 90 | 91 | 92 | class A_Uint64(AsamBaseType): 93 | """ASAM A_UINT64 codec.""" 94 | 95 | FMT = "Q" 96 | 97 | 98 | class A_Int8(AsamBaseType): 99 | """ASAM A_INT8 codec.""" 100 | 101 | FMT = "b" 102 | 103 | 104 | class A_Int16(AsamBaseType): 105 | """ASAM A_INT16 codec.""" 106 | 107 | FMT = "h" 108 | 109 | 110 | class A_Int32(AsamBaseType): 111 | """ASAM A_INT32 codec.""" 112 | 113 | FMT = "i" 114 | 115 | 116 | class A_Int64(AsamBaseType): 117 | """ASAM A_INT64 codec.""" 118 | 119 | FMT = "q" 120 | 121 | 122 | class A_Float32(AsamBaseType): 123 | """ASAM A_FLOAT32 codec.""" 124 | 125 | FMT = "f" 126 | 127 | 128 | class A_Float64(AsamBaseType): 129 | """ASAM A_FLOAT64 codec.""" 130 | 131 | FMT = "d" 132 | -------------------------------------------------------------------------------- /pyxcp/asamkeydll.c: -------------------------------------------------------------------------------- 1 | 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #if defined(_WIN32) 9 | #include 10 | 11 | #define LOAD_LIB(name) LoadLibrary((name)) 12 | #define GET_SYM(module, sym) GetProcAddress((module), (sym)) 13 | 14 | #else 15 | 16 | #define _GNU_SOURCE 17 | #include 18 | 19 | typedef uint8_t BYTE; 20 | typedef uint32_t DWORD; 21 | typedef void * HANDLE; 22 | 23 | #define LOAD_LIB(name) dlopen((name), RTLD_LAZY) 24 | #define GET_SYM(module, sym) dlsym((module), (sym)) 25 | 26 | 27 | #endif 28 | 29 | 30 | 31 | 32 | #define NP_BUFSIZE (4096) 33 | #define KEY_BUFSIZE (255) 34 | 35 | #define ERR_OK (0) 36 | 37 | #define ERR_INVALID_CMD_LINE (2) 38 | 39 | #define ERR_COULD_NOT_LOAD_DLL (16) 40 | #define ERR_COULD_NOT_LOAD_FUNC (17) 41 | 42 | 43 | char dllname[NP_BUFSIZE] = {0}; 44 | 45 | DWORD GetKey(char * const dllName, BYTE privilege, BYTE lenSeed, BYTE * seed, BYTE * lenKey, BYTE * key); 46 | void hexlify(uint8_t const * const buf, uint16_t len); 47 | 48 | typedef DWORD (*XCP_GetAvailablePrivilegesType)(BYTE * privilege); 49 | typedef DWORD (*XCP_ComputeKeyFromSeedType)(BYTE privilege, BYTE lenSeed, BYTE *seed, BYTE * lenKey, BYTE * key); 50 | 51 | 52 | uint8_t keyBuffer[KEY_BUFSIZE] = {0}; 53 | uint8_t seedBuffer[KEY_BUFSIZE] = {0}; 54 | char nameBuffer[KEY_BUFSIZE] = {0}; 55 | uint8_t keylen = KEY_BUFSIZE; 56 | uint8_t seedlen = 0; 57 | 58 | 59 | void hexlify(uint8_t const * const buf, uint16_t len) 60 | { 61 | for (uint16_t idx = 0; idx < len; ++idx) { 62 | printf("%02X", buf[idx]); 63 | } 64 | } 65 | 66 | DWORD GetKey(char * const dllName, BYTE privilege, BYTE lenSeed, BYTE * seed, BYTE * lenKey, BYTE * key) 67 | { 68 | HANDLE hModule = LOAD_LIB(dllName); 69 | XCP_ComputeKeyFromSeedType XCP_ComputeKeyFromSeed; 70 | 71 | if (hModule != NULL) { 72 | XCP_ComputeKeyFromSeed = (XCP_ComputeKeyFromSeedType)GET_SYM(hModule, "XCP_ComputeKeyFromSeed"); 73 | //printf("fp: %p\n", XCP_ComputeKeyFromSeed); 74 | if (XCP_ComputeKeyFromSeed != NULL) { 75 | return XCP_ComputeKeyFromSeed(privilege, lenSeed, seed, lenKey, key); 76 | } else { 77 | return ERR_COULD_NOT_LOAD_FUNC; 78 | } 79 | } else { 80 | return ERR_COULD_NOT_LOAD_DLL; 81 | } 82 | return ERR_OK; 83 | } 84 | 85 | 86 | 87 | int main(int argc, char ** argv) 88 | { 89 | BYTE privilege = 0; 90 | int idx; 91 | DWORD res; 92 | char cbuf[3] = {0}; 93 | 94 | for (idx = 1; idx < argc; ++idx) { 95 | if (idx == 1) { 96 | strcpy(dllname, argv[idx]); 97 | } else if (idx == 2) { 98 | privilege = atoi(argv[idx]); 99 | } else if (idx == 3) { 100 | strcpy(nameBuffer, argv[idx]); 101 | } 102 | } 103 | 104 | seedlen = strlen(nameBuffer) >> 1; 105 | for (idx = 0; idx < seedlen; ++idx) { 106 | cbuf[0] = nameBuffer[idx * 2]; 107 | cbuf[1] = nameBuffer[(idx * 2) + 1 ]; 108 | cbuf[2] = '\x00'; 109 | seedBuffer[idx] = strtol(cbuf, 0, 16); 110 | } 111 | 112 | res = GetKey((char *)&dllname, privilege, seedlen, (BYTE *)&seedBuffer, &keylen, (BYTE *)&keyBuffer); 113 | if (res == 0) { 114 | hexlify(keyBuffer, keylen); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /pyxcp/asamkeydll.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | gcc -O3 -Wall asamkeydll.c -ldl -o asamkeydll 3 | -------------------------------------------------------------------------------- /pyxcp/cmdline.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Parse (transport-layer specific) command line parameters 4 | and create a XCP master instance. 5 | """ 6 | 7 | import warnings 8 | from dataclasses import dataclass 9 | from typing import List 10 | 11 | from pyxcp.config import create_application, reset_application # noqa: F401 12 | from pyxcp.master import Master 13 | 14 | 15 | warnings.simplefilter("always") 16 | 17 | 18 | @dataclass 19 | class Option: 20 | short_opt: str 21 | long_opt: str = "" 22 | dest: str = "" 23 | help: str = "" 24 | type: str = "" 25 | default: str = "" 26 | 27 | 28 | class FakeParser: 29 | 30 | options: List[Option] = [] 31 | 32 | def add_argument(self, short_opt: str, long_opt: str = "", dest: str = "", help: str = "", type: str = "", default: str = ""): 33 | warnings.warn("Argument parser extension is currently not supported.", DeprecationWarning, 2) 34 | self.options.append(Option(short_opt, long_opt, dest, help, type, default)) 35 | 36 | 37 | class ArgumentParser: 38 | def __init__(self, callout=None, *args, **kws): 39 | self._parser = FakeParser() 40 | if callout is not None: 41 | warnings.warn("callout argument is currently not supported.", DeprecationWarning, 2) 42 | 43 | def run(self, policy=None, transport_layer_interface=None): 44 | application = create_application(self.parser.options) 45 | master = Master( 46 | application.transport.layer, config=application, policy=policy, transport_layer_interface=transport_layer_interface 47 | ) 48 | return master 49 | 50 | @property 51 | def parser(self): 52 | return self._parser 53 | -------------------------------------------------------------------------------- /pyxcp/config/legacy.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | from traitlets.config import LoggingConfigurable 4 | from traitlets.config.loader import Config 5 | 6 | 7 | LEGACY_KEYWORDS = { 8 | # General 9 | "LOGLEVEL": "General.loglevel", 10 | "DISABLE_ERROR_HANDLING": "General.disable_error_handling", 11 | "SEED_N_KEY_DLL": "General.seed_n_key_dll", 12 | "SEED_N_KEY_DLL_SAME_BIT_WIDTH": "General.seed_n_key_dll_same_bit_width", 13 | "DISCONNECT_RESPONSE_OPTIONAL": "General.disconnect_response_optional", 14 | # Transport 15 | "TRANSPORT": "Transport.layer", 16 | "CREATE_DAQ_TIMESTAMPS": "Transport.create_daq_timestamps", 17 | "TIMEOUT": "Transport.timeout", 18 | "ALIGNMENT": "Transport.alignment", 19 | # Eth 20 | "HOST": "Transport.Eth.host", 21 | "PORT": "Transport.Eth.port", 22 | "PROTOCOL": "Transport.Eth.protocol", 23 | "IPV6": "Transport.Eth.ipv6", 24 | "TCP_NODELAY": "Transport.Eth.tcp_nodelay", 25 | # Usb 26 | "SERIAL_NUMBER": "Transport.Usb.serial_number", 27 | "CONFIGURATION_NUMBER": "Transport.Usb.configuration_number", 28 | "INTERFACE_NUMBER": "Transport.Usb.interface_number", 29 | "COMMAND_ENDPOINT_NUMBER": "Transport.Usb.out_ep_number", 30 | "REPLY_ENDPOINT_NUMBER": "Transport.Usb.in_ep_number", 31 | "VENDOR_ID": "Transport.Usb.vendor_id", 32 | "PRODUCT_ID": "Transport.Usb.product_id", 33 | "LIBRARY": "Transport.Usb.library", 34 | # Can 35 | "CAN_DRIVER": "Transport.Can.interface", 36 | "CHANNEL": "Transport.Can.channel", 37 | "MAX_DLC_REQUIRED": "Transport.Can.max_dlc_required", 38 | # "MAX_CAN_FD_DLC": "Transport.Can.max_can_fd_dlc", 39 | "PADDING_VALUE": "Transport.Can.padding_value", 40 | "CAN_USE_DEFAULT_LISTENER": "Transport.Can.use_default_listener", 41 | # Swap master and slave IDs. (s. https://github.com/christoph2/pyxcp/issues/130) 42 | "CAN_ID_SLAVE": "Transport.Can.can_id_master", 43 | "CAN_ID_MASTER": "Transport.Can.can_id_slave", 44 | "CAN_ID_BROADCAST": "Transport.Can.can_id_broadcast", 45 | "BITRATE": "Transport.Can.bitrate", 46 | "RECEIVE_OWN_MESSAGES": "Transport.Can.receive_own_messages", 47 | "POLL_INTERVAL": "Transport.Can.poll_interval", 48 | "FD": "Transport.Can.fd", 49 | "DATA_BITRATE": "Transport.Can.data_bitrate", 50 | "ACCEPT_VIRTUAL": "Transport.Can.Kvaser.accept_virtual", 51 | "SJW": "Transport.Can.sjw_abr", 52 | "TSEG1": "Transport.Can.tseg1_abr", 53 | "TSEG2": "Transport.Can.tseg2_abr", 54 | "TTY_BAUDRATE": "Transport.Can.SlCan.ttyBaudrate", 55 | "UNIQUE_HARDWARE_ID": "Transport.Can.Ixxat.unique_hardware_id", 56 | "RX_FIFO_SIZE": "Transport.Can.Ixxat.rx_fifo_size", 57 | "TX_FIFO_SIZE": "Transport.Can.Ixxat.tx_fifo_size", 58 | "DRIVER_MODE": "Transport.Can.Kvaser.driver_mode", 59 | "NO_SAMP": "Transport.Can.Kvaser.no_samp", 60 | "SINGLE_HANDLE": "Transport.Can.Kvaser.single_handle", 61 | "USE_SYSTEM_TIMESTAMP": "Transport.Can.Neovi.use_system_timestamp", 62 | "OVERRIDE_LIBRARY_NAME": "Transport.Can.Neovi.override_library_name", 63 | "BAUDRATE": "Transport.Can.Serial.baudrate", 64 | "SLEEP_AFTER_OPEN": "Transport.Can.SlCan.sleep_after_open", 65 | "DEVICE_NUMBER": "Transport.Can.Systec.device_number", 66 | "RX_BUFFER_ENTRIES": "Transport.Can.Systec.rx_buffer_entries", 67 | "TX_BUFFER_ENTRIES": "Transport.Can.Systec.tx_buffer_entries", 68 | "FLAGS": "Transport.Can.Usb2Can.flags", 69 | "APP_NAME": "Transport.Can.Vector.app_name", 70 | "RX_QUEUE_SIZE": "Transport.Can.Vector.rx_queue_size", 71 | } 72 | 73 | 74 | def nested_dict_update(d: dict, key: str, value) -> None: 75 | root, *path, key = key.split(".") 76 | sub_dict = d[root] 77 | for part in path: 78 | if part not in sub_dict: 79 | sub_dict[part] = defaultdict(dict) 80 | sub_dict = sub_dict[part] 81 | sub_dict[key] = value 82 | 83 | 84 | def convert_config(legacy_config: dict, logger: LoggingConfigurable) -> Config: 85 | interface_name = None 86 | resolv = [] 87 | d = defaultdict(dict) 88 | for key, value in legacy_config.items(): 89 | key = key.upper() 90 | item = LEGACY_KEYWORDS.get(key) 91 | if item is None: 92 | logger.warning(f"Unknown keyword {key!r} in config file") 93 | continue 94 | if key == "CAN_DRIVER": 95 | value = value.lower() 96 | interface_name = value 97 | if key in ("SERIAL", "LOG_ERRORS", "STATE", "RTSCTS"): 98 | resolv.append((key, value)) 99 | else: 100 | nested_dict_update(d=d, key=item, value=value) 101 | for key, value in resolv: 102 | if key == "SERIAL": 103 | if interface_name == "neovi": 104 | d["Transport.Can.Neovi.serial"] = value 105 | elif interface_name == "vector": 106 | d["Transport.Can.Vector.serial"] = value 107 | elif key == "LOG_ERRORS": 108 | if interface_name == "nican": 109 | d["Transport.Can.NiCan.log_errors"] = value 110 | elif key == "STATE": 111 | if interface_name == "pcan": 112 | d["Transport.Can.PCan.state"] = value 113 | elif interface_name == "systec": 114 | d["Transport.Can.Systec.state"] = value 115 | elif key == "RTSCTS": 116 | if interface_name == "serial": 117 | d["Transport.Can.Serial.rtscts"] = value 118 | elif interface_name == "slcan": 119 | d["Transport.Can.SlCan.rtscts"] = value 120 | return Config(d) 121 | -------------------------------------------------------------------------------- /pyxcp/constants.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import struct 3 | from typing import Any, Callable 4 | 5 | 6 | PackerType = Callable[[int], bytes] 7 | UnpackerType = Callable[[bytes], tuple[Any, ...]] 8 | 9 | 10 | def makeBytePacker(byteorder: str = "@") -> PackerType: 11 | """""" 12 | return struct.Struct(f"{byteorder}B").pack 13 | 14 | 15 | def makeByteUnpacker(byteorder: str = "@") -> UnpackerType: 16 | """""" 17 | return struct.Struct(f"{byteorder}B").unpack 18 | 19 | 20 | def makeWordPacker(byteorder: str = "@") -> PackerType: 21 | """""" 22 | return struct.Struct(f"{byteorder}H").pack 23 | 24 | 25 | def makeWordUnpacker(byteorder: str = "@") -> UnpackerType: 26 | """""" 27 | return struct.Struct(f"{byteorder}H").unpack 28 | 29 | 30 | def makeDWordPacker(byteorder: str = "@") -> PackerType: 31 | """""" 32 | return struct.Struct(f"{byteorder}I").pack 33 | 34 | 35 | def makeDWordUnpacker(byteorder: str = "@") -> UnpackerType: 36 | """""" 37 | return struct.Struct(f"{byteorder}I").unpack 38 | 39 | 40 | def makeDLongPacker(byteorder: str = "@") -> PackerType: 41 | """""" 42 | return struct.Struct(f"{byteorder}Q").pack 43 | 44 | 45 | def makeDLongUnpacker(byteorder: str = "@") -> UnpackerType: 46 | """""" 47 | return struct.Struct(f"{byteorder}Q").unpack 48 | -------------------------------------------------------------------------------- /pyxcp/cpp_ext/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/cpp_ext/__init__.py -------------------------------------------------------------------------------- /pyxcp/cpp_ext/bin.hpp: -------------------------------------------------------------------------------- 1 | 2 | #if !defined(__BIN_HPP) 3 | #define __BIN_HPP 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "mcobject.hpp" 13 | 14 | class Bin { 15 | public: 16 | 17 | Bin(std::uint16_t size) : m_size(size), m_residual_capacity(size) { 18 | } 19 | 20 | Bin(std::uint16_t size, uint16_t residual_capacity, const std::vector& entries) : 21 | m_size(size), m_residual_capacity(residual_capacity), m_entries(entries) { 22 | } 23 | 24 | void append(const McObject& bin) { 25 | m_entries.emplace_back(bin); 26 | } 27 | 28 | void set_entries(std::vector&& entries) { 29 | m_entries = std::move(entries); 30 | } 31 | 32 | std::uint16_t get_size() const { 33 | return m_size; 34 | } 35 | 36 | void set_size(const std::uint16_t size) { 37 | m_size = size; 38 | } 39 | 40 | std::uint16_t get_residual_capacity() const { 41 | return m_residual_capacity; 42 | } 43 | 44 | void set_residual_capacity(const std::uint16_t residual_capacity) { 45 | m_residual_capacity = residual_capacity; 46 | } 47 | 48 | const std::vector& get_entries() const { 49 | return m_entries; 50 | } 51 | 52 | bool operator==(const Bin& other) const { 53 | return (m_size == other.m_size) && (m_residual_capacity == other.m_residual_capacity) && (m_entries == other.m_entries); 54 | } 55 | 56 | std::string dumps() const { 57 | std::stringstream ss; 58 | 59 | ss << to_binary(m_size); 60 | ss << to_binary(m_residual_capacity); 61 | 62 | std::size_t entries_size = m_entries.size(); 63 | ss << to_binary(entries_size); 64 | for (const auto& entry : m_entries) { 65 | ss << entry.dumps(); 66 | } 67 | 68 | return ss.str(); 69 | } 70 | 71 | private: 72 | 73 | std::uint16_t m_size; 74 | std::uint16_t m_residual_capacity; 75 | std::vector m_entries{}; 76 | }; 77 | 78 | std::string bin_entries_to_string(const std::vector& entries); 79 | 80 | std::string to_string(const Bin& obj) { 81 | std::stringstream ss; 82 | 83 | ss << "Bin(residual_capacity=" << obj.get_residual_capacity() << ", entries=[" << bin_entries_to_string(obj.get_entries()) 84 | << "])"; 85 | return ss.str(); 86 | } 87 | 88 | std::string bin_entries_to_string(const std::vector& entries) { 89 | std::stringstream ss; 90 | 91 | for (const auto& entry : entries) { 92 | ss << to_string(entry) << ",\n "; 93 | } 94 | return ss.str(); 95 | } 96 | 97 | #if 0 98 | 99 | @property 100 | def __len__(self) -> int: 101 | return len(self.entries) 102 | #endif 103 | 104 | #endif // __BIN_HPP 105 | -------------------------------------------------------------------------------- /pyxcp/cpp_ext/blockmem.hpp: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __BLOCKMEM_HPP 3 | #define __BLOCKMEM_HPP 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | /* 10 | * 11 | * Super simplicistic block memory manager. 12 | * 13 | */ 14 | template 15 | class BlockMemory { 16 | public: 17 | 18 | using mem_block_t = std::array; 19 | 20 | constexpr explicit BlockMemory() noexcept : m_memory{ nullptr }, m_allocation_count{ 0 } { 21 | m_memory = new T[_IS * _NB]; 22 | } 23 | 24 | ~BlockMemory() noexcept { 25 | if (m_memory) { 26 | delete[] m_memory; 27 | } 28 | } 29 | 30 | BlockMemory(const BlockMemory&) = delete; 31 | 32 | constexpr T* acquire() noexcept { 33 | const std::scoped_lock lock(m_mtx); 34 | 35 | if (m_allocation_count >= _NB) { 36 | return nullptr; 37 | } 38 | T* ptr = reinterpret_cast(m_memory + (m_allocation_count * _IS)); 39 | m_allocation_count++; 40 | return ptr; 41 | } 42 | 43 | constexpr void release() noexcept { 44 | const std::scoped_lock lock(m_mtx); 45 | if (m_allocation_count == 0) { 46 | return; 47 | } 48 | m_allocation_count--; 49 | } 50 | 51 | private: 52 | 53 | T* m_memory; 54 | std::uint32_t m_allocation_count; 55 | std::mutex m_mtx; 56 | }; 57 | 58 | #endif // __BLOCKMEM_HPP 59 | -------------------------------------------------------------------------------- /pyxcp/cpp_ext/event.hpp: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __EVENT_HPP 3 | #define __EVENT_HPP 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | class Event { 10 | public: 11 | 12 | Event(const Event& other) noexcept { 13 | std::scoped_lock lock(other.m_mtx); 14 | m_flag = other.m_flag; 15 | } 16 | 17 | ~Event() = default; 18 | Event() = default; 19 | 20 | void signal() noexcept { 21 | std::scoped_lock lock(m_mtx); 22 | m_flag = true; 23 | m_cond.notify_one(); 24 | } 25 | 26 | void wait() noexcept { 27 | std::unique_lock lock(m_mtx); 28 | m_cond.wait(lock, [this] { return m_flag; }); 29 | m_flag = false; 30 | } 31 | 32 | bool state() const noexcept { 33 | std::scoped_lock lock(m_mtx); 34 | return m_flag; 35 | } 36 | 37 | private: 38 | 39 | mutable std::mutex m_mtx{}; 40 | bool m_flag{ false }; 41 | std::condition_variable m_cond{}; 42 | }; 43 | 44 | #if 0 45 | class Spinlock { 46 | public: 47 | 48 | Spinlock() : m_flag(ATOMIC_FLAG_INIT) { 49 | } 50 | 51 | ~Spinlock() = default; 52 | 53 | void lock() { 54 | while (m_flag.test_and_set()) { 55 | } 56 | } 57 | 58 | void unlock() { 59 | m_flag.clear(); 60 | } 61 | 62 | private: 63 | std::atomic_flag m_flag; 64 | }; 65 | #endif 66 | 67 | #endif // __EVENT_HPP 68 | -------------------------------------------------------------------------------- /pyxcp/cpp_ext/extension_wrapper.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | #include "bin.hpp" 11 | #include "daqlist.hpp" 12 | #include "mcobject.hpp" 13 | 14 | namespace py = pybind11; 15 | using namespace pybind11::literals; 16 | 17 | class PyTimestampInfo : public TimestampInfo { 18 | public: 19 | 20 | using TimestampInfo::TimestampInfo; 21 | }; 22 | 23 | PYBIND11_MODULE(cpp_ext, m) { 24 | m.doc() = "C++ extensions for pyXCP."; 25 | 26 | //m.def("sleep_ms", &sleep_ms, "milliseconds"_a); 27 | //m.def("sleep_ns", &sleep_ns, "nanoseconds"_a); 28 | 29 | py::class_(m, "McObject") 30 | .def( 31 | py::init< 32 | std::string_view, std::uint32_t, std::uint8_t, std::uint16_t, const std::string&, const std::vector&>(), 33 | "name"_a, "address"_a, "ext"_a, "length"_a, "data_type"_a = "", "components"_a = std::vector() 34 | ) 35 | .def_property("name", &McObject::get_name, &McObject::set_name) 36 | .def_property("address", &McObject::get_address, &McObject::set_address) 37 | .def_property("ext", &McObject::get_ext, &McObject::set_ext) 38 | .def_property("length", &McObject::get_length, &McObject::set_length) 39 | .def_property("data_type", &McObject::get_data_type, &McObject::set_data_type) 40 | .def_property_readonly("components", &McObject::get_components) 41 | 42 | .def("add_component", &McObject::add_component, "component"_a) 43 | .def("__repr__", [](const McObject& self) { return to_string(self); }) 44 | .def("__hash__", [](const McObject& self) { return self.get_hash(); }) 45 | ; 46 | 47 | py::class_(m, "Bin") 48 | .def(py::init(), "size"_a) 49 | .def_property("size", &Bin::get_size, &Bin::set_size) 50 | .def_property("residual_capacity", &Bin::get_residual_capacity, &Bin::set_residual_capacity) 51 | .def_property("entries", &Bin::get_entries, nullptr) 52 | .def("append", &Bin::append) 53 | 54 | .def("__repr__", [](const Bin& self) { return to_string(self); }) 55 | 56 | .def("__eq__", [](const Bin& self, const Bin& other) { return self == other; }) 57 | 58 | .def("__len__", [](const Bin& self) { return std::size(self.get_entries()); }); 59 | 60 | py::class_(m, "DaqList") 61 | .def( 62 | py::init&, 63 | std::uint8_t, std::uint8_t>(), "name"_a, "event_num"_a, "stim"_a, "enable_timestamps"_a, "measurements"_a, 64 | "priority"_a=0, "prescaler"_a=1 65 | ) 66 | .def("__repr__", [](const DaqList& self) { return self.to_string(); }) 67 | .def_property("name", &DaqList::get_name, nullptr) 68 | .def_property("event_num", &DaqList::get_event_num, &DaqList::set_event_num) 69 | .def_property("priority", &DaqList::get_priority, nullptr) 70 | .def_property("prescaler", &DaqList::get_prescaler, nullptr) 71 | .def_property("stim", &DaqList::get_stim, nullptr) 72 | .def_property("enable_timestamps", &DaqList::get_enable_timestamps, nullptr) 73 | .def_property("measurements", &DaqList::get_measurements, nullptr) 74 | .def_property("measurements_opt", &DaqList::get_measurements_opt, &DaqList::set_measurements_opt) 75 | .def_property("headers", &DaqList::get_headers, nullptr) 76 | .def_property("odt_count", &DaqList::get_odt_count, nullptr) 77 | .def_property("total_entries", &DaqList::get_total_entries, nullptr) 78 | .def_property("total_length", &DaqList::get_total_length, nullptr); 79 | 80 | py::enum_(m, "TimestampType") 81 | .value("ABSOLUTE_TS", TimestampType::ABSOLUTE_TS) 82 | .value("RELATIVE_TS", TimestampType::RELATIVE_TS); 83 | 84 | py::class_(m, "Timestamp") 85 | .def(py::init(), "ts_type"_a) 86 | .def_property_readonly("absolute", &Timestamp::absolute) 87 | .def_property_readonly("relative", &Timestamp::relative) 88 | .def_property_readonly("value", &Timestamp::get_value) 89 | .def_property_readonly("initial_value", &Timestamp::get_initial_value); 90 | 91 | py::class_(m, "TimestampInfo", py::dynamic_attr()) 92 | .def(py::init()) 93 | .def(py::init()) 94 | 95 | .def_property_readonly("timestamp_ns", &TimestampInfo::get_timestamp_ns) 96 | .def_property("utc_offset", &TimestampInfo::get_utc_offset, &TimestampInfo::set_utc_offset) 97 | .def_property("dst_offset", &TimestampInfo::get_dst_offset, &TimestampInfo::set_dst_offset) 98 | .def_property("timezone", &TimestampInfo::get_timezone, &TimestampInfo::set_timezone); 99 | } 100 | -------------------------------------------------------------------------------- /pyxcp/cpp_ext/tsqueue.hpp: -------------------------------------------------------------------------------- 1 | 2 | #ifndef __TSQUEUE_HPP 3 | #define __TSQUEUE_HPP 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | template 10 | class TsQueue { 11 | public: 12 | 13 | TsQueue() = default; 14 | 15 | TsQueue(const TsQueue& other) noexcept { 16 | std::scoped_lock lock(other.m_mtx); 17 | m_queue = other.m_queue; 18 | } 19 | 20 | void put(T value) noexcept { 21 | std::scoped_lock lock(m_mtx); 22 | m_queue.push(value); 23 | m_cond.notify_one(); 24 | } 25 | 26 | std::shared_ptr get() noexcept { 27 | std::unique_lock lock(m_mtx); 28 | m_cond.wait(lock, [this] { return !m_queue.empty(); }); 29 | std::shared_ptr result(std::make_shared(m_queue.front())); 30 | m_queue.pop(); 31 | return result; 32 | } 33 | 34 | bool empty() const noexcept { 35 | std::scoped_lock lock(m_mtx); 36 | return m_queue.empty(); 37 | } 38 | 39 | private: 40 | 41 | mutable std::mutex m_mtx; 42 | std::queue m_queue; 43 | std::condition_variable m_cond; 44 | }; 45 | 46 | #endif // __TSQUEUE_HPP 47 | -------------------------------------------------------------------------------- /pyxcp/daq_stim/optimize/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Optimize data-structures like memory sections.""" 3 | 4 | from itertools import groupby 5 | from operator import attrgetter 6 | from typing import List 7 | 8 | from pyxcp.cpp_ext.cpp_ext import McObject 9 | 10 | 11 | def make_continuous_blocks(chunks: List[McObject], upper_bound=None, upper_bound_initial=None) -> List[McObject]: 12 | """Try to make continous blocks from a list of small, unordered `chunks`. 13 | 14 | Parameters 15 | ---------- 16 | chunks: list of `McObject` 17 | 18 | Returns 19 | ------- 20 | sorted list of `McObject` 21 | """ 22 | 23 | def key_func(x): 24 | return (x.ext, x.address) 25 | 26 | values = [] 27 | # 1. Groupy by address. 28 | for _key, value in groupby(sorted(chunks, key=key_func), key=key_func): 29 | # 2. Pick the largest one. 30 | values.append(max(value, key=attrgetter("length"))) 31 | result_sections = [] 32 | last_section = None 33 | last_ext = None 34 | first_section = True 35 | if upper_bound_initial is None: 36 | upper_bound_initial = upper_bound 37 | while values: 38 | section = values.pop(0) 39 | if (last_section and section.address <= last_section.address + last_section.length) and not (section.ext != last_ext): 40 | last_end = last_section.address + last_section.length - 1 41 | current_end = section.address + section.length - 1 42 | if last_end > section.address: 43 | pass 44 | else: 45 | offset = current_end - last_end 46 | if upper_bound: 47 | if first_section: 48 | upper_bound = upper_bound_initial 49 | first_section = False 50 | if last_section.length + offset <= upper_bound: 51 | last_section.length += offset 52 | last_section.add_component(section) 53 | else: 54 | result_sections.append( 55 | McObject(name="", address=section.address, ext=section.ext, length=section.length, components=[section]) 56 | ) 57 | else: 58 | last_section.length += offset 59 | last_section.add_component(section) 60 | else: 61 | # Create a new section. 62 | result_sections.append( 63 | McObject(name="", address=section.address, ext=section.ext, length=section.length, components=[section]) 64 | ) 65 | last_section = result_sections[-1] 66 | last_ext = last_section.ext 67 | return result_sections 68 | -------------------------------------------------------------------------------- /pyxcp/daq_stim/optimize/binpacking.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Bin-packing algorithms. 3 | """ 4 | from typing import List, Optional 5 | 6 | from pyxcp.cpp_ext.cpp_ext import Bin 7 | 8 | 9 | def first_fit_decreasing(items, bin_size: int, initial_bin_size: Optional[int] = None) -> List[Bin]: 10 | """bin-packing with first-fit-decreasing algorithm. 11 | 12 | Parameters 13 | ---------- 14 | items: list 15 | items that need to be stored/allocated. 16 | 17 | bin_size: int 18 | 19 | Returns 20 | ------- 21 | list 22 | Resulting bins 23 | """ 24 | if initial_bin_size is None: 25 | initial_bin_size = bin_size 26 | # bin_size = max(bin_size, initial_bin_size) 27 | bins = [Bin(size=initial_bin_size)] # Initial bin 28 | for item in sorted(items, key=lambda x: x.length, reverse=True): 29 | if item.length > bin_size: 30 | raise ValueError(f"Item {item!r} is too large to fit in a {bin_size} byte sized bin.") 31 | for bin in bins: 32 | if bin.residual_capacity >= item.length: 33 | bin.append(item) 34 | bin.residual_capacity -= item.length 35 | break 36 | else: 37 | new_bin = Bin(size=bin_size) 38 | bins.append(new_bin) 39 | new_bin.append(item) 40 | new_bin.residual_capacity -= item.length 41 | return bins 42 | -------------------------------------------------------------------------------- /pyxcp/daq_stim/scheduler.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "scheduler.hpp" 3 | 4 | #if defined(_WIN32) 5 | 6 | VOID CALLBACK TimerRoutine(PVOID lpParam, BOOLEAN TimerOrWaitFired) { 7 | if (lpParam == NULL) { 8 | printf("TimerRoutine lpParam is NULL\n"); 9 | } else { 10 | // lpParam points to the argument; in this case it is an int 11 | 12 | printf("Timer routine called. Parameter is %d.\n", *(int*)lpParam); 13 | if (TimerOrWaitFired) { 14 | printf("The wait timed out.\n"); 15 | } else { 16 | printf("The wait event was signaled.\n"); 17 | } 18 | } 19 | } 20 | 21 | #include 22 | 23 | void mul4_vectorized(float* ptr) { 24 | __m128 f = _mm_loadu_ps(ptr); 25 | f = _mm_mul_ps(f, f); 26 | _mm_storeu_ps(ptr, f); 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /pyxcp/daq_stim/scheduler.hpp: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef STIM_SCHEDULER_HPP 4 | #define STIM_SCHEDULER_HPP 5 | 6 | #if !defined(_CRT_SECURE_NO_WARNINGS) 7 | #define _CRT_SECURE_NO_WARNINGS (1) 8 | #endif 9 | 10 | #include 11 | 12 | #if defined(_WIN32) 13 | #include 14 | 15 | #include 16 | 17 | VOID CALLBACK TimerRoutine(PVOID lpParam, BOOLEAN TimerOrWaitFired); 18 | 19 | struct Scheduler { 20 | Scheduler() = default; 21 | ~Scheduler() = default; 22 | 23 | bool start_thread() noexcept { 24 | if (timer_thread.joinable()) { 25 | return false; 26 | } 27 | 28 | m_TimerQueue = CreateTimerQueue(); 29 | if (NULL == m_TimerQueue) { 30 | printf("CreateTimerQueue failed (%d)\n", GetLastError()); 31 | return false; 32 | } 33 | 34 | // Set a timer to call the timer routine in 10 seconds. 35 | if (!CreateTimerQueueTimer(&m_timer, m_TimerQueue, (WAITORTIMERCALLBACK)TimerRoutine, nullptr, 1, 500, 0)) { 36 | printf("CreateTimerQueueTimer failed (%d)\n", GetLastError()); 37 | return false; 38 | } 39 | 40 | stop_timer_thread_flag = false; 41 | timer_thread = std::jthread([this]() { 42 | while (!stop_timer_thread_flag) { 43 | printf("ENTER SLEEP loop!!!\n"); 44 | SleepEx(INFINITE, TRUE); 45 | stop_timer_thread_flag = TRUE; 46 | } 47 | }); 48 | return true; 49 | } 50 | 51 | bool stop_thread() noexcept { 52 | if (!timer_thread.joinable()) { 53 | return false; 54 | } 55 | stop_timer_thread_flag = true; 56 | // my_queue.put(std::nullopt); 57 | timer_thread.join(); 58 | return true; 59 | } 60 | 61 | std::jthread timer_thread{}; 62 | bool stop_timer_thread_flag{}; 63 | HANDLE m_timer{}; 64 | HANDLE m_TimerQueue; 65 | }; 66 | #else 67 | 68 | struct Scheduler { 69 | Scheduler() = default; 70 | ~Scheduler() = default; 71 | }; 72 | 73 | #endif 74 | 75 | #endif // STIM_SCHEDULER_HPP 76 | -------------------------------------------------------------------------------- /pyxcp/daq_stim/stim.cpp: -------------------------------------------------------------------------------- 1 | 2 | #if defined(_MSC_VER) 3 | #pragma comment(lib, "Winmm.lib") 4 | #pragma comment(lib, "Avrt.lib") 5 | #endif 6 | 7 | #include "stim.hpp" 8 | 9 | void make_dto() { 10 | } 11 | 12 | void init() { 13 | } 14 | -------------------------------------------------------------------------------- /pyxcp/daq_stim/stim_wrapper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | 7 | namespace py = pybind11; 8 | using namespace py::literals; 9 | 10 | #if defined(_MSC_VER) 11 | #pragma warning(disable: 4251 4273) 12 | #endif 13 | 14 | #include "stim.hpp" 15 | 16 | PYBIND11_MODULE(stim, m) { 17 | py::class_(m, "DaqEventInfo") 18 | .def(py::init(), 19 | "name"_a, "type_code"_a, "cycle"_a, "max_daq_lists"_a, "priority"_a, "consistency"_a, "daq_supported"_a, 20 | "stim_supported"_a, "packed_supported"_a 21 | ); 22 | 23 | py::class_(m, "Stim") 24 | .def(py::init()) 25 | .def("setDaqEventInfo", &Stim::setDaqEventInfo) 26 | .def("clear", &Stim::clear) 27 | .def("freeDaq", &Stim::freeDaq) 28 | .def("allocDaq", &Stim::allocDaq) 29 | .def("allocOdt", &Stim::allocOdt) 30 | .def("allocOdtEntry", &Stim::allocOdtEntry) 31 | .def("setDaqPtr", &Stim::setDaqPtr) 32 | .def("clearDaqList", &Stim::clearDaqList) 33 | .def("writeDaq", &Stim::writeDaq) 34 | .def("setDaqListMode", &Stim::setDaqListMode) 35 | .def("startStopDaqList", &Stim::startStopDaqList) 36 | .def("startStopSynch", &Stim::startStopSynch) 37 | 38 | .def("set_first_pid", &Stim::set_first_pid) 39 | .def("set_policy_feeder", [](Stim& self, const py::function& callback) { self.set_policy_feeder(callback); }) 40 | .def("set_frame_sender", [](Stim& self, const py::function& callback) { self.set_frame_sender(callback); }); 41 | 42 | py::class_(m, "FakeEnum") 43 | .def(py::init()) 44 | .def_property_readonly("name", &FakeEnum::get_name) 45 | .def_property_readonly("value", &FakeEnum::get_value) 46 | .def("bit_length", &FakeEnum::bit_length) 47 | .def("to_bytes", &FakeEnum::to_bytes) 48 | .def("__int__", [](const FakeEnum& self) { return self.get_value(); }); 49 | ; 50 | } 51 | -------------------------------------------------------------------------------- /pyxcp/dllif.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import binascii 3 | import ctypes 4 | import enum 5 | import platform 6 | import re 7 | import subprocess # nosec 8 | import sys 9 | from pathlib import Path 10 | 11 | 12 | class SeedNKeyResult(enum.IntEnum): 13 | ACK = 0 # o.k. 14 | ERR_PRIVILEGE_NOT_AVAILABLE = 1 # the requested privilege can not be unlocked with this DLL 15 | ERR_INVALID_SEED_LENGTH = 2 # the seed length is wrong, key could not be computed 16 | ERR_UNSUFFICIENT_KEY_LENGTH = 3 # the space for the key is too small 17 | 18 | ERR_COULD_NOT_LOAD_DLL = 16 19 | ERR_COULD_NOT_LOAD_FUNC = 17 20 | 21 | 22 | class SeedNKeyError(Exception): 23 | """""" 24 | 25 | 26 | LOADER = Path(str(sys.modules["pyxcp"].__file__)).parent / "asamkeydll" # Absolute path to DLL loader. 27 | 28 | bwidth, _ = platform.architecture() 29 | 30 | if sys.platform in ("win32", "linux", "darwin"): 31 | if bwidth == "64bit": 32 | use_ctypes = False 33 | elif bwidth == "32bit": 34 | use_ctypes = True 35 | else: 36 | raise RuntimeError(f"Platform {sys.platform!r} currently not supported.") 37 | 38 | 39 | def getKey(logger, dllName: str, privilege: int, seed: bytes, assume_same_bit_width: bool): 40 | dllName = str(Path(dllName).absolute()) # Fix loader issues. 41 | 42 | use_ctypes: bool = False 43 | if assume_same_bit_width: 44 | use_ctypes = True 45 | if use_ctypes: 46 | try: 47 | lib: ctypes.CDLL = ctypes.cdll.LoadLibrary(dllName) 48 | except OSError: 49 | logger.error(f"Could not load DLL {dllName!r} -- Probably an 64bit vs 32bit issue?") 50 | return (SeedNKeyResult.ERR_COULD_NOT_LOAD_DLL, None) 51 | func = lib.XCP_ComputeKeyFromSeed 52 | func.restype = ctypes.c_uint32 53 | func.argtypes = [ 54 | ctypes.c_uint8, 55 | ctypes.c_uint8, 56 | ctypes.c_char_p, 57 | ctypes.POINTER(ctypes.c_uint8), 58 | ctypes.c_char_p, 59 | ] 60 | key_buffer: ctypes.Array[ctypes.c_char] = ctypes.create_string_buffer(b"\000" * 128) 61 | key_length: ctypes.c_uint8 = ctypes.c_uint8(128) 62 | ret_code: int = func( 63 | privilege, 64 | len(seed), 65 | ctypes.c_char_p(seed), 66 | ctypes.byref(key_length), 67 | key_buffer, 68 | ) 69 | return (ret_code, key_buffer.raw[0 : key_length.value]) 70 | else: 71 | try: 72 | p0 = subprocess.Popen( 73 | [LOADER, dllName, str(privilege), binascii.hexlify(seed).decode("ascii")], 74 | stdout=subprocess.PIPE, 75 | shell=False, 76 | ) # nosec 77 | except FileNotFoundError as exc: 78 | logger.error(f"Could not find executable {LOADER!r} -- {exc}") 79 | return (SeedNKeyResult.ERR_COULD_NOT_LOAD_DLL, None) 80 | except OSError as exc: 81 | logger.error(f"Cannot execute {LOADER!r} -- {exc}") 82 | return (SeedNKeyResult.ERR_COULD_NOT_LOAD_DLL, None) 83 | key: bytes = b"" 84 | if p0.stdout: 85 | key = p0.stdout.read() 86 | p0.stdout.close() 87 | p0.kill() 88 | p0.wait() 89 | if not key: 90 | logger.error(f"Something went wrong while calling seed-and-key-DLL {dllName!r}") 91 | return (SeedNKeyResult.ERR_COULD_NOT_LOAD_DLL, None) 92 | res = re.split(b"\r?\n", key) 93 | returnCode = int(res[0]) 94 | key = binascii.unhexlify(res[1]) 95 | return (returnCode, key) 96 | -------------------------------------------------------------------------------- /pyxcp/examples/conf_can.toml: -------------------------------------------------------------------------------- 1 | TRANSPORT = "CAN" 2 | CAN_DRIVER = "KVaser" 3 | CAN_USE_DEFAULT_LISTENER = true 4 | CHANNEL = "0" 5 | ACCEPT_VIRTUAL = true 6 | CAN_ID_MASTER = 257 7 | CAN_ID_SLAVE = 258 8 | CAN_ID_BROADCAST = 256 9 | MAX_DLC_REQUIRED = false 10 | BITRATE = 50000 11 | DATA_BITRATE = 50000 12 | FD=false 13 | BTL_CYCLES = 16 14 | SAMPLE_RATE = 1 15 | SAMPLE_POINT = 87.5 16 | SJW = 2 17 | TSEG1 = 5 18 | TSEG2 = 2 19 | CREATE_DAQ_TIMESTAMPS = false 20 | -------------------------------------------------------------------------------- /pyxcp/examples/conf_can_user.toml: -------------------------------------------------------------------------------- 1 | TRANSPORT = "CAN" 2 | CAN_DRIVER = "MyCI" 3 | CAN_USE_DEFAULT_LISTENER = true 4 | CHANNEL = "0" 5 | CAN_ID_MASTER = 257 6 | CAN_ID_SLAVE = 258 7 | CAN_ID_BROADCAST = 256 8 | MAX_DLC_REQUIRED = false 9 | BITRATE = 250000 10 | BTL_CYCLES = 16 11 | SAMPLE_RATE = 1 12 | SAMPLE_POINT = 87.5 13 | SJW = 2 14 | TSEG1 = 5 15 | TSEG2 = 2 16 | CREATE_DAQ_TIMESTAMPS = false 17 | -------------------------------------------------------------------------------- /pyxcp/examples/conf_can_vector.json: -------------------------------------------------------------------------------- 1 | { 2 | "TRANSPORT": "CAN", 3 | "CAN_DRIVER": "Vector", 4 | "CAN_USE_DEFAULT_LISTENER": true, 5 | "CHANNEL": "1", 6 | "CAN_ID_MASTER": 2, 7 | "CAN_ID_SLAVE": 1, 8 | "CAN_ID_BROADCAST": 256, 9 | "MAX_DLC_REQUIRED": false, 10 | "CREATE_DAQ_TIMESTAMPS": false 11 | } 12 | -------------------------------------------------------------------------------- /pyxcp/examples/conf_can_vector.toml: -------------------------------------------------------------------------------- 1 | TRANSPORT = "CAN" 2 | CAN_DRIVER = "Vector" 3 | CAN_USE_DEFAULT_LISTENER = true 4 | CHANNEL = "0" 5 | CAN_ID_MASTER = 2 6 | CAN_ID_SLAVE = 1 7 | CAN_ID_BROADCAST = 256 8 | # MAX_DLC_REQUIRED = true 9 | CREATE_DAQ_TIMESTAMPS = false 10 | BITRATE=1000000 11 | SEED_N_KEY_DLL = "SeedNKeyXcp.dll" 12 | -------------------------------------------------------------------------------- /pyxcp/examples/conf_eth.toml: -------------------------------------------------------------------------------- 1 | TRANSPORT = "ETH" 2 | #HOST = "192.168.197.40" 3 | #HOST = "192.168.198.175" 4 | HOST="localhost" 5 | PORT = 5555 6 | PROTOCOL = "UDP" 7 | IPV6 = false 8 | CREATE_DAQ_TIMESTAMPS = true 9 | SEED_N_KEY_DLL="SeedNKeyXcp.dll" 10 | -------------------------------------------------------------------------------- /pyxcp/examples/conf_nixnet.json: -------------------------------------------------------------------------------- 1 | { 2 | "TRANSPORT": "CAN", 3 | "CAN_DRIVER": "NiXnet", 4 | "CAN_USE_DEFAULT_LISTENER": true, 5 | "CHANNEL": "CAN4", 6 | "ACCEPT_VIRTUAL": true, 7 | "BAUDRATE_PRESET": true, 8 | "CAN_ID_MASTER": 1911, 9 | "CAN_ID_SLAVE": 819, 10 | "CAN_ID_BROADCAST": 256, 11 | "MAX_DLC_REQUIRED": false, 12 | "BITRATE": 500000, 13 | "BTL_CYCLES": 16, 14 | "SAMPLE_RATE": 1, 15 | "SAMPLE_POINT": 87.5, 16 | "SJW": 2, 17 | "TSEG1": 5, 18 | "TSEG2": 2, 19 | "CREATE_DAQ_TIMESTAMPS": false 20 | } 21 | -------------------------------------------------------------------------------- /pyxcp/examples/conf_socket_can.toml: -------------------------------------------------------------------------------- 1 | TRANSPORT = "CAN" 2 | CAN_DRIVER = "SocketCAN" 3 | CAN_USE_DEFAULT_LISTENER = true 4 | CHANNEL = "can0" 5 | CAN_ID_MASTER = 257 6 | CAN_ID_SLAVE = 258 7 | CAN_ID_BROADCAST = 256 8 | CREATE_DAQ_TIMESTAMPS = false 9 | FD = false 10 | MAX_DLC_REQUIRED = true 11 | MAX_CAN_FD_DLC = 32 12 | PADDING_VALUE = 0 13 | -------------------------------------------------------------------------------- /pyxcp/examples/conf_sxi.json: -------------------------------------------------------------------------------- 1 | { 2 | "TRANSPORT": "SXI", 3 | "PORT": "COM10", 4 | "BITRATE": 38400, 5 | "BYTESIZE": 8, 6 | "PARITY": "N", 7 | "STOPBITS": 1, 8 | "CREATE_DAQ_TIMESTAMPS": false 9 | } 10 | -------------------------------------------------------------------------------- /pyxcp/examples/conf_sxi.toml: -------------------------------------------------------------------------------- 1 | TRANSPORT = "SXI" 2 | PORT = "COM10" 3 | BITRATE = 38400 4 | PARITY = "N" 5 | BYTESIZE = 8 6 | STOPBITS = 1 7 | CREATE_DAQ_TIMESTAMPS = false 8 | -------------------------------------------------------------------------------- /pyxcp/examples/run_daq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | import time 5 | 6 | from pyxcp.cmdline import ArgumentParser 7 | from pyxcp.daq_stim import DaqList, DaqRecorder, DaqToCsv # noqa: F401 8 | from pyxcp.types import XcpTimeoutError 9 | 10 | 11 | ap = ArgumentParser(description="DAQ test") 12 | 13 | XCP_LITE = False 14 | 15 | # 16 | # NOTE: UPDATE TO CORRECT ADDRESSES BEFORE RUNNING!!! 17 | # 18 | if XCP_LITE: 19 | # Vectorgrp XCPlite. 20 | DAQ_LISTS = [ 21 | DaqList( 22 | name="part_1", 23 | event_num=0, 24 | stim=False, 25 | enable_timestamps=False, 26 | measurements=[ 27 | ("byteCounter", 0x00023648, 0, "U8"), 28 | ("wordCounter", 0x0002364C, 0, "U16"), 29 | ("dwordCounter", 0x00023650, 0, "U32"), 30 | ("sbyteCounter", 0x00023649, 0, "I8"), 31 | ], 32 | priority=0, 33 | prescaler=1, 34 | ), 35 | DaqList( 36 | name="part_2", 37 | event_num=7, 38 | stim=False, 39 | enable_timestamps=False, 40 | measurements=[ 41 | ("swordCounter", 0x00023654, 0, "I16"), 42 | ("sdwordCounter", 0x00023658, 0, "I32"), 43 | ("channel1", 0x00023630, 0, "F64"), 44 | ("channel2", 0x00023638, 0, "F64"), 45 | ("channel3", 0x00023640, 0, "F64"), 46 | ], 47 | priority=0, 48 | prescaler=1, 49 | ), 50 | ] 51 | else: 52 | # XCPsim from CANape. 53 | DAQ_LISTS = [ 54 | DaqList( 55 | name="pwm_stuff", 56 | event_num=2, 57 | stim=False, 58 | enable_timestamps=True, 59 | measurements=[ 60 | ("channel1", 0x1BD004, 0, "F32"), 61 | ("period", 0x001C0028, 0, "F32"), 62 | ("channel2", 0x1BD008, 0, "F32"), 63 | ("PWMFiltered", 0x1BDDE2, 0, "U8"), 64 | ("PWM", 0x1BDDDF, 0, "U8"), 65 | ("Triangle", 0x1BDDDE, 0, "I8"), 66 | ], 67 | priority=0, 68 | prescaler=1, 69 | ), 70 | DaqList( 71 | name="bytes", 72 | event_num=1, 73 | stim=False, 74 | enable_timestamps=True, 75 | measurements=[ 76 | ("TestByte_000", 0x1BE11C, 0, "U8"), 77 | ("TestByte_015", 0x1BE158, 0, "U8"), 78 | ("TestByte_016", 0x1BE15C, 0, "U8"), 79 | ("TestByte_023", 0x1BE178, 0, "U8"), 80 | ("TestByte_024", 0x1BE17C, 0, "U8"), 81 | ("TestByte_034", 0x1BE1A4, 0, "U8"), 82 | ("TestByte_059", 0x1BE208, 0, "U8"), 83 | ("TestByte_061", 0x1BE210, 0, "U8"), 84 | ("TestByte_063", 0x1BE218, 0, "U8"), 85 | ("TestByte_064", 0x1BE21C, 0, "U8"), 86 | ("TestByte_097", 0x1BE2A0, 0, "U8"), 87 | ("TestByte_107", 0x1BE2C8, 0, "U8"), 88 | ("TestByte_131", 0x1BE328, 0, "U8"), 89 | ("TestByte_156", 0x1BE38C, 0, "U8"), 90 | ("TestByte_159", 0x1BE398, 0, "U8"), 91 | ("TestByte_182", 0x1BE3F4, 0, "U8"), 92 | ("TestByte_183", 0x1BE3F8, 0, "U8"), 93 | ("TestByte_189", 0x1BE410, 0, "U8"), 94 | ("TestByte_195", 0x1BE428, 0, "U8"), 95 | ("TestByte_216", 0x1BE47C, 0, "U8"), 96 | ("TestByte_218", 0x1BE484, 0, "U8"), 97 | ("TestByte_221", 0x1BE490, 0, "U8"), 98 | ("TestByte_251", 0x1BE508, 0, "U8"), 99 | ("TestByte_263", 0x1BE538, 0, "U8"), 100 | ("TestByte_276", 0x1BE56C, 0, "U8"), 101 | ("TestByte_277", 0x1BE570, 0, "U8"), 102 | ("TestByte_297", 0x1BE5C0, 0, "U8"), 103 | ("TestByte_302", 0x1BE5D4, 0, "U8"), 104 | ("TestByte_324", 0x1BE62C, 0, "U8"), 105 | ("TestByte_344", 0x1BE67C, 0, "U8"), 106 | ("TestByte_346", 0x1BE684, 0, "U8"), 107 | ], 108 | priority=0, 109 | prescaler=1, 110 | ), 111 | DaqList( 112 | name="words", 113 | event_num=3, 114 | stim=False, 115 | enable_timestamps=True, 116 | measurements=[ 117 | ("TestWord_001", 0x1BE120, 0, "U16"), 118 | ("TestWord_003", 0x1BE128, 0, "U16"), 119 | ("TestWord_004", 0x1BE12C, 0, "U16"), 120 | ("TestWord_005", 0x1BE134, 0, "U16"), 121 | ("TestWord_006", 0x1BE134, 0, "U16"), 122 | ("TestWord_007", 0x1BE138, 0, "U16"), 123 | ("TestWord_008", 0x1BE13C, 0, "U16"), 124 | ("TestWord_009", 0x1BE140, 0, "U16"), 125 | ("TestWord_011", 0x1BE148, 0, "U16"), 126 | ], 127 | priority=0, 128 | prescaler=1, 129 | ), 130 | ] 131 | 132 | daq_parser = DaqToCsv(DAQ_LISTS) # Record to CSV file(s). 133 | # daq_parser = DaqRecorder(DAQ_LISTS, "run_daq", 2) # Record to ".xmraw" file. 134 | 135 | with ap.run(policy=daq_parser) as x: 136 | try: 137 | x.connect() 138 | except XcpTimeoutError: 139 | print("TO") 140 | sys.exit(2) 141 | 142 | if x.slaveProperties.optionalCommMode: 143 | x.getCommModeInfo() 144 | 145 | x.cond_unlock("DAQ") # DAQ resource is locked in many cases. 146 | 147 | print("setup DAQ lists.") 148 | daq_parser.setup() # Execute setup procedures. 149 | print("start DAQ lists.") 150 | daq_parser.start() # Start DAQ lists. 151 | 152 | time.sleep(5.0 * 60.0) # Run for 15 minutes. 153 | 154 | print("Stop DAQ....") 155 | daq_parser.stop() # Stop DAQ lists. 156 | print("finalize DAQ lists.\n") 157 | x.disconnect() 158 | 159 | if hasattr(daq_parser, "files"): # `files` attribute is specific to `DaqToCsv`. 160 | print("Data written to:") 161 | print("================") 162 | for fl in daq_parser.files.values(): 163 | print(fl.name) 164 | -------------------------------------------------------------------------------- /pyxcp/examples/xcp_policy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Demostrates how to use frame recording policies including recorder extension. 3 | """ 4 | from pprint import pprint 5 | 6 | from pyxcp.cmdline import ArgumentParser 7 | from pyxcp.transport.base import FrameRecorderPolicy, StdoutPolicy # noqa: F401 8 | 9 | 10 | ap = ArgumentParser(description="pyXCP frame recording policy example.") 11 | 12 | LOG_FILE = "pyxcp" 13 | 14 | policy = FrameRecorderPolicy(LOG_FILE) 15 | use_recorder = True 16 | 17 | # policy = StdoutPolicy() # You may also try this one. 18 | # use_recorder = False 19 | 20 | with ap.run(policy=policy) as x: 21 | x.connect() 22 | if x.slaveProperties.optionalCommMode: 23 | x.getCommModeInfo() 24 | identifier = x.identifier(0x01) 25 | print("\nSlave Properties:") 26 | print("=================") 27 | print(f"ID: {identifier!r}") 28 | pprint(x.slaveProperties) 29 | x.disconnect() 30 | 31 | if use_recorder: 32 | from pyxcp.recorder import XcpLogFileReader 33 | from pyxcp.utils import hexDump 34 | 35 | try: 36 | import pandas # noqa: F401 37 | except ImportError: 38 | has_pandas = False 39 | else: 40 | has_pandas = True 41 | 42 | reader = XcpLogFileReader(LOG_FILE) 43 | hdr = reader.get_header() # Get file information. 44 | print("\nRecording file header") 45 | print("=====================\n") 46 | print(hdr) 47 | print("\nRecorded frames") 48 | print("===============\n") 49 | print("CAT CTR TS PAYLOAD") 50 | print("-" * 80) 51 | for category, counter, timestamp, payload in reader: 52 | print(f"{category.name:8} {counter:6} {timestamp:7.7f} {hexDump(payload)}") 53 | print("-" * 80) 54 | reader.reset_iter() # reader acts as an Python iterator -- can be reseted with this non-standard method. 55 | if has_pandas: 56 | print("\nRecordings as Pandas stuff") 57 | print("==========================\n") 58 | df = reader.as_dataframe() # Return recordings as Pandas DataFrame. 59 | print(df.info()) 60 | print(df.head(60)) 61 | -------------------------------------------------------------------------------- /pyxcp/examples/xcp_read_benchmark.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Very basic hello-world example. 3 | """ 4 | import time 5 | 6 | import matplotlib.pyplot as plt 7 | import seaborn as sns 8 | 9 | from pyxcp.cmdline import ArgumentParser 10 | from pyxcp.transport import FrameRecorderPolicy 11 | 12 | 13 | sns.set() 14 | 15 | 16 | ADDR = 0x4000 17 | LENGTH = 0x1000 18 | ITERATIONS = 100 19 | 20 | recorder_policy = FrameRecorderPolicy() # Create frame recorder. 21 | ap = ArgumentParser(description="pyXCP hello world.", policy=recorder_policy) 22 | with ap.run() as x: 23 | xs = [] 24 | ys = [] 25 | x.connect() 26 | for ctoSize in range(8, 64 + 4, 4): 27 | print(f"CTO-Size: {ctoSize}") 28 | xs.append(ctoSize) 29 | start = time.perf_counter() 30 | for _ in range(ITERATIONS): 31 | x.setMta(ADDR) 32 | data = x.fetch(LENGTH, ctoSize) 33 | et = time.perf_counter() - start 34 | ys.append(et) 35 | print(f"CTO size: {ctoSize:-3} -- elapsed time {et:-3.04}") 36 | x.disconnect() 37 | plt.plot(xs, ys) 38 | plt.show() 39 | -------------------------------------------------------------------------------- /pyxcp/examples/xcp_skel.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Use this as a copy-and-paste template for your own scripts. 3 | """ 4 | 5 | from pyxcp.cmdline import ArgumentParser 6 | 7 | 8 | def callout(master, args): 9 | if args.sk_dll: 10 | master.seedNKeyDLL = args.sk_dll 11 | 12 | 13 | ap = ArgumentParser(description="pyXCP skeleton.", callout=callout) 14 | 15 | # Add command-line option for seed-and-key DLL. 16 | ap.parser.add_argument( 17 | "-s", 18 | "--sk-dll", 19 | dest="sk_dll", 20 | help="Seed-and-Key .DLL name", 21 | type=str, 22 | default=None, 23 | ) 24 | 25 | with ap.run() as x: 26 | x.connect() 27 | if x.slaveProperties.optionalCommMode: 28 | # Collect additional properties. 29 | x.getCommModeInfo() 30 | 31 | # getId() is not strictly required. 32 | slave_name = x.identifier(0x01) 33 | 34 | # Unlock resources, if necessary. 35 | # Could be more specific, like cond_unlock("DAQ") 36 | # Note: Unlocking requires a seed-and-key DLL. 37 | x.cond_unlock() 38 | 39 | ## 40 | # Your own code goes here. 41 | ## 42 | 43 | x.disconnect() 44 | 45 | # Print some useful information. 46 | # print("\nSlave properties:") 47 | # print("=================") 48 | # print("ID: '{}'".format(slave_name.decode("utf8"))) 49 | # pprint(x.slaveProperties) 50 | -------------------------------------------------------------------------------- /pyxcp/examples/xcp_unlock.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Very basic hello-world example. 3 | """ 4 | from pyxcp.cmdline import ArgumentParser 5 | 6 | 7 | """ 8 | """ 9 | 10 | 11 | def callout(master, args): 12 | if args.sk_dll: 13 | master.seedNKeyDLL = args.sk_dll 14 | 15 | 16 | ap = ArgumentParser(callout) 17 | ap.parser.add_argument( 18 | "-s", 19 | "--sk-dll", 20 | dest="sk_dll", 21 | help="Seed-and-Key .DLL name", 22 | type=str, 23 | default=None, 24 | ) 25 | 26 | with ap.run() as x: 27 | x.connect() 28 | 29 | print("") 30 | rps = x.getCurrentProtectionStatus() 31 | print("Protection before unlocking:", rps, end="\n\n") 32 | 33 | x.cond_unlock() 34 | 35 | rps = x.getCurrentProtectionStatus() 36 | print("Protection after unlocking:", rps) 37 | 38 | x.disconnect() 39 | -------------------------------------------------------------------------------- /pyxcp/examples/xcp_user_supplied_driver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """User supplied CAN driver. 3 | 4 | Run as: 5 | 6 | .. code-block:: shell 7 | 8 | python xcp_user_supplied_driver.py -c conf_can_user.toml 9 | """ 10 | from pyxcp.cmdline import ArgumentParser 11 | from pyxcp.transport.can import CanInterfaceBase 12 | 13 | 14 | class MyCI(CanInterfaceBase): 15 | """ 16 | 17 | Relevant options in your configuration file (e.g. conf_can_user.toml): 18 | 19 | TRANSPORT = "CAN" 20 | CAN_DRIVER = "MyCI" # The name of your custom driver class. 21 | 22 | """ 23 | 24 | def init(self, parent, receive_callback): 25 | self.parent = parent 26 | 27 | def connect(self): 28 | pass 29 | 30 | def close(self): 31 | pass 32 | 33 | def get_timestamp_resolution(self): 34 | pass 35 | 36 | def read(self): 37 | pass 38 | 39 | def transmit(self, payload: bytes): 40 | print("\tTX-PAYLOAD", payload) 41 | 42 | 43 | ap = ArgumentParser(description="User supplied CAN driver.") 44 | with ap.run() as x: 45 | x.connect() 46 | x.disconnect() 47 | 48 | """ 49 | This do-nothing example will output 50 | 51 | TX-PAYLOAD b'\xff\x00' 52 | 53 | and then timeout (0xff is the service code for CONNECT_REQ). 54 | """ 55 | -------------------------------------------------------------------------------- /pyxcp/examples/xcphello.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Very basic hello-world example. 3 | """ 4 | from pprint import pprint 5 | 6 | from pyxcp.cmdline import ArgumentParser 7 | from pyxcp.utils import decode_bytes 8 | 9 | 10 | daq_info = False 11 | 12 | 13 | def callout(master, args): 14 | global daq_info 15 | if args.daq_info: 16 | daq_info = True 17 | 18 | 19 | ap = ArgumentParser(description="pyXCP hello world.", callout=callout) 20 | ap.parser.add_argument( 21 | "-d", 22 | "--daq-info", 23 | dest="daq_info", 24 | help="Display DAQ-info", 25 | default=False, 26 | action="store_true", 27 | ) 28 | 29 | with ap.run() as x: 30 | x.connect() 31 | if x.slaveProperties.optionalCommMode: 32 | x.getCommModeInfo() 33 | identifier = x.identifier(0x01) 34 | print("\nSlave Properties:") 35 | print("=================") 36 | print(f"ID: {identifier!r}") 37 | pprint(x.slaveProperties) 38 | x.cond_unlock() 39 | cps = x.getCurrentProtectionStatus() 40 | print("\nProtection Status") 41 | print("=================") 42 | for k, v in cps.items(): 43 | print(f" {k:6s}: {v}") 44 | daq = x.getDaqInfo() 45 | pprint(daq) 46 | if daq_info: 47 | dqp = x.getDaqProcessorInfo() 48 | print("\nDAQ Processor Info:") 49 | print("===================") 50 | print(dqp) 51 | print("\nDAQ Events:") 52 | print("===========") 53 | for idx in range(dqp.maxEventChannel): 54 | evt = x.getDaqEventInfo(idx) 55 | length = evt.eventChannelNameLength 56 | name = decode_bytes(x.pull(length)) 57 | dq = "DAQ" if evt.daqEventProperties.daq else "" 58 | st = "STIM" if evt.daqEventProperties.stim else "" 59 | dq_st = dq + " " + st 60 | print(f" [{idx:04}] {name:r}") 61 | print(f" dir: {dq_st}") 62 | print(f" packed: {evt.daqEventProperties.packed}") 63 | PFX_CONS = "CONSISTENCY_" 64 | print(f" consistency: {evt.daqEventProperties.consistency.strip(PFX_CONS)}") 65 | print(f" max. DAQ lists: {evt.maxDaqList}") 66 | PFX_TU = "EVENT_CHANNEL_TIME_UNIT_" 67 | print(f" unit: {evt.eventChannelTimeUnit.strip(PFX_TU)}") 68 | print(f" cycle: {evt.eventChannelTimeCycle or 'SPORADIC'}") 69 | print(f" priority {evt.eventChannelPriority}") 70 | 71 | dqr = x.getDaqResolutionInfo() 72 | print("\nDAQ Resolution Info:") 73 | print("====================") 74 | print(dqr) 75 | for idx in range(dqp.maxDaq): 76 | print(f"\nDAQ List Info #{idx}") 77 | print("=================") 78 | print(f"{x.getDaqListInfo(idx)}") 79 | x.disconnect() 80 | -------------------------------------------------------------------------------- /pyxcp/examples/xcphello_recorder.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Very basic hello-world example. 3 | """ 4 | from pprint import pprint 5 | 6 | from pyxcp.cmdline import ArgumentParser 7 | from pyxcp.recorder import XcpLogFileReader 8 | from pyxcp.transport import FrameRecorderPolicy 9 | from pyxcp.utils import decode_bytes 10 | 11 | 12 | daq_info = False 13 | 14 | 15 | def callout(master, args): 16 | global daq_info 17 | if args.daq_info: 18 | daq_info = True 19 | 20 | 21 | ap = ArgumentParser(description="pyXCP hello world.", callout=callout) 22 | ap.parser.add_argument( 23 | "-d", 24 | "--daq-info", 25 | dest="daq_info", 26 | help="Display DAQ-info", 27 | default=False, 28 | action="store_true", 29 | ) 30 | 31 | RECORDER_FILE_NAME = "xcphello" 32 | 33 | recorder_policy = FrameRecorderPolicy(RECORDER_FILE_NAME) # Create frame recorder. 34 | 35 | with ap.run(recorder_policy) as x: # parameter policy is new. 36 | x.connect() 37 | if x.slaveProperties.optionalCommMode: 38 | x.getCommModeInfo() 39 | identifier = x.identifier(0x01) 40 | print("\nSlave Properties:") 41 | print("=================") 42 | print(f"ID: {identifier!r}") 43 | pprint(x.slaveProperties) 44 | cps = x.getCurrentProtectionStatus() 45 | print("\nProtection Status") 46 | print("=================") 47 | for k, v in cps.items(): 48 | print(f" {k:6s}: {v}") 49 | if daq_info: 50 | dqp = x.getDaqProcessorInfo() 51 | print("\nDAQ Processor Info:") 52 | print("===================") 53 | print(dqp) 54 | print("\nDAQ Events:") 55 | print("===========") 56 | for idx in range(dqp.maxEventChannel): 57 | evt = x.getDaqEventInfo(idx) 58 | length = evt.eventChannelNameLength 59 | name = decode_bytes(x.pull(length)) 60 | dq = "DAQ" if evt.daqEventProperties.daq else "" 61 | st = "STIM" if evt.daqEventProperties.stim else "" 62 | dq_st = dq + " " + st 63 | print(f" [{idx:04}] {name:r}") 64 | print(f" dir: {dq_st}") 65 | print(f" packed: {evt.daqEventProperties.packed}") 66 | PFX_CONS = "CONSISTENCY_" 67 | print(f" consistency: {evt.daqEventProperties.consistency.strip(PFX_CONS)}") 68 | print(f" max. DAQ lists: {evt.maxDaqList}") 69 | PFX_TU = "EVENT_CHANNEL_TIME_UNIT_" 70 | print(f" unit: {evt.eventChannelTimeUnit.strip(PFX_TU)}") 71 | print(f" cycle: {evt.eventChannelTimeCycle or 'SPORADIC'}") 72 | print(f" priority {evt.eventChannelPriority}") 73 | 74 | dqr = x.getDaqResolutionInfo() 75 | print("\nDAQ Resolution Info:") 76 | print("====================") 77 | print(dqr) 78 | for idx in range(dqp.maxDaq): 79 | print(f"\nDAQ List Info #{idx}") 80 | print("=================") 81 | print(f"{x.getDaqListInfo(idx)}") 82 | x.disconnect() 83 | print("After recording...") 84 | ## 85 | ## Now read and dump recorded frames. 86 | ## 87 | reader = XcpLogFileReader(RECORDER_FILE_NAME) 88 | 89 | hdr = reader.get_header() # Get file information. 90 | print("\n\n") 91 | print("=" * 82) 92 | pprint(hdr) 93 | print("=" * 82) 94 | 95 | print("\nIterating over frames") 96 | print("=" * 82) 97 | for _frame in reader: 98 | print(_frame) 99 | print("---") 100 | 101 | reader.reset_iter() # Non-standard method to restart iteration. 102 | 103 | df = reader.as_dataframe() # Return recordings as Pandas DataFrame. 104 | print("\nRecording as Pandas DataFrame") 105 | print("=" * 82) 106 | print(df.head(24)) 107 | print("=" * 82) 108 | -------------------------------------------------------------------------------- /pyxcp/master/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Lowlevel API reflecting available XCP services 3 | 4 | .. note:: For technical reasons the API is split into two parts; 5 | common methods and a Python version specific part. 6 | 7 | .. [1] XCP Specification, Part 2 - Protocol Layer Specification 8 | """ 9 | from .master import Master # noqa: F401 10 | -------------------------------------------------------------------------------- /pyxcp/py.typed: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/py.typed -------------------------------------------------------------------------------- /pyxcp/recorder/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """XCP Frame Recording Facility. 3 | """ 4 | 5 | from dataclasses import dataclass 6 | from typing import Union 7 | 8 | from pyxcp.types import FrameCategory 9 | 10 | 11 | try: 12 | import pandas as pd 13 | except ImportError: 14 | HAS_PANDAS = False 15 | else: 16 | HAS_PANDAS = True 17 | 18 | from pyxcp.recorder.rekorder import DaqOnlinePolicy # noqa: F401 19 | from pyxcp.recorder.rekorder import ( 20 | DaqRecorderPolicy, # noqa: F401 21 | Deserializer, # noqa: F401 22 | MeasurementParameters, # noqa: F401 23 | ValueHolder, # noqa: F401 24 | ) 25 | from pyxcp.recorder.rekorder import XcpLogFileDecoder as _XcpLogFileDecoder 26 | from pyxcp.recorder.rekorder import _PyXcpLogFileReader, _PyXcpLogFileWriter, data_types 27 | 28 | 29 | DATA_TYPES = data_types() 30 | 31 | 32 | @dataclass 33 | class XcpLogFileHeader: 34 | """ """ 35 | 36 | version: int 37 | options: int 38 | num_containers: int 39 | record_count: int 40 | size_uncompressed: int 41 | size_compressed: int 42 | compression_ratio: float 43 | 44 | 45 | COUNTER_MAX = 0xFFFF 46 | 47 | 48 | class XcpLogFileReader: 49 | """ """ 50 | 51 | def __init__(self, file_name): 52 | self._reader = _PyXcpLogFileReader(file_name) 53 | 54 | @property 55 | def header(self): 56 | return self._reader.get_header() 57 | 58 | def get_header(self): 59 | return XcpLogFileHeader(*self._reader.get_header_as_tuple()) 60 | 61 | def get_metadata(self): 62 | return self._reader.get_metadata() 63 | 64 | def __iter__(self): 65 | while True: 66 | frames = self._reader.next_block() 67 | if frames is None: 68 | break 69 | for category, counter, timestamp, _, payload in frames: 70 | yield (FrameCategory(category), counter, timestamp, payload) 71 | 72 | def reset_iter(self): 73 | self._reader.reset() 74 | 75 | def as_dataframe(self): 76 | if HAS_PANDAS: 77 | df = pd.DataFrame((f for f in self), columns=["category", "counter", "timestamp", "payload"]) 78 | df = df.set_index("timestamp") 79 | df.counter = df.counter.astype("uint16") 80 | df.category = df.category.map({v: k for k, v in FrameCategory.__members__.items()}).astype("category") 81 | return df 82 | else: 83 | raise NotImplementedError("method as_dataframe() requires 'pandas' package") 84 | 85 | 86 | class XcpLogFileWriter: 87 | """ """ 88 | 89 | def __init__(self, file_name: str, prealloc=500, chunk_size=1): 90 | self._writer = _PyXcpLogFileWriter(file_name, prealloc, chunk_size) 91 | self._finalized = False 92 | 93 | def __del__(self): 94 | if not self._finalized: 95 | self.finalize() 96 | 97 | def add_frame(self, category: FrameCategory, counter: int, timestamp: float, payload: Union[bytes, bytearray]): 98 | self._writer.add_frame(category, counter % (COUNTER_MAX + 1), timestamp, len(payload), payload) 99 | 100 | def finalize(self): 101 | self._writer.finalize() 102 | -------------------------------------------------------------------------------- /pyxcp/recorder/build_clang.cmd: -------------------------------------------------------------------------------- 1 | clang++ -std=c++20 -O0 -fvectorize -fexceptions -Rpass=loop-vectorize -ggdb -Wall -Wextra -Weffc++ -DLZ4_DEBUG=1 -DSTANDALONE_REKORDER=1 lz4.cpp rekorder.cpp -o rekorder.exe 2 | -------------------------------------------------------------------------------- /pyxcp/recorder/build_clang.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | clang++ -std=c++20 -O0 -fvectorize -Rpass=loop-vectorize -ggdb -Wall -Wextra -Weffc++ -lpthread -DLZ4_DEBUG=1 -DSTANDALONE_REKORDER=1 lz4.cpp rekorder.cpp -o rekorder 3 | -------------------------------------------------------------------------------- /pyxcp/recorder/build_gcc.cmd: -------------------------------------------------------------------------------- 1 | g++ -std=c++20 -O0 -ggdb -march=native -fexceptions -ffast-math -ftree-vectorize -msse4 -mfpmath=sse -pg -fopt-info-vec-all -Wall -Wextra -Weffc++ -fcoroutines -DLZ4_DEBUG=1 -DSTANDALONE_REKORDER=1 lz4.cpp rekorder.cpp -o rekorder.exe 2 | -------------------------------------------------------------------------------- /pyxcp/recorder/build_gcc.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | g++ -std=c++20 -O0 -ffast-math -ftree-vectorize -ftree-vectorizer-verbose=9 -fcoroutines -ggdb -Wall -Wextra -Weffc++ -DLZ4_DEBUG=1 -DSTANDALONE_REKORDER=1 lz4.cpp rekorder.cpp -o rekorder -lpthread 3 | -------------------------------------------------------------------------------- /pyxcp/recorder/build_gcc_arm.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | g++ -std=c++20 -O3 -mcpu=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard -ffast-math -ftree-vectorize -ftree-vectorizer-verbose=9 -ggdb -Wall -Wextra -Weffc++ -DLZ4_DEBUG=1 -DSTANDALONE_REKORDER=1 lz4.cpp rekorder.cpp -o rekorder 3 | -------------------------------------------------------------------------------- /pyxcp/recorder/reader.hpp: -------------------------------------------------------------------------------- 1 | 2 | #ifndef RECORDER_READER_HPP 3 | #define RECORDER_READER_HPP 4 | 5 | #include 6 | 7 | class XcpLogFileReader { 8 | public: 9 | 10 | explicit XcpLogFileReader(const std::string &file_name) { 11 | if (!file_name.ends_with(detail::FILE_EXTENSION)) { 12 | m_file_name = file_name + detail::FILE_EXTENSION; 13 | } else { 14 | m_file_name = file_name; 15 | } 16 | 17 | m_mmap = new mio::mmap_source(m_file_name); 18 | blob_t magic[detail::MAGIC_SIZE + 1]; 19 | 20 | read_bytes(0UL, detail::MAGIC_SIZE, magic); 21 | if (memcmp(detail::MAGIC.c_str(), magic, detail::MAGIC_SIZE) != 0) { 22 | throw std::runtime_error("Invalid file magic."); 23 | } 24 | m_offset = detail::MAGIC_SIZE; 25 | 26 | read_bytes(m_offset, detail::FILE_HEADER_SIZE, reinterpret_cast(&m_header)); 27 | // printf("Sizes: %u %u %.3f\n", m_header.size_uncompressed, 28 | // m_header.size_compressed, 29 | // float(m_header.size_uncompressed) / float(m_header.size_compressed)); 30 | if (m_header.hdr_size != detail::FILE_HEADER_SIZE + detail::MAGIC_SIZE) { 31 | throw std::runtime_error("File header size does not match."); 32 | } 33 | if (detail::VERSION != m_header.version) { 34 | throw std::runtime_error("File version mismatch."); 35 | } 36 | #if 0 37 | if (m_header.num_containers < 1) { 38 | throw std::runtime_error("At least one container required."); 39 | } 40 | #endif 41 | m_offset += detail::FILE_HEADER_SIZE; 42 | 43 | if ((m_header.options & XMRAW_HAS_METADATA) == XMRAW_HAS_METADATA) { 44 | std::size_t metadata_length = 0; 45 | std::size_t data_start = m_offset + sizeof(std::size_t); 46 | 47 | read_bytes(m_offset, sizeof(std::size_t), reinterpret_cast(&metadata_length)); 48 | 49 | std::copy(ptr(data_start), ptr(data_start + metadata_length), std::back_inserter(m_metadata)); 50 | // std::cout << "Metadata: " << m_metadata << std::endl; 51 | m_offset += (metadata_length + sizeof(std::size_t)); 52 | } 53 | } 54 | 55 | [[nodiscard]] FileHeaderType get_header() const noexcept { 56 | return m_header; 57 | } 58 | 59 | [[nodiscard]] auto get_header_as_tuple() const noexcept -> HeaderTuple { 60 | auto hdr = get_header(); 61 | 62 | return std::make_tuple( 63 | hdr.version, hdr.options, hdr.num_containers, hdr.record_count, hdr.size_uncompressed, hdr.size_compressed, 64 | (double)((std::uint64_t)(((double)hdr.size_uncompressed / (double)hdr.size_compressed * 100.0) + 0.5)) / 100.0 65 | ); 66 | } 67 | 68 | [[nodiscard]] auto get_metadata() const noexcept { 69 | return m_metadata; 70 | } 71 | 72 | void reset() noexcept { 73 | m_current_container = 0; 74 | m_offset = file_header_size(); 75 | } 76 | 77 | std::optional next_block() { 78 | auto container = ContainerHeaderType{}; 79 | auto frame = frame_header_t{}; 80 | std::uint64_t boffs = 0; 81 | auto result = FrameVector{}; 82 | payload_t payload; 83 | 84 | if (m_current_container >= m_header.num_containers) { 85 | return std::nullopt; 86 | } 87 | read_bytes(m_offset, detail::CONTAINER_SIZE, reinterpret_cast(&container)); 88 | __ALIGN auto buffer = new blob_t[container.size_uncompressed]; 89 | m_offset += detail::CONTAINER_SIZE; 90 | result.reserve(container.record_count); 91 | const int uc_size = ::LZ4_decompress_safe( 92 | reinterpret_cast(ptr(m_offset)), reinterpret_cast(buffer), container.size_compressed, 93 | container.size_uncompressed 94 | ); 95 | if (uc_size < 0) { 96 | throw std::runtime_error("LZ4 decompression failed."); 97 | } 98 | boffs = 0; 99 | for (std::uint64_t idx = 0; idx < container.record_count; ++idx) { 100 | _fcopy(reinterpret_cast(&frame), reinterpret_cast(&(buffer[boffs])), sizeof(frame_header_t)); 101 | boffs += sizeof(frame_header_t); 102 | result.emplace_back( 103 | frame.category, frame.counter, frame.timestamp, frame.length, create_payload(frame.length, &buffer[boffs]) 104 | ); 105 | boffs += frame.length; 106 | } 107 | m_offset += container.size_compressed; 108 | m_current_container += 1; 109 | delete[] buffer; 110 | 111 | return std::optional{ result }; 112 | } 113 | 114 | ~XcpLogFileReader() noexcept { 115 | delete m_mmap; 116 | } 117 | 118 | protected: 119 | 120 | [[nodiscard]] blob_t const *ptr(std::uint64_t pos = 0) const { 121 | return reinterpret_cast(m_mmap->data() + pos); 122 | } 123 | 124 | void read_bytes(std::uint64_t pos, std::uint64_t count, blob_t *buf) const { 125 | auto addr = reinterpret_cast(ptr(pos)); 126 | _fcopy(reinterpret_cast(buf), addr, count); 127 | } 128 | 129 | private: 130 | 131 | std::string m_file_name; 132 | std::uint64_t m_offset{ 0 }; 133 | std::uint64_t m_current_container{ 0 }; 134 | mio::mmap_source *m_mmap{ nullptr }; 135 | FileHeaderType m_header; 136 | std::string m_metadata; 137 | }; 138 | 139 | #endif // RECORDER_READER_HPP 140 | -------------------------------------------------------------------------------- /pyxcp/recorder/recorder.rst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/recorder/recorder.rst -------------------------------------------------------------------------------- /pyxcp/recorder/rekorder.cpp: -------------------------------------------------------------------------------- 1 | 2 | #define STANDALONE_REKORDER (1) 3 | 4 | #include "rekorder.hpp" 5 | 6 | void some_records(XcpLogFileWriter& writer) { 7 | const auto COUNT = 1024 * 100 * 5; 8 | unsigned filler = 0x00; 9 | char buffer[1024]; 10 | 11 | for (auto idx = 0; idx < COUNT; ++idx) { 12 | auto fr = frame_header_t{}; 13 | fr.category = 1; 14 | fr.counter = idx; 15 | fr.timestamp = std::clock(); 16 | fr.length = 10 + (rand() % 240); 17 | filler = (filler + 1) % 16; 18 | memset(buffer, filler, fr.length); 19 | writer.add_frame(fr.category, fr.counter, fr.timestamp, fr.length, std::bit_cast(&buffer)); 20 | } 21 | } 22 | 23 | int main() { 24 | srand(42); 25 | 26 | printf("\nWRITER\n"); 27 | printf("======\n"); 28 | 29 | auto writer = XcpLogFileWriter("test_logger", 250, 1); 30 | some_records(writer); 31 | writer.finalize(); 32 | 33 | printf("\nREADER\n"); 34 | printf("======\n"); 35 | 36 | auto reader = XcpLogFileReader("test_logger"); 37 | auto header = reader.get_header(); 38 | 39 | printf("size: %u\n", header.hdr_size); 40 | printf("version: %u\n", header.version); 41 | printf("options: %u\n", header.options); 42 | printf("containers: %u\n", header.num_containers); 43 | printf("records: %u\n", header.record_count); 44 | printf("size/compressed: %u\n", header.size_compressed); 45 | printf("size/uncompressed: %u\n", header.size_uncompressed); 46 | printf("compression ratio: %.2f\n", static_cast(header.size_uncompressed) / static_cast(header.size_compressed)); 47 | 48 | while (true) { 49 | const auto& frames = reader.next_block(); 50 | if (!frames) { 51 | break; 52 | } 53 | for (const auto& frame : frames.value()) { 54 | auto const& [category, counter, timestamp, length, payload] = frame; 55 | } 56 | } 57 | printf("---\n"); 58 | printf("Finished.\n"); 59 | } 60 | -------------------------------------------------------------------------------- /pyxcp/recorder/setup.py: -------------------------------------------------------------------------------- 1 | import subprocess # nosec 2 | from distutils.core import setup 3 | 4 | from pybind11.setup_helpers import ( 5 | ParallelCompile, 6 | Pybind11Extension, 7 | build_ext, 8 | naive_recompile, 9 | ) 10 | 11 | 12 | # ParallelCompile("NPY_NUM_BUILD_JOBS").install() 13 | ParallelCompile("NPY_NUM_BUILD_JOBS", needs_recompile=naive_recompile).install() 14 | 15 | INCLUDE_DIRS = subprocess.getoutput("pybind11-config --include") # nosec 16 | 17 | # os.environ ["CFLAGS"] = '' 18 | 19 | PKG_NAME = "rekorder_test" 20 | EXT_NAMES = ["rekorder"] 21 | __version__ = "0.0.1" 22 | 23 | ext_modules = [ 24 | Pybind11Extension( 25 | EXT_NAMES[0], 26 | include_dirs=[INCLUDE_DIRS], 27 | sources=["lz4.c", "wrap.cpp"], 28 | define_macros=[("EXTENSION_NAME", EXT_NAMES[0])], 29 | cxx_std=20, # Extension will use C++20 generators/coroutines. 30 | ), 31 | ] 32 | 33 | setup( 34 | name=PKG_NAME, 35 | version="0.0.1", 36 | author="John Doe", 37 | description="Example", 38 | ext_modules=ext_modules, 39 | cmdclass={"build_ext": build_ext}, 40 | zip_save=False, 41 | ) 42 | -------------------------------------------------------------------------------- /pyxcp/recorder/test_reko.py: -------------------------------------------------------------------------------- 1 | from time import perf_counter 2 | 3 | from pyxcp.recorder import FrameCategory, XcpLogFileReader, XcpLogFileWriter 4 | 5 | 6 | # Pre-allocate a 100MB file -- Note: due to the nature of memory-mapped files, this is a HARD limit. 7 | # Chunk size is 1MB (i.e. compression granularity). 8 | writer = XcpLogFileWriter("test_logger", prealloc=100, chunk_size=1) 9 | 10 | # Write some records. 11 | start_time = perf_counter() 12 | for idx in range(512 * 1024): 13 | value = idx % 256 14 | writer.add_frame(FrameCategory.CMD, idx, perf_counter() - start_time, bytes([value] * value)) 15 | writer.finalize() # We're done. 16 | 17 | 18 | reader = XcpLogFileReader("test_logger") 19 | hdr = reader.get_header() # Get file information. 20 | print(hdr) 21 | 22 | df = reader.as_dataframe() # Return recordings as Pandas DataFrame. 23 | print(df.info()) 24 | print(df.describe()) 25 | print(df) 26 | 27 | reader.reset_iter() # Non-standard method to restart iteration. 28 | 29 | idx = 0 30 | # Iterate over frames/records. 31 | for _frame in reader: 32 | idx += 1 33 | print("---") 34 | print(f"Iterated over {idx} frames") 35 | -------------------------------------------------------------------------------- /pyxcp/scripts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/scripts/__init__.py -------------------------------------------------------------------------------- /pyxcp/scripts/pyxcp_probe_can_drivers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import can 4 | 5 | 6 | def main(): 7 | can.log.setLevel("ERROR") 8 | interfaces = can.detect_available_configs() 9 | print("-" * 80) 10 | if not interfaces: 11 | print("No CAN-interfaces installed on your system.") 12 | else: 13 | print("\nInstalled CAN-interfaces on your system:\n") 14 | interfaces = sorted(interfaces, key=lambda e: e["interface"]) 15 | for interface in interfaces: 16 | print("\t{:20s} -- CHANNEL: {}".format(interface["interface"], interface["channel"])) 17 | 18 | 19 | if __name__ == "__main__": 20 | main() 21 | -------------------------------------------------------------------------------- /pyxcp/scripts/xcp_examples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | Copy pyXCP examples to a given directory. 5 | """ 6 | 7 | import argparse 8 | import sys 9 | from pathlib import Path 10 | 11 | from pyxcp import console 12 | 13 | 14 | pyver = sys.version_info 15 | if pyver.major == 3 and pyver.minor <= 9: 16 | import pkg_resources 17 | 18 | def copy_files_from_package(package_name: str, source_directory: str, args: argparse.Namespace) -> None: 19 | destination_directory = args.output_directory 20 | force = args.force 21 | for fn in pkg_resources.resource_listdir(package_name, source_directory): 22 | source_file = Path(pkg_resources.resource_filename(package_name, f"{source_directory}/{fn}")) 23 | if source_file.suffix == ".py": 24 | dest_file = Path(destination_directory) / fn 25 | if dest_file.exists() and not force: 26 | console.print(f"[white]Destination file [blue]{fn!r} [white]already exists. Skipping.") 27 | continue 28 | console.print(f"[blue]{source_file} [white]==> [green]{dest_file}") 29 | data = source_file.read_text(encoding="utf-8") 30 | dest_file.parent.mkdir(parents=True, exist_ok=True) 31 | dest_file.write_text(data, encoding="utf-8") 32 | 33 | else: 34 | import importlib.resources 35 | 36 | def copy_files_from_package(package_name: str, source_directory: str, args: argparse.Namespace) -> None: 37 | destination_directory = args.output_directory 38 | force = args.force 39 | for fn in importlib.resources.files(f"{package_name}.{source_directory}").iterdir(): 40 | if fn.suffix == ".py": 41 | data = fn.read_text(encoding="utf-8") 42 | dest_file = Path(destination_directory) / fn.name 43 | if dest_file.exists() and not force: 44 | console.print(f"[white]Destination file [blue]{fn.name!r} [white]already exists. Skipping.") 45 | continue 46 | console.print(f"[blue]{fn} [white]==> [green]{dest_file}") 47 | dest_file.parent.mkdir(parents=True, exist_ok=True) 48 | dest_file.write_text(data, encoding="utf-8") 49 | 50 | 51 | def main(): 52 | parser = argparse.ArgumentParser(description=__doc__) 53 | 54 | parser.add_argument("output_directory", metavar="output_directory", type=Path, help="output directory") 55 | 56 | parser.add_argument("-f", "--force", action="store_true", help="overwrite existing files.") 57 | 58 | args = parser.parse_args() 59 | print("Copying pyXCP examples...\n") 60 | copy_files_from_package("pyxcp", "examples", args) 61 | 62 | 63 | if __name__ == "__main__": 64 | main() 65 | -------------------------------------------------------------------------------- /pyxcp/scripts/xcp_fetch_a2l.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Fetch A2L file from XCP slave (if supported). 3 | """ 4 | import sys 5 | from pathlib import Path 6 | 7 | from rich.prompt import Confirm 8 | 9 | from pyxcp.cmdline import ArgumentParser 10 | from pyxcp.types import XcpGetIdType 11 | 12 | 13 | def main(): 14 | ap = ArgumentParser(description="Fetch A2L file from XCP slave.") 15 | 16 | with ap.run() as x: 17 | x.connect() 18 | 19 | # TODO: error-handling. 20 | file_name = x.identifier(XcpGetIdType.FILENAME) 21 | content = x.identifier(XcpGetIdType.FILE_TO_UPLOAD) 22 | x.disconnect() 23 | if not content: 24 | sys.exit(f"Empty response from ID '{XcpGetIdType.FILE_TO_UPLOAD!r}'.") 25 | if not file_name: 26 | file_name = "output.a2l" 27 | if not file_name.lower().endswith(".a2l"): 28 | file_name += ".a2l" 29 | dest = Path(file_name) 30 | if dest.exists(): 31 | if not Confirm.ask(f"Destination file [green]{dest.name!r}[/green] already exists. Do you want to overwrite it?"): 32 | print("Aborting...") 33 | exit(1) 34 | with dest.open("wt", encoding="utf-8") as of: 35 | of.write(content) 36 | print(f"A2L data written to {file_name!r}.") 37 | 38 | 39 | if __name__ == "__main__": 40 | main() 41 | -------------------------------------------------------------------------------- /pyxcp/scripts/xcp_id_scanner.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Scan for available IDs. 3 | """ 4 | 5 | from pyxcp.cmdline import ArgumentParser 6 | 7 | 8 | def main(): 9 | ap = ArgumentParser(description="Scan for available IDs.") 10 | with ap.run() as x: 11 | x.connect() 12 | result = x.id_scanner() 13 | for key, value in result.items(): 14 | print(f"{key}: {value}", end="\n\n") 15 | x.disconnect() 16 | 17 | 18 | if __name__ == "__main__": 19 | main() 20 | -------------------------------------------------------------------------------- /pyxcp/scripts/xcp_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """XCP info/exploration tool. 4 | """ 5 | 6 | from pprint import pprint 7 | 8 | from pyxcp.cmdline import ArgumentParser 9 | from pyxcp.types import TryCommandResult 10 | 11 | 12 | def getPagInfo(x): 13 | result = {} 14 | if x.slaveProperties.supportsCalpag: 15 | status, pag = x.try_command(x.getPagProcessorInfo) 16 | if status == TryCommandResult.OK: 17 | result["maxSegments"] = pag.maxSegments 18 | result["pagProperties"] = {} 19 | result["pagProperties"]["freezeSupported"] = pag.pagProperties.freezeSupported 20 | result["segments"] = [] 21 | for i in range(pag.maxSegments): 22 | segment = {} 23 | status, std_info = x.try_command(x.getSegmentInfo, 0x01, i, 0, 0) 24 | std_info = x.getSegmentInfo(0x01, i, 2, 0) 25 | if status == TryCommandResult.OK: 26 | segment["maxPages"] = std_info.maxPages 27 | segment["addressExtension"] = std_info.addressExtension 28 | segment["maxMapping"] = std_info.maxMapping 29 | segment["compressionMethod"] = std_info.compressionMethod 30 | segment["encryptionMethod"] = std_info.encryptionMethod 31 | 32 | status, seg_address = x.try_command(x.getSegmentInfo, 0x00, i, 0, 0) 33 | status, seg_length = x.try_command(x.getSegmentInfo, 0x00, i, 1, 0) 34 | 35 | segment["address"] = seg_address.basicInfo 36 | segment["length"] = seg_length.basicInfo 37 | 38 | result["segments"].append(segment) 39 | 40 | status, pgi = x.try_command(x.getPageInfo, i, 0) 41 | # print("PAGE:", pgi) 42 | # for j in range(si.maxPages): 43 | # pgi = x.getPageInfo(i, j) 44 | # print(pgi) 45 | else: 46 | break 47 | return result 48 | 49 | 50 | def main(): 51 | ap = ArgumentParser(description="XCP info/exploration tool.") 52 | 53 | with ap.run() as x: 54 | x.connect() 55 | if x.slaveProperties.optionalCommMode: 56 | x.try_command(x.getCommModeInfo, extra_msg="availability signaled by CONNECT, this may be a slave configuration error.") 57 | print("\nSlave Properties:") 58 | print("=================") 59 | pprint(x.slaveProperties) 60 | 61 | result = x.id_scanner() 62 | print("\n") 63 | print("Implemented IDs:") 64 | print("================") 65 | for key, value in result.items(): 66 | print(f"{key}: {value}", end="\n\n") 67 | cps = x.getCurrentProtectionStatus() 68 | print("\nProtection Status") 69 | print("=================") 70 | for k, v in cps.items(): 71 | print(f" {k:6s}: {v}") 72 | x.cond_unlock() 73 | print("\nDAQ Info:") 74 | print("=========") 75 | if x.slaveProperties.supportsDaq: 76 | daq_info = x.getDaqInfo() 77 | pprint(daq_info) 78 | 79 | daq_pro = daq_info["processor"] 80 | daq_properties = daq_pro["properties"] 81 | if x.slaveProperties.transport_layer == "CAN": 82 | print("") 83 | if daq_properties["pidOffSupported"]: 84 | print("*** pidOffSupported -- i.e. one CAN-ID per DAQ-list.") 85 | else: 86 | print("*** NO support for PID_OFF") 87 | num_predefined = daq_pro["minDaq"] 88 | print("\nPredefined DAQ-Lists") 89 | print("====================") 90 | if num_predefined > 0: 91 | print(f"There are {num_predefined} predefined DAQ-lists") 92 | for idx in range(num_predefined): 93 | print(f"DAQ-List #{idx}\n____________\n") 94 | status, dm = x.try_command(x.getDaqListMode, idx) 95 | if status == TryCommandResult.OK: 96 | print(dm) 97 | status, di = x.try_command(x.getDaqListInfo, idx) 98 | if status == TryCommandResult.OK: 99 | print(di) 100 | else: 101 | print("*** NO Predefined DAQ-Lists") 102 | else: 103 | print("*** DAQ IS NOT SUPPORTED .") 104 | print("\nPAG Info:") 105 | print("=========") 106 | if x.slaveProperties.supportsCalpag: 107 | pgi = getPagInfo(x) 108 | pprint(pgi) 109 | else: 110 | print("*** PAGING IS NOT SUPPORTED.") 111 | print("\nPGM Info:") 112 | print("=========") 113 | if x.slaveProperties.supportsPgm: 114 | status, pgm = x.try_command(x.getPgmProcessorInfo) 115 | if status == TryCommandResult.OK: 116 | print(pgm) 117 | else: 118 | print("*** FLASH PROGRAMMING IS NOT SUPPORTED.") 119 | if x.slaveProperties.transport_layer == "CAN": 120 | print("\nTransport-Layer CAN:") 121 | print("====================") 122 | status, res = x.try_command(x.getSlaveID, 0) 123 | if status == TryCommandResult.OK: 124 | print("CAN identifier for CMD/STIM:\n", res) 125 | else: 126 | pass 127 | # print("*** GET_SLAVE_ID() IS NOT SUPPORTED.") # no response from bc address ??? 128 | 129 | print("\nPer DAQ-list Identifier") 130 | print("-----------------------") 131 | daq_id = 0 132 | while True: 133 | status, res = x.try_command(x.getDaqId, daq_id) 134 | if status == TryCommandResult.OK: 135 | print(f"DAQ-list #{daq_id}:", res) 136 | daq_id += 1 137 | else: 138 | break 139 | if daq_id == 0: 140 | print("N/A") 141 | x.disconnect() 142 | print("\nDone.") 143 | 144 | 145 | if __name__ == "__main__": 146 | main() 147 | -------------------------------------------------------------------------------- /pyxcp/scripts/xcp_profile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Create / convert pyxcp profiles (configurations). 3 | """ 4 | 5 | import sys 6 | 7 | from pyxcp.cmdline import ArgumentParser 8 | 9 | 10 | def main(): 11 | if len(sys.argv) == 1: 12 | sys.argv.append("profile") 13 | elif len(sys.argv) >= 2 and sys.argv[1] != "profile": 14 | sys.argv.insert(1, "profile") 15 | 16 | ap = ArgumentParser(description="Create / convert pyxcp profiles (configurations).") 17 | 18 | try: 19 | with ap.run() as x: # noqa: F841 20 | pass 21 | except FileNotFoundError as e: 22 | print(f"Error: {e}") 23 | sys.exit(1) 24 | 25 | 26 | if __name__ == "__main__": 27 | main() 28 | -------------------------------------------------------------------------------- /pyxcp/scripts/xmraw_converter.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | from pyxcp.recorder.converter import convert_xmraw 4 | 5 | 6 | parser = argparse.ArgumentParser(description="Convert .xmraw files.") 7 | 8 | parser.add_argument( 9 | "target_type", 10 | help="target file type", 11 | choices=[ 12 | "arrow", 13 | "csv", 14 | "excel", 15 | "hdf5", 16 | "mdf", 17 | "sqlite3", 18 | ], 19 | ) 20 | 21 | 22 | def main(): 23 | parser.add_argument("xmraw_file", help=".xmraw file") 24 | parser.add_argument("-t", "--taget-file-name", dest="target_file_name", help="target file name") 25 | args = parser.parse_args() 26 | 27 | convert_xmraw(args.target_type, args.xmraw_file, args.target_file_name) 28 | 29 | 30 | if __name__ == "__main__": 31 | main() 32 | -------------------------------------------------------------------------------- /pyxcp/stim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/stim/__init__.py -------------------------------------------------------------------------------- /pyxcp/tests/test_asam_types.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyxcp.asam import types 4 | 5 | 6 | def testEncodeUint32_0(): 7 | assert types.A_Uint32("<").encode(3415750566) == b"\xa67\x98\xcb" 8 | 9 | 10 | def testDecodeUint32_0(): 11 | assert types.A_Uint32("<").decode((0xA6, 0x37, 0x98, 0xCB)) == 3415750566 12 | 13 | 14 | def testLittleEndian(): 15 | assert isinstance(types.AsamBaseType(types.INTEL), types.AsamBaseType) 16 | 17 | 18 | def testBigEndian(): 19 | assert isinstance(types.AsamBaseType(types.MOTOROLA), types.AsamBaseType) 20 | 21 | 22 | def testInvalidByteOrderRaisesTypeError(): 23 | with pytest.raises(ValueError): 24 | types.AsamBaseType("#") 25 | -------------------------------------------------------------------------------- /pyxcp/tests/test_checksum.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyxcp import checksum 4 | 5 | 6 | """ 7 | XCP_ADD_11 0x10 0x10 8 | XCP_ADD_12 0x0F10 0x0F10 9 | XCP_ADD_14 0x00000F10 0x00000F10 10 | XCP_ADD_22 0x1800 0x0710 11 | XCP_ADD_24 0x00071800 0x00080710 12 | XCP_ADD_44 0x140C03F8 0xFC040B10 13 | 14 | XCP_CRC_16 0xC76A 0xC76A 15 | XCP_CRC_16_CITT 0x9D50 0x9D50 16 | XCP_CRC_32 0x89CD97CE 0x89CD97CE 17 | """ 18 | 19 | TEST = bytes( 20 | ( 21 | 0x01, 22 | 0x02, 23 | 0x03, 24 | 0x04, 25 | 0x05, 26 | 0x06, 27 | 0x07, 28 | 0x08, 29 | 0x09, 30 | 0x0A, 31 | 0x0B, 32 | 0x0C, 33 | 0x0D, 34 | 0x0E, 35 | 0x0F, 36 | 0x10, 37 | 0xF1, 38 | 0xF2, 39 | 0xF3, 40 | 0xF4, 41 | 0xF5, 42 | 0xF6, 43 | 0xF7, 44 | 0xF8, 45 | 0xF9, 46 | 0xFA, 47 | 0xFB, 48 | 0xFC, 49 | 0xFD, 50 | 0xFE, 51 | 0xFF, 52 | 0x00, 53 | ) 54 | ) 55 | 56 | 57 | def testAdd11(): 58 | assert checksum.check(TEST, "XCP_ADD_11") == 0x10 59 | 60 | 61 | def testAdd12(): 62 | assert checksum.check(TEST, "XCP_ADD_12") == 0x0F10 63 | 64 | 65 | def testAdd14(): 66 | assert checksum.check(TEST, "XCP_ADD_14") == 0x00000F10 67 | 68 | 69 | def testAdd22(): 70 | assert checksum.check(TEST, "XCP_ADD_22") == 0x1800 71 | 72 | 73 | def testAdd24(): 74 | assert checksum.check(TEST, "XCP_ADD_24") == 0x00071800 75 | 76 | 77 | def testAdd44(): 78 | assert checksum.check(TEST, "XCP_ADD_44") == 0x140C03F8 79 | 80 | 81 | def testCrc16(): 82 | assert checksum.check(TEST, "XCP_CRC_16") == 0xC76A 83 | 84 | 85 | def testCrc16Ccitt(): 86 | assert checksum.check(TEST, "XCP_CRC_16_CITT") == 0x9D50 87 | 88 | 89 | def testCrc32(): 90 | assert checksum.check(TEST, "XCP_CRC_32") == 0x89CD97CE 91 | 92 | 93 | def testUserDefined(): 94 | with pytest.raises(NotImplementedError): 95 | checksum.check(TEST, "XCP_USER_DEFINED") 96 | -------------------------------------------------------------------------------- /pyxcp/tests/test_daq.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from pyxcp.daq_stim import DaqList, DaqProcessor 4 | 5 | 6 | DAQ_INFO = { 7 | "channels": [ 8 | { 9 | "cycle": 0, 10 | "maxDaqList": 1, 11 | "name": "Key T", 12 | "priority": 0, 13 | "properties": {"consistency": "CONSISTENCY_ODT", "daq": True, "packed": False, "stim": False}, 14 | "unit": "EVENT_CHANNEL_TIME_UNIT_1MS", 15 | }, 16 | { 17 | "cycle": 10, 18 | "maxDaqList": 1, 19 | "name": "10 ms", 20 | "priority": 1, 21 | "properties": {"consistency": "CONSISTENCY_ODT", "daq": True, "packed": False, "stim": True}, 22 | "unit": "EVENT_CHANNEL_TIME_UNIT_1MS", 23 | }, 24 | { 25 | "cycle": 100, 26 | "maxDaqList": 1, 27 | "name": "100ms", 28 | "priority": 2, 29 | "properties": {"consistency": "CONSISTENCY_ODT", "daq": True, "packed": False, "stim": True}, 30 | "unit": "EVENT_CHANNEL_TIME_UNIT_1MS", 31 | }, 32 | { 33 | "cycle": 1, 34 | "maxDaqList": 1, 35 | "name": "1ms", 36 | "priority": 3, 37 | "properties": {"consistency": "CONSISTENCY_ODT", "daq": True, "packed": False, "stim": True}, 38 | "unit": "EVENT_CHANNEL_TIME_UNIT_1MS", 39 | }, 40 | { 41 | "cycle": 10, 42 | "maxDaqList": 1, 43 | "name": "FilterBypassDaq", 44 | "priority": 4, 45 | "properties": {"consistency": "CONSISTENCY_ODT", "daq": True, "packed": False, "stim": True}, 46 | "unit": "EVENT_CHANNEL_TIME_UNIT_1MS", 47 | }, 48 | { 49 | "cycle": 10, 50 | "maxDaqList": 1, 51 | "name": "FilterBypassStim", 52 | "priority": 5, 53 | "properties": {"consistency": "CONSISTENCY_ODT", "daq": False, "packed": False, "stim": True}, 54 | "unit": "EVENT_CHANNEL_TIME_UNIT_1MS", 55 | }, 56 | ], 57 | "processor": { 58 | "keyByte": { 59 | "addressExtension": "AE_DIFFERENT_WITHIN_ODT", 60 | "identificationField": "IDF_REL_ODT_NUMBER_ABS_DAQ_LIST_NUMBER_BYTE", 61 | "optimisationType": "OM_DEFAULT", 62 | }, 63 | "maxDaq": 0, 64 | "minDaq": 0, 65 | "properties": { 66 | "bitStimSupported": False, 67 | "configType": "DYNAMIC", 68 | "overloadEvent": False, 69 | "overloadMsb": True, 70 | "pidOffSupported": False, 71 | "prescalerSupported": True, 72 | "resumeSupported": True, 73 | "timestampSupported": True, 74 | }, 75 | }, 76 | "resolution": { 77 | "granularityOdtEntrySizeDaq": 1, 78 | "granularityOdtEntrySizeStim": 1, 79 | "maxOdtEntrySizeDaq": 218, 80 | "maxOdtEntrySizeStim": 218, 81 | "timestampMode": {"fixed": False, "size": "S4", "unit": "DAQ_TIMESTAMP_UNIT_10US"}, 82 | "timestampTicks": 10, 83 | }, 84 | } 85 | 86 | SLAVE_INFO = { 87 | "addressGranularity": 0, 88 | "byteOrder": 0, 89 | "interleavedMode": False, 90 | "masterBlockMode": True, 91 | "maxBs": 2, 92 | "maxCto": 255, 93 | "maxDto": 1500, 94 | "maxWriteDaqMultipleElements": 31, 95 | "minSt": 0, 96 | "optionalCommMode": True, 97 | "pgmProcessor": {}, 98 | "protocolLayerVersion": 1, 99 | "queueSize": 0, 100 | "slaveBlockMode": True, 101 | "supportsCalpag": True, 102 | "supportsDaq": True, 103 | "supportsPgm": True, 104 | "supportsStim": True, 105 | "transportLayerVersion": 1, 106 | "xcpDriverVersionNumber": 25, 107 | } 108 | 109 | 110 | class AttrDict(dict): 111 | def __getattr__(self, name): 112 | return self[name] 113 | 114 | 115 | class MockMaster: 116 | def __init__(self): 117 | self.slaveProperties = AttrDict( 118 | { 119 | "maxDto": 1500, 120 | "supportsDaq": True, 121 | } 122 | ) 123 | 124 | def getDaqInfo(self): 125 | return DAQ_INFO 126 | 127 | def freeDaq(self): 128 | pass 129 | 130 | def allocDaq(self, daq_count): 131 | self.daq_count = daq_count 132 | 133 | def allocOdt(self, daq_num, odt_count): 134 | pass 135 | 136 | def allocOdtEntry(self, daq_num, odt_num, entry_count): 137 | pass 138 | 139 | def setDaqPtr(self, daqListNumber, odtNumber, odtEntryNumber): 140 | pass 141 | 142 | def writeDaq(self, bitOffset, entrySize, addressExt, address): 143 | pass 144 | 145 | def setDaqListMode(self, mode, daqListNumber, eventChannelNumber, prescaler, priority): 146 | pass 147 | 148 | def startStopDaqList(self, mode, daqListNumber): 149 | pass 150 | 151 | def startStopSynch(self, mode): 152 | pass 153 | 154 | 155 | DAQ_LISTS = [ 156 | DaqList( 157 | "list1", 158 | 1, 159 | False, 160 | True, 161 | [ 162 | ("channel1", 0x1BD004, 0, "U32"), 163 | ("channel2", 0x1BD008, 0, "U32"), 164 | ("PWMFiltered", 0x1BDDE2, 0, "U8"), 165 | ("PWM", 0x1BDDDF, 0, "U8"), 166 | ("Triangle", 0x1BDDDE, 0, "U8"), 167 | ], 168 | ), 169 | DaqList( 170 | "list2", 171 | 3, 172 | False, 173 | True, 174 | [ 175 | ("TestWord_002", 0x1BE124, 0, "U16"), 176 | ("TestWord_003", 0x1BE128, 0, "U16"), 177 | ("TestWord_001", 0x1BE120, 0, "U16"), 178 | ("TestWord_003", 0x1BE128, 0, "U16"), 179 | ("TestWord_004", 0x1BE12C, 0, "U16"), 180 | ("TestWord_005", 0x1BE134, 0, "U16"), 181 | ("TestWord_006", 0x1BE134, 0, "U16"), 182 | ("TestWord_007", 0x1BE138, 0, "U16"), 183 | ("TestWord_008", 0x1BE13C, 0, "U16"), 184 | ("TestWord_009", 0x1BE140, 0, "U16"), 185 | ("TestWord_011", 0x1BE148, 0, "U16"), 186 | ], 187 | ), 188 | ] 189 | 190 | # daq = DaqProcessor(DAQ_LISTS) 191 | # daq.set_master(MockMaster()) 192 | # daq.setup() 193 | # daq.start() 194 | -------------------------------------------------------------------------------- /pyxcp/tests/test_frame_padding.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | from pyxcp.transport.can import pad_frame 4 | 5 | 6 | def test_frame_padding_no_padding_length(): 7 | EXPECTED = ( 8 | (0, 8), 9 | (1, 8), 10 | (2, 8), 11 | (3, 8), 12 | (4, 8), 13 | (5, 8), 14 | (6, 8), 15 | (7, 8), 16 | (8, 8), 17 | (9, 12), 18 | (10, 12), 19 | (11, 12), 20 | (12, 12), 21 | (13, 16), 22 | (14, 16), 23 | (15, 16), 24 | (16, 16), 25 | (17, 20), 26 | (18, 20), 27 | (19, 20), 28 | (20, 20), 29 | (21, 24), 30 | (22, 24), 31 | (23, 24), 32 | (24, 24), 33 | (25, 32), 34 | (26, 32), 35 | (27, 32), 36 | (28, 32), 37 | (29, 32), 38 | (30, 32), 39 | (31, 32), 40 | (32, 32), 41 | (33, 48), 42 | (34, 48), 43 | (35, 48), 44 | (36, 48), 45 | (37, 48), 46 | (38, 48), 47 | (39, 48), 48 | (40, 48), 49 | (41, 48), 50 | (42, 48), 51 | (43, 48), 52 | (44, 48), 53 | (45, 48), 54 | (46, 48), 55 | (47, 48), 56 | (48, 48), 57 | (49, 64), 58 | (50, 64), 59 | (51, 64), 60 | (52, 64), 61 | (53, 64), 62 | (54, 64), 63 | (55, 64), 64 | (56, 64), 65 | (57, 64), 66 | (58, 64), 67 | (59, 64), 68 | (60, 64), 69 | (61, 64), 70 | (62, 64), 71 | (63, 64), 72 | (64, 64), 73 | ) 74 | for frame_len in range(65): 75 | frame = bytes(frame_len) 76 | padded_frame = pad_frame(frame, padding_value=0, pad_frame=True) 77 | frame_len = len(padded_frame) 78 | _, expected_len = EXPECTED[frame_len] 79 | assert frame_len == expected_len 80 | 81 | 82 | @pytest.mark.skip 83 | def test_frame_padding_padding_length32(): 84 | EXPECTED = ( 85 | (0, 32), 86 | (1, 32), 87 | (2, 32), 88 | (3, 32), 89 | (4, 32), 90 | (5, 32), 91 | (6, 32), 92 | (7, 32), 93 | (8, 32), 94 | (9, 32), 95 | (10, 32), 96 | (11, 32), 97 | (12, 32), 98 | (13, 32), 99 | (14, 32), 100 | (15, 32), 101 | (16, 32), 102 | (17, 32), 103 | (18, 32), 104 | (19, 32), 105 | (20, 32), 106 | (21, 32), 107 | (22, 32), 108 | (23, 32), 109 | (24, 32), 110 | (25, 32), 111 | (26, 32), 112 | (27, 32), 113 | (28, 32), 114 | (29, 32), 115 | (30, 32), 116 | (31, 32), 117 | (32, 32), 118 | (33, 48), 119 | (34, 48), 120 | (35, 48), 121 | (36, 48), 122 | (37, 48), 123 | (38, 48), 124 | (39, 48), 125 | (40, 48), 126 | (41, 48), 127 | (42, 48), 128 | (43, 48), 129 | (44, 48), 130 | (45, 48), 131 | (46, 48), 132 | (47, 48), 133 | (48, 48), 134 | (49, 64), 135 | (50, 64), 136 | (51, 64), 137 | (52, 64), 138 | (53, 64), 139 | (54, 64), 140 | (55, 64), 141 | (56, 64), 142 | (57, 64), 143 | (58, 64), 144 | (59, 64), 145 | (60, 64), 146 | (61, 64), 147 | (62, 64), 148 | (63, 64), 149 | (64, 64), 150 | ) 151 | for frame_len in range(65): 152 | frame = bytes(frame_len) 153 | padded_frame = pad_frame(frame, padding_value=0, pad_frame=True) 154 | frame_len = len(padded_frame) 155 | _, expected_len = EXPECTED[frame_len] 156 | assert frame_len == expected_len 157 | -------------------------------------------------------------------------------- /pyxcp/tests/test_transport.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import pyxcp.transport.base as tr 4 | 5 | 6 | def test_factory_works(): 7 | assert isinstance(tr.create_transport("eth"), tr.BaseTransport) 8 | assert isinstance(tr.create_transport("sxi"), tr.BaseTransport) 9 | assert isinstance( 10 | tr.create_transport( 11 | "can", 12 | config={ 13 | "CAN_ID_MASTER": 1, 14 | "CAN_ID_SLAVE": 2, 15 | "CAN_DRIVER": "MockCanInterface", 16 | }, 17 | ), 18 | tr.BaseTransport, 19 | ) 20 | 21 | 22 | def test_factory_works_case_insensitive(): 23 | assert isinstance(tr.create_transport("ETH"), tr.BaseTransport) 24 | assert isinstance(tr.create_transport("SXI"), tr.BaseTransport) 25 | assert isinstance( 26 | tr.create_transport( 27 | "CAN", 28 | config={ 29 | "CAN_ID_MASTER": 1, 30 | "CAN_ID_SLAVE": 2, 31 | "CAN_DRIVER": "MockCanInterface", 32 | }, 33 | ), 34 | tr.BaseTransport, 35 | ) 36 | 37 | 38 | def test_factory_invalid_transport_name_raises(): 39 | with pytest.raises(ValueError): 40 | tr.create_transport("xCp") 41 | 42 | 43 | def test_transport_names(): 44 | transports = tr.available_transports() 45 | 46 | assert "can" in transports 47 | assert "eth" in transports 48 | assert "sxi" in transports 49 | 50 | 51 | def test_transport_names_are_lower_case_only(): 52 | transports = tr.available_transports() 53 | 54 | assert "CAN" not in transports 55 | assert "ETH" not in transports 56 | assert "SXI" not in transports 57 | 58 | 59 | def test_transport_classes(): 60 | transports = tr.available_transports() 61 | 62 | assert issubclass(transports.get("can"), tr.BaseTransport) 63 | assert issubclass(transports.get("eth"), tr.BaseTransport) 64 | assert issubclass(transports.get("sxi"), tr.BaseTransport) 65 | -------------------------------------------------------------------------------- /pyxcp/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | from sys import version_info 2 | 3 | from pyxcp.utils import PYTHON_VERSION, flatten, getPythonVersion, hexDump, slicer 4 | 5 | 6 | def test_hexdump(capsys): 7 | print(hexDump(range(16)), end="") 8 | captured = capsys.readouterr() 9 | assert captured.out == "[00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f]" 10 | 11 | 12 | def test_slicer1(): 13 | res = slicer([1, 2, 3, 4, 5, 6, 7, 8], 4) 14 | assert res == [[1, 2, 3, 4], [5, 6, 7, 8]] 15 | 16 | 17 | def test_slicer2(): 18 | res = slicer(["10", "20", "30", "40", "50", "60", "70", "80"], 4, tuple) 19 | assert res == [("10", "20", "30", "40"), ("50", "60", "70", "80")] 20 | 21 | 22 | def test_flatten1(): 23 | res = flatten([[1, 2, 3, 4], [5, 6, 7, 8]]) 24 | assert res == [1, 2, 3, 4, 5, 6, 7, 8] 25 | 26 | 27 | def test_version(): 28 | assert getPythonVersion() == version_info 29 | assert PYTHON_VERSION == version_info 30 | assert getPythonVersion() == PYTHON_VERSION 31 | -------------------------------------------------------------------------------- /pyxcp/timing.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import time 3 | 4 | 5 | class Timing: 6 | T_US = 1000 * 1000 7 | T_MS = 1000 8 | T_S = 1 9 | 10 | UNIT_MAP = { 11 | T_US: "uS", 12 | T_MS: "mS", 13 | T_S: "S", 14 | } 15 | FMT = "min: {0:2.3f} {4}\nmax: {1:2.3f} {4}\n" "avg: {2:2.3f} {4}\nlast: {3:2.3f} {4}" 16 | 17 | def __init__(self, unit=T_MS, record=False): 18 | self.min = None 19 | self.max = None 20 | self.avg = None 21 | self._previous = None 22 | self.unit = unit 23 | self._record = record 24 | self._values = [] 25 | 26 | def start(self): 27 | self._start = time.perf_counter() 28 | 29 | def stop(self): 30 | self._stop = time.perf_counter() 31 | elapsed = self._stop - self._start 32 | if self._record: 33 | self._values.append(elapsed) 34 | if self._previous: 35 | self.min = min(self._previous, elapsed) 36 | self.max = max(self._previous, elapsed) 37 | self.avg = (self._previous + elapsed) / 2 38 | else: 39 | self.min = self.max = self.avg = elapsed 40 | self._previous = elapsed 41 | 42 | def __str__(self): 43 | unitName = Timing.UNIT_MAP.get(self.unit, "??") 44 | self.min = 0 if self.min is None else self.min 45 | self.max = 0 if self.max is None else self.max 46 | self.avg = 0 if self.avg is None else self.avg 47 | self._previous = 0 if self._previous is None else self._previous 48 | return Timing.FMT.format( 49 | self.min * self.unit, 50 | self.max * self.unit, 51 | self.avg * self.unit, 52 | self._previous * self.unit, 53 | unitName, 54 | ) 55 | 56 | __repr__ = __str__ 57 | 58 | @property 59 | def values(self): 60 | return self._values 61 | -------------------------------------------------------------------------------- /pyxcp/transport/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from .base import FrameAcquisitionPolicy # noqa: F401 3 | from .base import FrameRecorderPolicy # noqa: F401 4 | from .base import LegacyFrameAcquisitionPolicy # noqa: F401 5 | from .base import NoOpPolicy # noqa: F401 6 | from .base import StdoutPolicy # noqa: F401 7 | from .can import Can # noqa: F401 8 | from .eth import Eth # noqa: F401 9 | from .sxi import SxI # noqa: F401 10 | from .usb_transport import Usb # noqa: F401 11 | -------------------------------------------------------------------------------- /pyxcp/transport/base_transport.hpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/transport/base_transport.hpp -------------------------------------------------------------------------------- /pyxcp/transport/sxi.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from collections import deque 3 | from dataclasses import dataclass 4 | from typing import Optional 5 | 6 | import serial 7 | 8 | import pyxcp.types as types 9 | from pyxcp.transport.base import BaseTransport 10 | 11 | 12 | @dataclass 13 | class HeaderValues: 14 | length: int = 0 15 | counter: int = 0 16 | filler: int = 0 17 | 18 | 19 | RECV_SIZE = 16384 20 | 21 | 22 | class SxI(BaseTransport): 23 | """""" 24 | 25 | def __init__(self, config=None, policy=None, transport_layer_interface: Optional[serial.Serial] = None) -> None: 26 | super().__init__(config, policy, transport_layer_interface) 27 | self.load_config(config) 28 | self.port_name = self.config.port 29 | self.baudrate = self.config.bitrate 30 | self.bytesize = self.config.bytesize 31 | self.parity = self.config.parity 32 | self.stopbits = self.config.stopbits 33 | self.mode = self.config.mode 34 | self.header_format = self.config.header_format 35 | self.tail_format = self.config.tail_format 36 | self.framing = self.config.framing 37 | self.esc_sync = self.config.esc_sync 38 | self.esc_esc = self.config.esc_esc 39 | self.make_header() 40 | self.comm_port: serial.Serial 41 | 42 | if self.has_user_supplied_interface and transport_layer_interface: 43 | self.comm_port = transport_layer_interface 44 | else: 45 | self.logger.info(f"XCPonSxI - trying to open serial comm_port {self.port_name}.") 46 | try: 47 | self.comm_port = serial.Serial( 48 | port=self.port_name, 49 | baudrate=self.baudrate, 50 | bytesize=self.bytesize, 51 | parity=self.parity, 52 | stopbits=self.stopbits, 53 | timeout=self.timeout, 54 | write_timeout=self.timeout, 55 | ) 56 | except serial.SerialException as e: 57 | self.logger.critical(f"XCPonSxI - {e}") 58 | raise 59 | self._packets = deque() 60 | 61 | def __del__(self) -> None: 62 | self.close_connection() 63 | 64 | def make_header(self) -> None: 65 | def unpack_len(args): 66 | (length,) = args 67 | return HeaderValues(length=length) 68 | 69 | def unpack_len_counter(args): 70 | length, counter = args 71 | return HeaderValues(length=length, counter=counter) 72 | 73 | def unpack_len_filler(args): 74 | length, filler = args 75 | return HeaderValues(length=length, filler=filler) 76 | 77 | HEADER_FORMATS = { 78 | "HEADER_LEN_BYTE": ("B", unpack_len), 79 | "HEADER_LEN_CTR_BYTE": ("BB", unpack_len_counter), 80 | "HEADER_LEN_FILL_BYTE": ("BB", unpack_len_filler), 81 | "HEADER_LEN_WORD": ("H", unpack_len), 82 | "HEADER_LEN_CTR_WORD": ("HH", unpack_len_counter), 83 | "HEADER_LEN_FILL_WORD": ("HH", unpack_len_filler), 84 | } 85 | fmt, unpacker = HEADER_FORMATS[self.header_format] 86 | self.HEADER = struct.Struct(f"<{fmt}") 87 | self.HEADER_SIZE = self.HEADER.size 88 | self.unpacker = unpacker 89 | 90 | def connect(self) -> None: 91 | self.logger.info(f"XCPonSxI - serial comm_port openend: {self.comm_port.portstr}@{self.baudrate} Bits/Sec.") 92 | self.start_listener() 93 | 94 | def output(self, enable) -> None: 95 | if enable: 96 | self.comm_port.rts = False 97 | self.comm_port.dtr = False 98 | else: 99 | self.comm_port.rts = True 100 | self.comm_port.dtr = True 101 | 102 | def flush(self) -> None: 103 | self.comm_port.flush() 104 | 105 | def start_listener(self) -> None: 106 | super().start_listener() 107 | 108 | def listen(self) -> None: 109 | while True: 110 | if self.closeEvent.is_set(): 111 | return 112 | if not self.comm_port.in_waiting: 113 | continue 114 | 115 | recv_timestamp = self.timestamp.value 116 | header_values = self.unpacker(self.HEADER.unpack(self.comm_port.read(self.HEADER_SIZE))) 117 | length, counter, _ = header_values.length, header_values.counter, header_values.filler 118 | 119 | response = self.comm_port.read(length) 120 | self.timing.stop() 121 | 122 | if len(response) != length: 123 | raise types.FrameSizeError("Size mismatch.") 124 | self.process_response(response, length, counter, recv_timestamp) 125 | 126 | def send(self, frame) -> None: 127 | self.pre_send_timestamp = self.timestamp.value 128 | self.comm_port.write(frame) 129 | self.post_send_timestamp = self.timestamp.value 130 | 131 | def close_connection(self) -> None: 132 | if hasattr(self, "comm_port") and self.comm_port.is_open and not self.has_user_supplied_interface: 133 | self.comm_port.close() 134 | -------------------------------------------------------------------------------- /pyxcp/transport/transport_wrapper.cpp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/transport/transport_wrapper.cpp -------------------------------------------------------------------------------- /pyxcp/utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import datetime 3 | from enum import IntEnum 4 | import functools 5 | import operator 6 | import sys 7 | from binascii import hexlify 8 | from time import perf_counter, sleep 9 | 10 | import chardet 11 | import pytz 12 | 13 | from pyxcp.cpp_ext.cpp_ext import TimestampInfo 14 | 15 | 16 | def hexDump(arr): 17 | if isinstance(arr, (bytes, bytearray)): 18 | size = len(arr) 19 | try: 20 | arr = arr.hex() 21 | except BaseException: # noqa: B036 22 | arr = hexlify(arr).decode("ascii") 23 | return "[{}]".format(" ".join([arr[i * 2 : (i + 1) * 2] for i in range(size)])) 24 | elif isinstance(arr, (list, tuple)): 25 | arr = bytes(arr) 26 | size = len(arr) 27 | try: 28 | arr = arr.hex() 29 | except BaseException: # noqa: B036 30 | arr = hexlify(arr).decode("ascii") 31 | return "[{}]".format(" ".join([arr[i * 2 : (i + 1) * 2] for i in range(size)])) 32 | else: 33 | return "[{}]".format(" ".join([f"{x:02x}" for x in arr])) 34 | 35 | 36 | def seconds_to_nanoseconds(value: float) -> int: 37 | return int(value * 1_000_000_000) 38 | 39 | 40 | def slicer(iterable, sliceLength, converter=None): 41 | if converter is None: 42 | converter = type(iterable) 43 | length = len(iterable) 44 | return [converter(iterable[item : item + sliceLength]) for item in range(0, length, sliceLength)] 45 | 46 | 47 | def functools_reduce_iconcat(a): 48 | return functools.reduce(operator.iconcat, a, []) 49 | 50 | 51 | def flatten(*args): 52 | """Flatten a list of lists into a single list. 53 | 54 | s. https://stackoverflow.com/questions/952914/how-do-i-make-a-flat-list-out-of-a-list-of-lists 55 | """ 56 | return functools.reduce(operator.iconcat, args, []) 57 | 58 | 59 | def getPythonVersion(): 60 | return sys.version_info 61 | 62 | 63 | def decode_bytes(byte_str: bytes) -> str: 64 | """Decode bytes with the help of chardet""" 65 | encoding = chardet.detect(byte_str).get("encoding") 66 | if not encoding: 67 | return byte_str.decode("ascii", "ignore") 68 | else: 69 | return byte_str.decode(encoding) 70 | 71 | 72 | PYTHON_VERSION = getPythonVersion() 73 | 74 | 75 | def short_sleep(): 76 | sleep(0.0005) 77 | 78 | 79 | def delay(amount: float): 80 | """Performe a busy-wait delay, which is much more precise than `time.sleep`""" 81 | 82 | start = perf_counter() 83 | while perf_counter() < start + amount: 84 | pass 85 | 86 | 87 | class CurrentDatetime(TimestampInfo): 88 | 89 | def __init__(self, timestamp_ns: int): 90 | TimestampInfo.__init__(self, timestamp_ns) 91 | timezone = pytz.timezone(self.timezone) 92 | dt = datetime.datetime.fromtimestamp(timestamp_ns / 1_000_000_000.0) 93 | self.utc_offset = int(timezone.utcoffset(dt).total_seconds() / 60) 94 | self.dst_offset = int(timezone.dst(dt).total_seconds() / 60) 95 | 96 | def __str__(self): 97 | return f"""CurrentDatetime( 98 | datetime="{datetime.datetime.fromtimestamp(self.timestamp_ns / 1_000_000_000.0)!s}", 99 | timezone="{self.timezone}", 100 | timestamp_ns={self.timestamp_ns}, 101 | utc_offset={self.utc_offset}, 102 | dst_offset={self.dst_offset} 103 | )""" 104 | 105 | 106 | def enum_from_str(enum_class: IntEnum, enumerator: str) -> IntEnum: 107 | """Create an `IntEnum` instance from an enumerator `str`. 108 | 109 | Parameters 110 | ---------- 111 | enum_class: IntEnum 112 | 113 | enumerator: str 114 | 115 | Example 116 | ------- 117 | 118 | class Color(enum.IntEnum): 119 | RED = 0 120 | GREEN = 1 121 | BLUE = 2 122 | 123 | color: Color = enum_from_str(Color, "GREEN") 124 | 125 | 126 | """ 127 | return enum_class(enum_class.__members__.get(enumerator)) 128 | -------------------------------------------------------------------------------- /pyxcp/vector/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/christoph2/pyxcp/47645d9a5fc5b8b383367c3212cdc1a392ab602c/pyxcp/vector/__init__.py -------------------------------------------------------------------------------- /pyxcp/vector/map.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | MAP_NAMES = { 4 | 1: "BorlandC 16 Bit", 5 | 2: "M166", 6 | 3: "Watcom", 7 | 4: "HiTech HC05", 8 | 6: "IEEE", 9 | 7: "Cosmic", 10 | 8: "SDS", 11 | 9: "Fujitsu Softune 1(.mp1)", 12 | 10: "GNU", 13 | 11: "Keil 16x", 14 | 12: "BorlandC 32 Bit", 15 | 13: "Keil 16x (static)", 16 | 14: "Keil 8051", 17 | 15: "ISI", 18 | 16: "Hiware HC12", 19 | 17: "TI TMS470", 20 | 18: "Archimedes", 21 | 19: "COFF", 22 | 20: "IAR", 23 | 21: "VisualDSP", 24 | 22: "GNU 16x", 25 | 23: "GNU VxWorks", 26 | 24: "GNU 68k", 27 | 25: "DiabData", 28 | 26: "VisualDSP DOS", 29 | 27: "HEW SH7055", 30 | 28: "Metrowerks", 31 | 29: "Microsoft standard", 32 | 30: "ELF/DWARF 16 Bit", 33 | 31: "ELF/DWARF 32 Bit", 34 | 32: "Fujitsu Softune 3..8(.mps)", 35 | 33: "Microware Hawk", 36 | 34: "TI C6711", 37 | 35: "Hitachi H8S", 38 | 36: "IAR HC12", 39 | 37: "Greenhill Multi 2000", 40 | 38: "LN308(MITSUBISHI) for M16C/80", 41 | 39: "COFF settings auto detected", 42 | 40: "NEC CC78K/0 v35", 43 | 41: "Microsoft extended", 44 | 42: "ICCAVR", 45 | 43: "Omf96 (.m96)", 46 | 44: "COFF/DWARF", 47 | 45: "OMF96 Binary (Tasking C196)", 48 | 46: "OMF166 Binary (Keil C166)", 49 | 47: "Microware Hawk Plug&Play ASCII", 50 | 48: "UBROF Binary (IAR)", 51 | 49: "Renesas M32R/M32192 ASCII", 52 | 50: "OMF251 Binary (Keil C251)", 53 | 51: "Microsoft standard VC8", 54 | 52: "Microsoft VC8 Release Build (MATLAB DLL)", 55 | 53: "Microsoft VC8 Debug Build (MATLAB DLL)", 56 | 54: "Microsoft VC8 Debug file (pdb)", 57 | } 58 | 59 | """ 60 | 3.0 Automatic detection sequence 61 | 62 | - The master (CANape) sends the command GET_ID with the idType = 219 (0xDB hex) to the ECU 63 | - The ECU sets the MTA (memory transfer address) pointer to the first byte of the data block containing the 64 | MAP file identification and returns the number of bytes which need to be uploaded to the master as well as 65 | the counter which defines the appropriate memory segment 66 | - The master requests the specified number of bytes from the ECU by sending UPLOAD commands 67 | - The master looks for the appropriate MAP file in the MAP file directory and updates the addresses in the 68 | a2l file accordingly 69 | 70 | Command sequence GET_ID 71 | 72 | Id of the MAP format 73 | 74 | Counter 75 | 76 | MAP name 77 | 78 | """ 79 | 80 | 81 | def mapfile_name(name, counter, fmt): 82 | return f"{fmt:2d}{counter:d}{name:s}.map" 83 | -------------------------------------------------------------------------------- /reformat.cmd: -------------------------------------------------------------------------------- 1 | clang-format -i .\pyxcp\cpp_ext\*.cpp .\pyxcp\recorder\*.cpp .\pyxcp\cpp_ext\*.hpp .\pyxcp\recorder\*.hpp .\pyxcp\daq_stim\*.hpp .\pyxcp\daq_stim\*.cpp 2 | -------------------------------------------------------------------------------- /selective_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import sys 3 | from pathlib import Path 4 | 5 | 6 | """ 7 | dir_map 8 | file_map 9 | ignore_file 10 | ignore_dir 11 | """ 12 | 13 | 14 | def main(args): 15 | print("SELECTIVE_TESTS called with:", args) 16 | dirs = set() 17 | args = args[1:] 18 | with open("st.txt", "w") as of: 19 | for arg in args: 20 | parent = str(Path(arg).parent) 21 | dirs.add(parent) 22 | of.write(str(arg)) 23 | of.write("\t") 24 | of.write(parent) 25 | of.write("\n") 26 | 27 | 28 | if __name__ == "__main__": 29 | main(sys.argv) 30 | --------------------------------------------------------------------------------