pax_global_header 0000666 0000000 0000000 00000000064 15207004341 0014506 g ustar 00root root 0000000 0000000 52 comment=31eab59d09b9e9a91f28a9ff415dd1921f1ca9d9
vblf-0.3.0/ 0000775 0000000 0000000 00000000000 15207004341 0012437 5 ustar 00root root 0000000 0000000 vblf-0.3.0/.github/ 0000775 0000000 0000000 00000000000 15207004341 0013777 5 ustar 00root root 0000000 0000000 vblf-0.3.0/.github/dependabot.yml 0000664 0000000 0000000 00000001061 15207004341 0016625 0 ustar 00root root 0000000 0000000 version: 2
updates:
# Enable version updates for GitHub Actions
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
cooldown:
default-days: 7
groups:
github-actions:
patterns:
- "*"
# Enable version updates for development dependencies
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "monthly"
cooldown:
default-days: 7
versioning-strategy: "increase-if-necessary"
groups:
dev-deps:
patterns:
- "*"
vblf-0.3.0/.github/release.yml 0000664 0000000 0000000 00000000073 15207004341 0016142 0 ustar 00root root 0000000 0000000 changelog:
exclude:
authors:
- dependabot[bot]
vblf-0.3.0/.github/workflows/ 0000775 0000000 0000000 00000000000 15207004341 0016034 5 ustar 00root root 0000000 0000000 vblf-0.3.0/.github/workflows/ci.yml 0000664 0000000 0000000 00000007545 15207004341 0017165 0 ustar 00root root 0000000 0000000 name: CI
on:
pull_request:
push:
tags:
- 'v*'
branches-ignore:
- 'dependabot/**'
env:
PY_COLORS: "1"
permissions:
contents: read
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ "ubuntu-latest", "windows-latest", "macos-latest" ]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "pypy-3.11"]
fail-fast: false
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # 6.2.0
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tox
- name: Test
run: |
tox -e py
linter:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # 6.2.0
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tox
- name: Run ruff linter
run: |
tox -e format,lint,type
build:
name: Build package
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # 6.2.0
with:
python-version: "3.12"
- name: Install dependencies
run: python -m pip install build twine
- name: Build wheel and sdist
run: python -m build
- name: Check artifacts
run: twine check --strict dist/*
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # 7.0.1
with:
name: dist-artifact
path: dist/*
docs:
name: Build documentation
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # 6.0.2
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # 6.2.0
with:
python-version: "3.12"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install tox
- name: Build documentation
run: |
tox -e docs
- name: Upload documentation artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # 7.0.1
with:
name: doc-artifact
path: build/*
upload_pypi:
name: Publish
needs: build
runs-on: ubuntu-latest
environment:
name: pypi
url: https://pypi.org/p/vblf
permissions:
id-token: write # IMPORTANT: this permission is mandatory for trusted publishing
contents: write # for action-gh-release
attestations: write # for attest-build-provenance
# create a new release for tagged commits
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # 8.0.1
with:
name: dist-artifact
path: dist
- name: Generate artifact attestation
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # 4.1.0
with:
subject-path: 'dist/*'
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # 1.14.0
vblf-0.3.0/.gitignore 0000664 0000000 0000000 00000001242 15207004341 0014426 0 ustar 00root root 0000000 0000000 # Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Sphinx documentation
docs/_build/
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# IDEs
.idea/
.vscode/
vblf-0.3.0/.pre-commit-config.yaml 0000664 0000000 0000000 00000001566 15207004341 0016730 0 ustar 00root root 0000000 0000000 repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-added-large-files
args: ['--maxkb=1000']
- id: check-case-conflict
- id: check-merge-conflict
- id: check-symlinks
- id: check-yaml
- id: debug-statements
- id: end-of-file-fixer
- id: mixed-line-ending
- id: requirements-txt-fixer
- id: trailing-whitespace
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.15.15"
hooks:
# Run the linter.
- id: ruff-check
args: ["--fix", "--show-fixes"]
# Run the formatter.
- id: ruff-format
- repo: https://github.com/woodruffw/zizmor-pre-commit
# Zizmor version.
rev: v1.25.2
hooks:
# Run the linter.
- id: zizmor
- repo: https://github.com/twisted/towncrier
rev: 25.8.0
hooks:
- id: towncrier-check
vblf-0.3.0/.readthedocs.yaml 0000664 0000000 0000000 00000001147 15207004341 0015671 0 ustar 00root root 0000000 0000000 # Read the Docs configuration file
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
# Required
version: 2
# Set the OS, Python version, and other tools you might need
build:
os: ubuntu-24.04
tools:
python: "3.12"
# Build documentation in the "docs/" directory with Sphinx
sphinx:
configuration: docs/conf.py
# Optionally, but recommended,
# declare the Python requirements required to build your documentation
# See https://docs.readthedocs.io/en/stable/guides/reproducible-builds.html
python:
install:
- requirements: docs/requirements.txt
- method: pip
path: .
vblf-0.3.0/CHANGELOG.md 0000664 0000000 0000000 00000001733 15207004341 0014254 0 ustar 00root root 0000000 0000000 # Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
This project uses [*towncrier*](https://towncrier.readthedocs.io/) and the changes for the upcoming release can be found in .
## [v0.3.0](https://github.com/zariiii9003/vblf/tree/v0.3.0) - 2026-05-31
### Added
- SystemTime: add to_datetime() ([#18](https://github.com/zariiii9003/vblf/issues/18))
## [v0.2.1](https://github.com/zariiii9003/vblf/tree/v0.2.1) - 2025-10-21
### Added
- Implement EthernetFrameEx, EthernetStatistic and DiagRequestInterpretation. ([#10](https://github.com/zariiii9003/vblf/issues/10))
## [v0.1.0](https://github.com/zariiii9003/vblf/tree/v0.1.0) - 2025-06-20
### Added
- Initial implementation
vblf-0.3.0/CONTRIBUTING.md 0000664 0000000 0000000 00000006223 15207004341 0014673 0 ustar 00root root 0000000 0000000 # Contributing to vblf
Thank you for your interest in contributing to **vblf**! This guide will help you set up your development environment, run tests, ensure code quality, and add changelog entries for your contributions.
## 1. Clone the Repository
First, clone the repository from GitHub:
```bash
git clone https://github.com/zariiii9003/vblf.git
cd vblf
```
- This will create a local copy of the project and move you into the project directory.
## 2. Install Development Dependencies
To get started, install the development dependencies. These include tools for testing, linting, formatting, and documentation.
```bash
python -m pip install -e . --group dev
```
- This command installs the package in editable mode and all dependencies listed under the `dev` group in `pyproject.toml` (including `tox`, `towncrier`, linters, etc.).
## 3. Set Up Pre-commit Hooks
After installing development dependencies, set up pre-commit hooks to automatically check formatting and linting before each commit:
```bash
pre-commit install
```
- This command installs the git hooks defined in `.pre-commit-config.yaml` (if present), so checks like formatting and linting run automatically when you commit.
## 4. Running All Checks and Tests
To run all predefined checks and tests, simply run:
```bash
tox
```
- This will execute all environments defined in `tox.ini`: formatting, linting, type checking, tests, and documentation build. It's the easiest way to ensure your code passes all quality checks before submitting a contribution.
If you want to check only a specific aspect, you can run an individual environment:
- **Formatting:**
```bash
tox -e format
```
Checks code formatting using `ruff`.
- **Linting:**
```bash
tox -e lint
```
Runs `ruff` for linting and code style checks.
- **Type Checking:**
```bash
tox -e type
```
Runs `mypy` for static type checking.
- **Tests:**
```bash
tox -e py
```
Runs all tests using `pytest`.
- **Documentation:**
```bash
tox -e docs
```
Builds the documentation using Sphinx.
## 5. Adding a News Fragment (Changelog Entry)
This project uses [towncrier](https://towncrier.readthedocs.io/) to manage the changelog. For every user-facing change, add a news fragment in the `changelog.d/` directory.
- The filename should be `..md`, e.g., `123.added.md` or `456.fixed.md`. Both issue and pull request numbers are valid.
- Valid types are: `added`, `changed`, `deprecated`, `removed`, `fixed`, `security`.
- Write a short description of your change in the file.
You can create a news fragment manually, or from the command line using towncrier:
```bash
towncrier create -c "Fixed a bug!" 5.fixed.md
```
This will create a file `changelog.d/5.fixed.md` with the provided content.
Example:
```
changelog.d/123.added.md
```
Content:
```
Added support for new BLF message type.
```
When a release is made, these fragments are automatically compiled into `CHANGELOG.md`.
## 6. Submitting Your Contribution
- Ensure your code passes all checks and tests.
- Add a news fragment for your change.
- Open a pull request with a clear description of your changes.
Thank you for helping improve **vblf**!
vblf-0.3.0/LICENSE 0000664 0000000 0000000 00000002117 15207004341 0013445 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2024-present Artur Drogunow
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
vblf-0.3.0/README.md 0000664 0000000 0000000 00000003026 15207004341 0013717 0 ustar 00root root 0000000 0000000 # Binary Logging Format Library for Python
[](https://pypi.org/project/vblf)
[](https://pypi.org/project/vblf)
## Introduction
A Python library for reading and writing BLF files, a proprietary binary logging format of Vector Informatik GmbH. BLF files are commonly used in automotive logging and testing scenarios.
## Installation
```bash
pip install vblf
```
## Usage
### Reading BLF Files
```python
from vblf.can import CanMessage, CanMessage2
from vblf.reader import BlfReader
# Open a BLF file
with BlfReader("example.blf") as reader:
# Iterate through all objects in the file
for obj in reader:
print(f"Type: {obj.header.base.object_type.name}")
print(f"Timestamp: {obj.header.object_time_stamp}")
# Handle CAN messages
if isinstance(obj, (CanMessage, CanMessage2)):
print(f"CAN ID: {obj.id}")
print(f"Data: {obj.data.hex()}")
```
### Writing BLF Files
```python
from vblf.can import CanMessage
from vblf.writer import BlfWriter
# Create a new BLF file
with BlfWriter("output.blf") as writer:
# Create a CAN message
msg = CanMessage(...)
# Write the message to the file
writer.write(msg)
```
## License
This project is licensed under the MIT License.
## Acknowledgments
- Vector Informatik GmbH for the BLF file format
- Tobias Lorenz for his C++ library [vector_blf](https://bitbucket.org/tobylorenz/vector_blf/src/master/)
vblf-0.3.0/changelog.d/ 0000775 0000000 0000000 00000000000 15207004341 0014610 5 ustar 00root root 0000000 0000000 vblf-0.3.0/changelog.d/.gitignore 0000664 0000000 0000000 00000000142 15207004341 0016575 0 ustar 00root root 0000000 0000000 *
!.gitignore
!*.security.md
!*.removed.md
!*.deprecated.md
!*.added.md
!*.changed.md
!*.fixed.md
vblf-0.3.0/docs/ 0000775 0000000 0000000 00000000000 15207004341 0013367 5 ustar 00root root 0000000 0000000 vblf-0.3.0/docs/can.rst 0000664 0000000 0000000 00000000062 15207004341 0014660 0 ustar 00root root 0000000 0000000 CAN Objects
-----------
.. automodule:: vblf.can
vblf-0.3.0/docs/conf.py 0000664 0000000 0000000 00000003051 15207004341 0014665 0 ustar 00root root 0000000 0000000 # Configuration file for the Sphinx documentation builder.
#
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = "vblf"
copyright = "2024, Artur Drogunow" # noqa: A001
author = "Artur Drogunow"
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.intersphinx",
"sphinx.ext.viewcode",
]
templates_path = ["_templates"]
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# location of typehints
autodoc_typehints = "description"
# The default autodoc directive flags
autodoc_default_options = {
"members": True,
"show-inheritance": True,
"undoc-members": True,
"member-order": "bysource",
}
# decide whether module names are prepended to all object names
add_module_names = False
# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output
html_theme = "furo"
# -- Options for intersphinx extension ---------------------------------------
# https://www.sphinx-doc.org/en/master/usage/extensions/intersphinx.html#configuration
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
}
vblf-0.3.0/docs/constants.rst 0000664 0000000 0000000 00000000064 15207004341 0016135 0 ustar 00root root 0000000 0000000 Constants
---------
.. automodule:: vblf.constants
vblf-0.3.0/docs/ethernet.rst 0000664 0000000 0000000 00000000101 15207004341 0015727 0 ustar 00root root 0000000 0000000 Ethernet Objects
----------------
.. automodule:: vblf.ethernet
vblf-0.3.0/docs/flexray.rst 0000664 0000000 0000000 00000000076 15207004341 0015576 0 ustar 00root root 0000000 0000000 FlexRay Objects
---------------
.. automodule:: vblf.flexray
vblf-0.3.0/docs/general.rst 0000664 0000000 0000000 00000000076 15207004341 0015541 0 ustar 00root root 0000000 0000000 General Objects
---------------
.. automodule:: vblf.general
vblf-0.3.0/docs/index.rst 0000664 0000000 0000000 00000003607 15207004341 0015236 0 ustar 00root root 0000000 0000000 BLF Library for Python
======================
.. image:: https://img.shields.io/pypi/v/vblf.svg
:target: https://pypi.org/project/vblf
:alt: PyPI - Version
.. image:: https://img.shields.io/pypi/pyversions/vblf.svg
:target: https://pypi.org/project/vblf
:alt: PyPI - Python Version
.. toctree::
:maxdepth: 1
:caption: Contents:
reader
writer
general
can
ethernet
flexray
lin
most
constants
Introduction
------------
This is a Python library for reading and writing BLF files,
a proprietary binary logging format of Vector Informatik GmbH.
BLF files are commonly used in automotive logging and testing scenarios.
Installation
------------
.. code-block:: bash
pip install vblf
Usage
-----
Reading BLF Files
~~~~~~~~~~~~~~~~~
.. code-block:: python
from vblf.can import CanMessage, CanMessage2
from vblf.reader import BlfReader
# Open a BLF file
with BlfReader("example.blf") as reader:
# Iterate through all objects in the file
for obj in reader:
print(f"Type: {obj.header.base.object_type.name}")
print(f"Timestamp: {obj.header.object_time_stamp}")
# Handle CAN messages
if isinstance(obj, (CanMessage, CanMessage2)):
print(f"CAN ID: {obj.id}")
print(f"Data: {obj.data.hex()}")
Writing BLF Files
~~~~~~~~~~~~~~~~~
.. code-block:: python
from vblf.can import CanMessage
from vblf.writer import BlfWriter
# Create a new BLF file
with BlfWriter("output.blf") as writer:
# Create a CAN message
msg = CanMessage(...)
# Write the message to the file
writer.write(msg)
License
-------
This project is licensed under the MIT License.
Acknowledgments
---------------
- Vector Informatik GmbH for the BLF file format
- Tobias Lorenz for his C++ library `vector_blf `_
vblf-0.3.0/docs/lin.rst 0000664 0000000 0000000 00000000062 15207004341 0014701 0 ustar 00root root 0000000 0000000 LIN Objects
-----------
.. automodule:: vblf.lin
vblf-0.3.0/docs/most.rst 0000664 0000000 0000000 00000000065 15207004341 0015104 0 ustar 00root root 0000000 0000000 MOST Objects
------------
.. automodule:: vblf.most
vblf-0.3.0/docs/reader.rst 0000664 0000000 0000000 00000000061 15207004341 0015360 0 ustar 00root root 0000000 0000000 BlfReader
---------
.. automodule:: vblf.reader
vblf-0.3.0/docs/requirements.txt 0000664 0000000 0000000 00000000035 15207004341 0016651 0 ustar 00root root 0000000 0000000 furo>=2024.8.6
sphinx>=8.1.3
vblf-0.3.0/docs/writer.rst 0000664 0000000 0000000 00000000061 15207004341 0015432 0 ustar 00root root 0000000 0000000 BlfWriter
---------
.. automodule:: vblf.writer
vblf-0.3.0/pyproject.toml 0000664 0000000 0000000 00000005206 15207004341 0015356 0 ustar 00root root 0000000 0000000 [build-system]
requires = ["setuptools==82.*", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta"
[project]
name = "vblf"
description = "A library to read and write Vector BLF files (binary log format)"
readme = "README.md"
authors = [{ name = "Artur Drogunow", email = "Artur.Drogunow@zf.com" }]
license = { text = "MIT" }
requires-python = ">=3.9"
dependencies = [ "typing_extensions>=4.0.0" ]
dynamic = ["version"]
keywords = ["BLF", "Vector", "binary log format", "Automotive"]
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
]
[project.urls]
Issues = "https://github.com/zariiii9003/vblf/issues"
Source = "https://github.com/zariiii9003/vblf"
Homepage = "https://github.com/zariiii9003/vblf"
[tool.setuptools_scm]
# presence of this section triggers setuptools_scm to manage the version
[dependency-groups]
dev = [
"tox>=4.23.2",
"towncrier",
{include-group = "lint"},
]
test = [
"pytest==8.4.*",
]
lint = [
"ruff==0.15.*",
"mypy==1.19.*",
]
[tool.ruff]
line-length = 100
lint.extend-select = [
"A", # flake8-builtins
"B", # flake8-bugbear
"BLE", # flake8-blind-except
"C4", # flake8-comprehensions
"E", # pycodestyle
"EM", # flake8-errmsg
"F", # pyflakes
"FURB", # refurb
"G", # flake8-logging-format
"I", # isort
"N", # pep8-naming
"PERF", # Perflint
"PIE", # flake8-pie
"PL", # pylint
"RSE", # flake8-raise
"RUF", # Ruff-specific rules
"SIM", # flake8-simplify
"T20", # flake8-print
"TC", # flake8-type-checking
"UP", # pyupgrade
"W", # pycodestyle
]
lint.ignore = [
"PLR0913", # too-many-arguments
]
lint.per-file-ignores."tests/**.py" = [
"PL", # pylint
]
[tool.towncrier]
directory = "changelog.d"
filename = "CHANGELOG.md"
start_string = "\n"
underlines = ["", "", ""]
title_format = "## [{version}](https://github.com/zariiii9003/vblf/tree/{version}) - {project_date}"
issue_format = "[#{issue}](https://github.com/zariiii9003/vblf/issues/{issue})"
[[tool.towncrier.type]]
directory = "security"
name = "Security"
showcontent = true
[[tool.towncrier.type]]
directory = "removed"
name = "Removed"
showcontent = true
[[tool.towncrier.type]]
directory = "deprecated"
name = "Deprecated"
showcontent = true
[[tool.towncrier.type]]
directory = "added"
name = "Added"
showcontent = true
[[tool.towncrier.type]]
directory = "changed"
name = "Changed"
showcontent = true
[[tool.towncrier.type]]
directory = "fixed"
name = "Fixed"
showcontent = true
vblf-0.3.0/src/ 0000775 0000000 0000000 00000000000 15207004341 0013226 5 ustar 00root root 0000000 0000000 vblf-0.3.0/src/vblf/ 0000775 0000000 0000000 00000000000 15207004341 0014157 5 ustar 00root root 0000000 0000000 vblf-0.3.0/src/vblf/__init__.py 0000664 0000000 0000000 00000000242 15207004341 0016266 0 ustar 00root root 0000000 0000000 import contextlib
from importlib.metadata import PackageNotFoundError, version
with contextlib.suppress(PackageNotFoundError):
__version__ = version("vblf")
vblf-0.3.0/src/vblf/can.py 0000664 0000000 0000000 00000047540 15207004341 0015304 0 ustar 00root root 0000000 0000000 import struct
from dataclasses import dataclass
from typing import ClassVar
from typing_extensions import Self
from vblf.constants import CanFdFlags, ObjFlags, ObjType
from .general import ObjectHeader, ObjectWithHeader
@dataclass
class CanMessage(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HBBI8s")
header: ObjectHeader
channel: int
flags: int
dlc: int
frame_id: int
data: bytes
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
(
channel,
flags,
dlc,
frame_id,
data_,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(
header,
channel,
flags,
dlc,
frame_id,
data_,
)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(
self.channel,
self.flags,
self.dlc,
self.frame_id,
self.data,
)
@classmethod
def new(
cls,
object_flags: ObjFlags,
object_time_stamp: int,
channel: int,
flags: int,
dlc: int,
frame_id: int,
data: bytes,
) -> Self:
header = ObjectHeader.new(
ObjectHeader.SIZE + cls._FORMAT.size,
ObjType.CAN_MESSAGE,
object_flags,
0,
object_time_stamp,
)
return cls(
header,
channel,
flags,
dlc,
frame_id,
data,
)
@dataclass
class CanMessage2(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HBBI8sIBBH")
header: ObjectHeader
channel: int
flags: int
dlc: int
frame_id: int
data: bytes
frame_length: int
bit_count: int
reserved1: int
reserved2: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
(
channel,
flags,
dlc,
frame_id,
data_,
frame_length,
bit_count,
reserved1,
reserved2,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(
header,
channel,
flags,
dlc,
frame_id,
data_,
frame_length,
bit_count,
reserved1,
reserved2,
)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(
self.channel,
self.flags,
self.dlc,
self.frame_id,
self.data,
self.frame_length,
self.bit_count,
self.reserved1,
self.reserved2,
)
@classmethod
def new(
cls,
object_flags: ObjFlags,
object_time_stamp: int,
channel: int,
flags: int,
dlc: int,
frame_id: int,
data: bytes,
frame_length: int,
bit_count: int,
) -> Self:
header = ObjectHeader.new(
ObjectHeader.SIZE + cls._FORMAT.size,
ObjType.CAN_MESSAGE2,
object_flags,
0,
object_time_stamp,
)
return cls(
header,
channel,
flags,
dlc,
frame_id,
data,
frame_length,
bit_count,
0,
0,
)
@dataclass
class CanFdMessage(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HBBIIBBBBI64sI")
header: ObjectHeader
channel: int
flags: int
dlc: int
frame_id: int
frame_length: int
arb_bit_count: int
canfd_flags: CanFdFlags
valid_data_bytes: int
reserved1: int
reserved2: int
data: bytes
reserved3: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
(
channel,
flags,
dlc,
frame_id,
frame_length,
arb_bit_count,
canfd_flags,
valid_data_bytes,
reserved1,
reserved2,
data_,
reserved3,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(
header,
channel,
flags,
dlc,
frame_id,
frame_length,
arb_bit_count,
CanFdFlags(canfd_flags),
valid_data_bytes,
reserved1,
reserved2,
data_,
reserved3,
)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(
self.channel,
self.flags,
self.dlc,
self.frame_id,
self.frame_length,
self.arb_bit_count,
self.canfd_flags,
self.valid_data_bytes,
self.reserved1,
self.reserved2,
self.data,
self.reserved3,
)
@classmethod
def new(
cls,
object_flags: ObjFlags,
object_time_stamp: int,
channel: int,
flags: int,
dlc: int,
frame_id: int,
frame_length: int,
arb_bit_count: int,
canfd_flags: CanFdFlags,
data: bytes,
) -> Self:
header = ObjectHeader.new(
ObjectHeader.SIZE + cls._FORMAT.size,
ObjType.CAN_FD_MESSAGE,
object_flags,
0,
object_time_stamp,
)
return cls(
header,
channel,
flags,
dlc,
frame_id,
frame_length,
arb_bit_count,
canfd_flags,
len(data),
0,
0,
data,
0,
)
@dataclass
class CanFdMessage64(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("BBBBIIIIIIIHBBI")
_FORMAT_EXT: ClassVar[struct.Struct] = struct.Struct("II")
header: ObjectHeader
channel: int
dlc: int
valid_data_bytes: int
tx_count: int
frame_id: int
frame_length: int
flags: CanFdFlags
btr_cfg_arb: int
btr_cfg_data: int
time_offset_brs_ns: int
time_offset_crc_del_ns: int
bit_count: int
dir: int
ext_data_offset: int
crc: int
data: bytes
btr_ext_arb: int
btr_ext_data: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
# get fixed size values
(
channel,
dlc,
valid_data_bytes,
tx_count,
frame_id,
frame_length,
flags,
btr_cfg_arb,
btr_cfg_data,
time_offset_brs_ns,
time_offset_crc_del_ns,
bit_count,
direction,
ext_data_offset,
crc,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
# get data
data_offset = ObjectHeader.SIZE + cls._FORMAT.size
data_length = (ext_data_offset or header.base.object_size) - data_offset
data = buffer[data_offset : data_offset + data_length]
# get ext frame data
btr_ext_arb, btr_ext_data = 0, 0
if (
ext_data_offset != 0
and header.base.object_size >= ext_data_offset + cls._FORMAT_EXT.size
):
btr_ext_arb, btr_ext_data = cls._FORMAT_EXT.unpack_from(buffer, ext_data_offset)
return cls(
header,
channel,
dlc,
valid_data_bytes,
tx_count,
frame_id,
frame_length,
CanFdFlags(flags),
btr_cfg_arb,
btr_cfg_data,
time_offset_brs_ns,
time_offset_crc_del_ns,
bit_count,
direction,
ext_data_offset,
crc,
data,
btr_ext_arb,
btr_ext_data,
)
def pack(self) -> bytes:
buffer = bytearray(self.header.base.object_size)
# pack header
self.header.pack_into(buffer, 0)
# pack fixed size values
self._FORMAT.pack_into(
buffer,
ObjectHeader.SIZE,
self.channel,
self.dlc,
self.valid_data_bytes,
self.tx_count,
self.frame_id,
self.frame_length,
self.flags,
self.btr_cfg_arb,
self.btr_cfg_data,
self.time_offset_brs_ns,
self.time_offset_crc_del_ns,
self.bit_count,
self.dir,
self.ext_data_offset,
self.crc,
)
# pack data
data_offset = ObjectHeader.SIZE + self._FORMAT.size
buffer[data_offset : data_offset + len(self.data)] = self.data
# pack ext frame data
if (
self.ext_data_offset != 0
and self.header.base.object_size >= self.ext_data_offset + self._FORMAT_EXT.size
):
self._FORMAT_EXT.pack_into(
buffer, self.ext_data_offset, self.btr_ext_arb, self.btr_ext_data
)
return bytes(buffer)
@dataclass
class CanDriverStatistic(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HHIIIIIII")
header: ObjectHeader
channel: int
bus_load: int
standard_data_frames: int
extended_data_frames: int
standard_remote_frames: int
extended_remote_frames: int
error_frames: int
overload_frames: int
reserved: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
(
channel,
bus_load,
standard_data_frames,
extended_data_frames,
standard_remote_frames,
extended_remote_frames,
error_frames,
overload_frames,
reserved,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(
header,
channel,
bus_load,
standard_data_frames,
extended_data_frames,
standard_remote_frames,
extended_remote_frames,
error_frames,
overload_frames,
reserved,
)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(
self.channel,
self.bus_load,
self.standard_data_frames,
self.extended_data_frames,
self.standard_remote_frames,
self.extended_remote_frames,
self.error_frames,
self.overload_frames,
self.reserved,
)
@dataclass
class CanDriverError(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HBBI")
header: ObjectHeader
channel: int
tx_errors: int
rx_errors: int
error_code: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
(
channel,
tx_errors,
rx_errors,
error_code,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(
header,
channel,
tx_errors,
rx_errors,
error_code,
)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(
self.channel,
self.tx_errors,
self.rx_errors,
self.error_code,
)
@dataclass
class CanDriverErrorExt(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HBBIIBBH4I")
header: ObjectHeader
channel: int
tx_errors: int
rx_errors: int
error_code: int
flags: int
state: int
reserved1: int
reserved2: int
reserved3: list[int]
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
(
channel,
tx_errors,
rx_errors,
error_code,
flags,
state,
reserved1,
reserved2,
reserved3_0,
reserved3_1,
reserved3_2,
reserved3_3,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(
header,
channel,
tx_errors,
rx_errors,
error_code,
flags,
state,
reserved1,
reserved2,
[reserved3_0, reserved3_1, reserved3_2, reserved3_3],
)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(
self.channel,
self.tx_errors,
self.rx_errors,
self.error_code,
self.flags,
self.state,
self.reserved1,
self.reserved2,
self.reserved3[0],
self.reserved3[1],
self.reserved3[2],
self.reserved3[3],
)
@dataclass
class CanErrorFrame(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HHI")
header: ObjectHeader
channel: int
length: int
reserved: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
channel, length, reserved = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(header, channel, length, reserved)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(self.channel, self.length, self.reserved)
@dataclass
class CanErrorFrameExt(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HHIBBBBIIHH8s")
header: ObjectHeader
channel: int
length: int
flags: int
ecc: int
position: int
dlc: int
reserved1: int
frame_length_in_ns: int
frame_id: int
flags_ext: int
reserved2: int
data: bytes
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
(
channel,
length,
flags,
ecc,
position,
dlc,
reserverd1,
frame_length_in_ns,
frame_id,
flags_ext,
reserved2,
data,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(
header,
channel,
length,
flags,
ecc,
position,
dlc,
reserverd1,
frame_length_in_ns,
frame_id,
flags_ext,
reserved2,
data,
)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(
self.channel,
self.length,
self.flags,
self.ecc,
self.position,
self.dlc,
self.reserved1,
self.frame_length_in_ns,
self.frame_id,
self.flags_ext,
self.reserved2,
self.data,
)
@dataclass
class CanFdErrorFrame64(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("BBBBHHHBBIIIIIIIHH")
_FORMAT_EXT: ClassVar[struct.Struct] = struct.Struct("II")
header: ObjectHeader
channel: int
dlc: int
valid_data_bytes: int
ecc: int
flags: int
error_code_ext: int
ext_flags: int
ext_data_offset: int
reserved1: int
frame_id: int
frame_length: int
btr_cfg_arb: int
btr_cfg_data: int
time_offset_brs_ns: int
time_offset_crc_del_ns: int
crc: int
error_position: int
reserved2: int
data: bytes
btr_ext_arb: int
btr_ext_data: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
# get fixed size values
(
channel,
dlc,
valid_data_bytes,
ecc,
flags,
error_code_ext,
ext_flags,
ext_data_offset,
reserved1,
frame_id,
frame_length,
btr_cfg_arb,
btr_cfg_data,
time_offset_brs_ns,
time_offset_crc_del_ns,
crc,
error_position,
reserved2,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
# get data
data_offset = ObjectHeader.SIZE + cls._FORMAT.size
data = buffer[data_offset : data_offset + valid_data_bytes]
# get ext frame data
btr_ext_arb, btr_ext_data = 0, 0
if header.base.object_size >= ext_data_offset + cls._FORMAT_EXT.size:
btr_ext_arb, btr_ext_data = cls._FORMAT_EXT.unpack_from(buffer, ext_data_offset)
return cls(
header,
channel,
dlc,
valid_data_bytes,
ecc,
flags,
error_code_ext,
ext_flags,
ext_data_offset,
reserved1,
frame_id,
frame_length,
btr_cfg_arb,
btr_cfg_data,
time_offset_brs_ns,
time_offset_crc_del_ns,
crc,
error_position,
reserved2,
data,
btr_ext_arb,
btr_ext_data,
)
def pack(self) -> bytes:
buffer = bytearray(self.header.base.object_size)
# pack header
self.header.pack_into(buffer, 0)
# pack fixed size values
self._FORMAT.pack_into(
buffer,
ObjectHeader.SIZE,
self.channel,
self.dlc,
self.valid_data_bytes,
self.ecc,
self.flags,
self.error_code_ext,
self.ext_flags,
self.ext_data_offset,
self.reserved1,
self.frame_id,
self.frame_length,
self.btr_cfg_arb,
self.btr_cfg_data,
self.time_offset_brs_ns,
self.time_offset_crc_del_ns,
self.crc,
self.error_position,
self.reserved2,
)
# pack data
data_offset = ObjectHeader.SIZE + self._FORMAT.size
buffer[data_offset : data_offset + self.valid_data_bytes] = self.data
# pack ext frame data
if self.header.base.object_size >= self.ext_data_offset + self._FORMAT_EXT.size:
self._FORMAT_EXT.pack_into(
buffer, self.ext_data_offset, self.btr_ext_arb, self.btr_ext_data
)
return bytes(buffer)
@dataclass
class CanDriverHwSync(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HBBI")
header: ObjectHeader
channel: int
flags: int
reserved1: int
reserved2: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
channel, flags, reserved1, reserved2 = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(header, channel, flags, reserved1, reserved2)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(
self.channel, self.flags, self.reserved1, self.reserved2
)
@dataclass
class CanOverloadFrame(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HHI")
header: ObjectHeader
channel: int
reserved1: int
reserved2: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
channel, reserved1, reserved2 = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(header, channel, reserved1, reserved2)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(self.channel, self.reserved1, self.reserved2)
vblf-0.3.0/src/vblf/constants.py 0000664 0000000 0000000 00000012337 15207004341 0016553 0 ustar 00root root 0000000 0000000 from enum import IntEnum, IntFlag
from typing import Final
FILE_SIGNATURE: Final = b"LOGG"
OBJ_SIGNATURE: Final = b"LOBJ"
OBJ_SIGNATURE_SIZE: Final = len(OBJ_SIGNATURE)
class ObjType(IntEnum):
UNKNOWN = 0
CAN_MESSAGE = 1
CAN_ERROR = 2
CAN_OVERLOAD = 3
CAN_STATISTIC = 4
APP_TRIGGER = 5
ENV_INTEGER = 6
ENV_DOUBLE = 7
ENV_STRING = 8
ENV_DATA = 9
LOG_CONTAINER = 10
LIN_MESSAGE = 11
LIN_CRC_ERROR = 12
LIN_DLC_INFO = 13
LIN_RCV_ERROR = 14
LIN_SND_ERROR = 15
LIN_SLV_TIMEOUT = 16
LIN_SCHED_MODCH = 17
LIN_SYN_ERROR = 18
LIN_BAUDRATE = 19
LIN_SLEEP = 20
LIN_WAKEUP = 21
MOST_SPY = 22
MOST_CTRL = 23
MOST_LIGHTLOCK = 24
MOST_STATISTIC = 25
reserved_1 = 26
reserved_2 = 27
reserved_3 = 28
FLEXRAY_DATA = 29
FLEXRAY_SYNC = 30
CAN_DRIVER_ERROR = 31
MOST_PKT = 32
MOST_PKT2 = 33
MOST_HWMODE = 34
MOST_REG = 35
MOST_GENREG = 36
MOST_NETSTATE = 37
MOST_DATALOST = 38
MOST_TRIGGER = 39
FLEXRAY_CYCLE = 40
FLEXRAY_MESSAGE = 41
LIN_CHECKSUM_INFO = 42
LIN_SPIKE_EVENT = 43
CAN_DRIVER_SYNC = 44
FLEXRAY_STATUS = 45
GPS_EVENT = 46
FR_ERROR = 47
FR_STATUS = 48
FR_STARTCYCLE = 49
FR_RCVMESSAGE = 50
REALTIMECLOCK = 51
AVAILABLE2 = 52
AVAILABLE3 = 53
LIN_STATISTIC = 54
J1708_MESSAGE = 55
J1708_VIRTUAL_MSG = 56
LIN_MESSAGE2 = 57
LIN_SND_ERROR2 = 58
LIN_SYN_ERROR2 = 59
LIN_CRC_ERROR2 = 60
LIN_RCV_ERROR2 = 61
LIN_WAKEUP2 = 62
LIN_SPIKE_EVENT2 = 63
LIN_LONG_DOM_SIG = 64
APP_TEXT = 65
FR_RCVMESSAGE_EX = 66
MOST_STATISTICEX = 67
MOST_TXLIGHT = 68
MOST_ALLOCTAB = 69
MOST_STRESS = 70
ETHERNET_FRAME = 71
SYS_VARIABLE = 72
CAN_ERROR_EXT = 73
CAN_DRIVER_ERROR_EXT = 74
LIN_LONG_DOM_SIG2 = 75
MOST_150_MESSAGE = 76
MOST_150_PKT = 77
MOST_ETHERNET_PKT = 78
MOST_150_MESSAGE_FRAGMENT = 79
MOST_150_PKT_FRAGMENT = 80
MOST_ETHERNET_PKT_FRAGMENT = 81
MOST_SYSTEM_EVENT = 82
MOST_150_ALLOCTAB = 83
MOST_50_MESSAGE = 84
MOST_50_PKT = 85
CAN_MESSAGE2 = 86
LIN_UNEXPECTED_WAKEUP = 87
LIN_SHORT_OR_SLOW_RESPONSE = 88
LIN_DISTURBANCE_EVENT = 89
SERIAL_EVENT = 90
OVERRUN_ERROR = 91
EVENT_COMMENT = 92
WLAN_FRAME = 93
WLAN_STATISTIC = 94
MOST_ECL = 95
GLOBAL_MARKER = 96
AFDX_FRAME = 97
AFDX_STATISTIC = 98
KLINE_STATUSEVENT = 99
CAN_FD_MESSAGE = 100
CAN_FD_MESSAGE_64 = 101
ETHERNET_RX_ERROR = 102
ETHERNET_STATUS = 103
CAN_FD_ERROR_64 = 104
LIN_SHORT_OR_SLOW_RESPONSE2 = 105
AFDX_STATUS = 106
AFDX_BUS_STATISTIC = 107
reserved_4 = 108
AFDX_ERROR_EVENT = 109
A429_ERROR = 110
A429_STATUS = 111
A429_BUS_STATISTIC = 112
A429_MESSAGE = 113
ETHERNET_STATISTIC = 114
reserved_5 = 115
reserved_6 = 116
reserved_7 = 117
TEST_STRUCTURE = 118
DIAG_REQUEST_INTERPRETATION = 119
ETHERNET_FRAME_EX = 120
ETHERNET_FRAME_FORWARDED = 121
ETHERNET_ERROR_EX = 122
ETHERNET_ERROR_FORWARDED = 123
FUNCTION_BUS = 124
DATA_LOST_BEGIN = 125
DATA_LOST_END = 126
WATER_MARK_EVENT = 127
TRIGGER_CONDITION = 128
@staticmethod
def from_int(object_type: int) -> "ObjType":
try:
return ObjType(object_type)
except ValueError:
return ObjType.UNKNOWN
class AppId(IntEnum):
UNKNOWN = 0
CANALYZER = 1
CANOE = 2
CANSTRESS = 3
CANLOG = 4
CANAPE = 5
CANCASEXLLOG = 6
VLCONFIG = 7
PORSCHELOGGER = 200
@staticmethod
def from_int(application_id: int) -> "AppId":
try:
return AppId(application_id)
except ValueError:
return AppId.UNKNOWN
class Compression(IntEnum):
NONE = 0
SPEED = 1
DEFAULT = 6
MAX = 9
@staticmethod
def from_int(compression_level: int) -> "Compression | int":
try:
return Compression(compression_level)
except ValueError:
return compression_level
class ObjFlags(IntFlag):
TIME_TEN_MICS = 0x1
TIME_ONE_NANS = 0x2
class TriggerFlag(IntFlag):
SINGLE_TRIGGER = 0x0
LOGGING_START = 0x1
LOGGING_STOP = 0x2
class CanFdFlags(IntFlag):
NERR = 0x0004
HIGH_VOLTAGE_WAKE_UP = 0x0008
REMOTE_FRAME = 0x0010
TX_ACKNOWLEDGE = 0x0040
TX_REQUEST = 0x0080
SRR = 0x0200
R0 = 0x0400
R1 = 0x0800
FDF = 0x1000
BRS = 0x2000
ESI = 0x4000
FRAME_PART_OF_BURST = 0x20000
SINGLE_SHOT_MODE_NOT_TRANSMITTED = 0x40000
SINGLE_SHOT_MODE_REASON = 0x80000 # 0 = arbitration lost, 1 = frame disturbed
class SysVarType(IntEnum):
DOUBLE = 1
LONG = 2
STRING = 3
DOUBLEARRAY = 4
LONGARRAY = 5
LONGLONG = 6
BYTEARRAY = 7
class BusType(IntEnum):
CAN = 1
LIN = 5
MOST = 6
FLEXRAY = 7
J1708 = 9
ETHERNET = 10
WLAN = 13
AFDX = 14
class AppTextSource(IntEnum):
MEASUREMENTCOMMENT = 0
DBCHANNELINFO = 1
METADATA = 2
ATTACHMENT = 3
TRACELINE = 4
class FunctionBusType(IntEnum):
UNDEFINED = 0
SIGNAL = 1
SERVICE_FUNCTION = 2
STATE = 3
class TriggerConditionStatus(IntEnum):
UNKNOWN = 0
START = 1
STOP = 2
STARTSTOP = 3
vblf-0.3.0/src/vblf/ethernet.py 0000664 0000000 0000000 00000010144 15207004341 0016347 0 ustar 00root root 0000000 0000000 import struct
from dataclasses import dataclass
from typing import ClassVar
from typing_extensions import Self
from vblf.general import ObjectHeader, ObjectWithHeader
@dataclass
class EthernetFrameEx(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HHHHQIHHII")
header: ObjectHeader
struct_length: int
flags: int
channel: int
hardware_channel: int
frame_duration: int
frame_checksum: int
dir: int
frame_length: int
frame_handle: int
reserved: int
frame_data: bytes
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer, 0)
(
struct_length,
flags,
channel,
hardware_channel,
frame_duration,
frame_checksum,
direction,
frame_length,
frame_handle,
reserved,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
# get data_bytes
data_offset = ObjectHeader.SIZE + cls._FORMAT.size
available_bytes = min(frame_length, len(buffer) - data_offset)
data_bytes = buffer[data_offset : data_offset + available_bytes]
return cls(
header,
struct_length,
flags,
channel,
hardware_channel,
frame_duration,
frame_checksum,
direction,
frame_length,
frame_handle,
reserved,
data_bytes,
)
def pack(self) -> bytes:
buffer = bytearray(self.header.base.object_size)
self.header.pack_into(buffer, 0)
self._FORMAT.pack_into(
buffer,
ObjectHeader.SIZE,
self.struct_length,
self.flags,
self.channel,
self.hardware_channel,
self.frame_duration,
self.frame_checksum,
self.dir,
self.frame_length,
self.frame_handle,
self.reserved,
)
# pack data_bytes
data_offset = ObjectHeader.SIZE + self._FORMAT.size
buffer[data_offset : data_offset + len(self.frame_data)] = self.frame_data
return bytes(buffer)
@dataclass
class EthernetStatistic(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HHIQQQQQQQhHI")
channel: int
reserved_1: int
reserved_2: int
rcv_ok_hw: int
xmit_ok_hw: int
rcv_error_hw: int
xmit_error_hw: int
rcv_bytes_hw: int
xmit_bytes_hw: int
rcv_no_buffer_hw: int
sqi: int
hardware_channel: int
reserved_3: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer, 0)
(
channel,
reserved_1,
reserved_2,
rcv_ok_hw,
xmit_ok_hw,
rcv_error_hw,
xmit_error_hw,
rcv_bytes_hw,
xmit_bytes_hw,
rcv_no_buffer_hw,
sqi,
hardware_channel,
reserved_3,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(
header,
channel,
reserved_1,
reserved_2,
rcv_ok_hw,
xmit_ok_hw,
rcv_error_hw,
xmit_error_hw,
rcv_bytes_hw,
xmit_bytes_hw,
rcv_no_buffer_hw,
sqi,
hardware_channel,
reserved_3,
)
def pack(self) -> bytes:
buffer = bytearray(self.header.base.object_size)
self.header.pack_into(buffer, 0)
self._FORMAT.pack_into(
buffer,
ObjectHeader.SIZE,
self.channel,
self.reserved_1,
self.reserved_2,
self.rcv_ok_hw,
self.xmit_ok_hw,
self.rcv_error_hw,
self.xmit_error_hw,
self.rcv_bytes_hw,
self.xmit_bytes_hw,
self.rcv_no_buffer_hw,
self.sqi,
self.hardware_channel,
self.reserved_3,
)
return bytes(buffer)
vblf-0.3.0/src/vblf/flexray.py 0000664 0000000 0000000 00000006443 15207004341 0016212 0 ustar 00root root 0000000 0000000 import struct
from dataclasses import dataclass
from typing import ClassVar
from typing_extensions import Self
from vblf.general import ObjectHeader, ObjectWithHeader
@dataclass
class FlexrayVFrReceiveMsgEx(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HHHHIIHHHHHHIIIIIIHHH26s")
header: ObjectHeader
channel: int
version: int
channel_mask: int
dir_flags: int
client_index: int
cluster_no: int
frame_id: int
header_crc1: int
header_crc2: int
byte_count: int
data_count: int
cycle: int
tag: int
data: int
frame_flags: int
app_parameter: int
frame_crc: int
frame_length_ns: int
frame_id1: int
pdu_offset: int
blf_log_mask: int
reserved: bytes
data_bytes: bytes
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer, 0)
(
channel,
version,
channel_mask,
dir_flags,
client_index,
cluster_no,
frame_id,
header_crc1,
header_crc2,
byte_count,
data_count,
cycle,
tag,
data,
frame_flags,
app_parameter,
frame_crc,
frame_length_ns,
frame_id1,
pdu_offset,
blf_log_mask,
reserved,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
# get data_bytes
data_offset = ObjectHeader.SIZE + cls._FORMAT.size
available_bytes = min(data_count, len(buffer) - data_offset)
data_bytes = buffer[data_offset : data_offset + available_bytes]
return cls(
header,
channel,
version,
channel_mask,
dir_flags,
client_index,
cluster_no,
frame_id,
header_crc1,
header_crc2,
byte_count,
data_count,
cycle,
tag,
data,
frame_flags,
app_parameter,
frame_crc,
frame_length_ns,
frame_id1,
pdu_offset,
blf_log_mask,
reserved,
data_bytes,
)
def pack(self) -> bytes:
buffer = bytearray(self.header.base.object_size)
self.header.pack_into(buffer, 0)
self._FORMAT.pack_into(
buffer,
ObjectHeader.SIZE,
self.channel,
self.version,
self.channel_mask,
self.dir_flags,
self.client_index,
self.cluster_no,
self.frame_id,
self.header_crc1,
self.header_crc2,
self.byte_count,
self.data_count,
self.cycle,
self.tag,
self.data,
self.frame_flags,
self.app_parameter,
self.frame_crc,
self.frame_length_ns,
self.frame_id1,
self.pdu_offset,
self.blf_log_mask,
self.reserved,
)
# pack data_bytes
data_offset = ObjectHeader.SIZE + self._FORMAT.size
buffer[data_offset : data_offset + len(self.data_bytes)] = self.data_bytes
return bytes(buffer)
vblf-0.3.0/src/vblf/general.py 0000664 0000000 0000000 00000066445 15207004341 0016165 0 ustar 00root root 0000000 0000000 import datetime
import struct
from dataclasses import dataclass
from typing import ClassVar, Generic, Optional, TypeVar, Union
from typing_extensions import Self
from vblf import __version__
from vblf.constants import (
FILE_SIGNATURE,
OBJ_SIGNATURE,
AppId,
AppTextSource,
BusType,
Compression,
FunctionBusType,
ObjFlags,
ObjType,
SysVarType,
TriggerConditionStatus,
TriggerFlag,
)
HeaderType = TypeVar("HeaderType", bound="HeaderWithBase")
@dataclass
class SystemTime:
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HHHHHHHH")
year: int
month: int
day_of_week: int
day: int
hour: int
minute: int
second: int
milliseconds: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
return cls(*cls._FORMAT.unpack(buffer))
def pack(self) -> bytes:
return self._FORMAT.pack(*self.__dict__.values())
@classmethod
def unpack_from(cls, buffer: bytes, offset: int = 0) -> Self:
return cls(*cls._FORMAT.unpack_from(buffer, offset))
def pack_into(self, buffer: bytearray, offset: int) -> None:
return self._FORMAT.pack_into(buffer, offset, *self.__dict__.values())
@classmethod
def from_datetime(cls, dt: Optional[datetime.datetime] = None) -> Self:
if not dt:
dt = datetime.datetime.now(tz=datetime.timezone.utc)
return cls(
dt.year,
dt.month,
(dt.weekday() + 1) % 7,
dt.day,
dt.hour,
dt.minute,
dt.second,
min(999, round(dt.microsecond / 1000.0)),
)
def to_datetime(self) -> datetime.datetime:
return datetime.datetime(
self.year,
self.month,
self.day,
self.hour,
self.minute,
self.second,
self.milliseconds * 1000,
)
@dataclass
class ObjectHeaderBase:
_FORMAT: ClassVar[struct.Struct] = struct.Struct("4sHHII")
SIZE: ClassVar[int] = _FORMAT.size
signature: bytes
header_size: int
header_version: int
object_size: int
object_type: ObjType
@classmethod
def unpack(cls, buffer: bytes) -> Self:
(
signature,
header_size,
header_version,
object_size,
object_type,
) = cls._FORMAT.unpack(buffer)
return cls(
signature,
header_size,
header_version,
object_size,
ObjType.from_int(object_type),
)
@classmethod
def unpack_from(cls, buffer: bytes, offset: int = 0) -> Self:
(
signature,
header_size,
header_version,
object_size,
object_type,
) = cls._FORMAT.unpack_from(buffer, offset)
return cls(
signature,
header_size,
header_version,
object_size,
ObjType.from_int(object_type),
)
def pack(self) -> bytes:
return self._FORMAT.pack(*self.__dict__.values())
def pack_into(self, buffer: bytearray, offset: int) -> None:
self._FORMAT.pack_into(buffer, offset, *self.__dict__.values())
@dataclass
class HeaderWithBase:
base: ObjectHeaderBase
@classmethod
def unpack(cls, buffer: bytes) -> Self:
raise NotImplementedError
@classmethod
def unpack_from(cls, buffer: bytes, offset: int = 0) -> Self:
raise NotImplementedError
def pack(self) -> bytes:
raise NotImplementedError
def pack_into(self, buffer: bytearray, offset: int) -> None:
raise NotImplementedError
@dataclass
class VarObjectHeader(HeaderWithBase):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("IHHQ")
SIZE: ClassVar[int] = ObjectHeaderBase.SIZE + _FORMAT.size
object_flags: ObjFlags
object_static_size: int
object_version: int
object_time_stamp: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
base = ObjectHeaderBase.unpack_from(buffer, 0)
(
object_flags,
object_static_size,
object_version,
object_time_stamp,
) = cls._FORMAT.unpack_from(buffer, ObjectHeaderBase.SIZE)
return cls(
base,
ObjFlags(object_flags),
object_static_size,
object_version,
object_time_stamp,
)
@classmethod
def unpack_from(cls, buffer: bytes, offset: int = 0) -> Self:
base = ObjectHeaderBase.unpack_from(buffer, 0)
(
object_flags,
object_static_size,
object_version,
object_time_stamp,
) = cls._FORMAT.unpack_from(buffer, offset + ObjectHeaderBase.SIZE)
return cls(
base,
ObjFlags(object_flags),
object_static_size,
object_version,
object_time_stamp,
)
def pack(self) -> bytes:
return self.base.pack() + self._FORMAT.pack(
self.object_flags,
self.object_static_size,
self.object_version,
self.object_time_stamp,
)
def pack_into(self, buffer: bytearray, offset: int) -> None:
self.base.pack_into(buffer, offset)
self._FORMAT.pack_into(
buffer,
offset + ObjectHeaderBase.SIZE,
self.object_flags,
self.object_static_size,
self.object_version,
self.object_time_stamp,
)
@dataclass
class ObjectHeader(HeaderWithBase):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("IHHQ")
SIZE: ClassVar[int] = ObjectHeaderBase.SIZE + _FORMAT.size
object_flags: ObjFlags
client_index: int
object_version: int
object_time_stamp: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
base = ObjectHeaderBase.unpack_from(buffer, 0)
(
object_flags,
client_index,
object_version,
object_time_stamp,
) = cls._FORMAT.unpack_from(buffer, ObjectHeaderBase.SIZE)
return cls(
base,
ObjFlags(object_flags),
client_index,
object_version,
object_time_stamp,
)
@classmethod
def unpack_from(cls, buffer: bytes, offset: int = 0) -> Self:
base = ObjectHeaderBase.unpack_from(buffer, offset)
(
object_flags,
client_index,
object_version,
object_time_stamp,
) = cls._FORMAT.unpack_from(buffer, offset + ObjectHeaderBase.SIZE)
return cls(
base,
ObjFlags(object_flags),
client_index,
object_version,
object_time_stamp,
)
def pack(self) -> bytes:
return self.base.pack() + self._FORMAT.pack(
self.object_flags,
self.client_index,
self.object_version,
self.object_time_stamp,
)
def pack_into(self, buffer: bytearray, offset: int) -> None:
self.base.pack_into(buffer, offset)
self._FORMAT.pack_into(
buffer,
offset + ObjectHeaderBase.SIZE,
self.object_flags,
self.client_index,
self.object_version,
self.object_time_stamp,
)
@classmethod
def new(
cls,
object_size: int,
object_type: ObjType,
object_flags: ObjFlags,
object_version: int,
object_time_stamp: int,
) -> Self:
base = ObjectHeaderBase(
signature=OBJ_SIGNATURE,
header_size=cls.SIZE,
header_version=1,
object_size=object_size,
object_type=object_type,
)
header = cls(
base,
object_flags,
0,
object_version,
object_time_stamp,
)
return header
# @dataclass
# class ObjectHeader2(ObjectHeaderBase):
# _FORMAT: ClassVar[struct.Struct] = struct.Struct("IBBHQQ")
# SIZE: ClassVar[int] = ObjectHeaderBase.SIZE + _FORMAT.size
# object_flags: int
# time_stamp_status: int
# reserved1: int
# object_version: int
# object_time_stamp: int
# original_time_stamp: int
@dataclass
class ObjectWithHeader(Generic[HeaderType]):
header: HeaderType
@classmethod
def unpack(cls, buffer: bytes) -> Self:
raise NotImplementedError
def pack(self) -> bytes:
raise NotImplementedError
@dataclass
class NotImplementedObject(ObjectWithHeader[HeaderWithBase]):
header: HeaderWithBase
buffer: bytes
@classmethod
def unpack(cls, buffer: bytes) -> Self:
base = ObjectHeaderBase.unpack_from(buffer, 0)
header = HeaderWithBase(base)
return cls(header, buffer)
def pack(self) -> bytes:
return self.buffer
@dataclass
class FileStatistics:
_FORMAT: ClassVar[struct.Struct] = struct.Struct("4sIIBBBBQQII32xQ64s")
SIZE: ClassVar[int] = _FORMAT.size
signature: bytes
statistics_size: int
api_number: int
application_id: AppId
compression_level: Union[int, Compression]
application_major: int
application_minor: int
file_size: int
uncompressed_file_size: int
object_count: int
application_build: int
measurement_start_time: SystemTime
last_object_time: SystemTime
restore_points_offset: int
reserved: bytes
@classmethod
def unpack(cls, buffer: bytes) -> "FileStatistics":
(
signature,
statistics_size,
api_number,
application_id,
compression_level,
application_major,
application_minor,
file_size,
uncompressed_file_size,
object_count,
application_build,
restore_points_offset,
reserved,
) = cls._FORMAT.unpack(buffer)
measurement_start_time = SystemTime.unpack_from(buffer, 40)
last_object_time = SystemTime.unpack_from(buffer, 56)
return cls(
signature,
statistics_size,
api_number,
AppId.from_int(application_id),
Compression.from_int(compression_level),
application_major,
application_minor,
file_size,
uncompressed_file_size,
object_count,
application_build,
measurement_start_time,
last_object_time,
restore_points_offset,
reserved,
)
def pack(self) -> bytes:
buffer = bytearray(self._FORMAT.size)
self._FORMAT.pack_into(
buffer,
0,
self.signature,
self.statistics_size,
self.api_number,
self.application_id,
self.compression_level,
self.application_major,
self.application_minor,
self.file_size,
self.uncompressed_file_size,
self.object_count,
self.application_build,
self.restore_points_offset,
self.reserved,
)
self.measurement_start_time.pack_into(buffer, 40)
self.last_object_time.pack_into(buffer, 56)
return bytes(buffer)
@classmethod
def new(cls) -> Self:
major_version, minor_version, *_ = __version__.split(".")
return cls(
signature=FILE_SIGNATURE,
statistics_size=FileStatistics.SIZE,
api_number=0x3E4630,
application_id=AppId.UNKNOWN,
compression_level=Compression.DEFAULT,
application_major=int(major_version),
application_minor=int(minor_version),
file_size=FileStatistics.SIZE,
uncompressed_file_size=FileStatistics.SIZE,
object_count=0,
application_build=0,
measurement_start_time=SystemTime.from_datetime(),
last_object_time=SystemTime.from_datetime(),
restore_points_offset=0,
reserved=bytes(64),
)
@dataclass
class LogContainer(ObjectWithHeader[ObjectHeader]):
header: ObjectHeader
data: bytes
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
return cls(header, buffer[ObjectHeader.SIZE :])
def pack(self) -> bytes:
return self.header.pack() + self.data
@classmethod
def new(
cls,
data: bytes,
time_stamp: int,
flags: ObjFlags = ObjFlags.TIME_ONE_NANS,
client_index: int = 0,
) -> Self:
base = ObjectHeaderBase(
signature=OBJ_SIGNATURE,
header_size=ObjectHeader.SIZE,
header_version=1,
object_size=ObjectHeader.SIZE + len(data),
object_type=ObjType.LOG_CONTAINER,
)
header = ObjectHeader(
base=base,
object_flags=flags,
client_index=client_index,
object_version=0,
object_time_stamp=time_stamp,
)
return cls(
header=header,
data=data,
)
@dataclass
class AppText(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("IIII")
header: ObjectHeader
source: AppTextSource
reserved1: int
text_length: int
reserved2: int
text: str
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
(
source,
reserved1,
text_length,
reserved2,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
text_offset = ObjectHeader.SIZE + cls._FORMAT.size
text = buffer[text_offset : text_offset + text_length - 1].decode("cp1252")
return cls(
header,
AppTextSource(source),
reserved1,
text_length,
reserved2,
text,
)
def pack(self) -> bytes:
buffer = bytearray(self.header.base.object_size)
self.header.pack_into(buffer, 0)
self._FORMAT.pack_into(
buffer,
ObjectHeader.SIZE,
self.source,
self.reserved1,
self.text_length,
self.reserved2,
)
encoded_text = self.text.encode("cp1252") + b"\x00"
text_offset = ObjectHeader.SIZE + self._FORMAT.size
buffer[text_offset : text_offset + self.text_length] = encoded_text
return bytes(buffer)
@dataclass
class AppTrigger(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("QQHHI")
header: ObjectHeader
pre_trigger_time: int
post_trigger_time: int
channel: int
flags: TriggerFlag
app_specific: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer, 0)
(
pre_trigger_time,
post_trigger_time,
channel,
flags,
app_specific,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(
header,
pre_trigger_time,
post_trigger_time,
channel,
TriggerFlag(flags),
app_specific,
)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(
self.pre_trigger_time,
self.post_trigger_time,
self.channel,
self.flags,
self.app_specific,
)
@dataclass
class EnvironmentVariable(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("IIQ")
header: ObjectHeader
name_length: int
data_length: int
reserved: int
name: str
data: bytes
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer, 0)
(name_length, data_length, reserved) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
# get name
name_offset = ObjectHeader.SIZE + cls._FORMAT.size
name = buffer[name_offset : name_offset + name_length].decode("cp1252")
# get data
data_offset = name_offset + name_length
data = buffer[data_offset : data_offset + data_length]
return cls(
header,
name_length,
data_length,
reserved,
name,
data,
)
def pack(self) -> bytes:
buffer = bytearray(self.header.base.object_size)
# write header
self.header.pack_into(buffer, 0)
# write fixed size values
self._FORMAT.pack_into(
buffer,
ObjectHeader.SIZE,
self.name_length,
self.data_length,
self.reserved,
)
# write name
name_offset = ObjectHeader.SIZE + self._FORMAT.size
buffer[name_offset : name_offset + self.name_length] = self.name.encode("cp1252")
# write data
data_offset = name_offset + self.name_length
buffer[data_offset : data_offset + self.data_length] = self.data
return bytes(buffer)
@dataclass
class SystemVariable(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("IIQIIQ")
header: ObjectHeader
type: SysVarType
representation: int
reserved1: int
name_length: int
data_length: int
reserved2: int
name: str
data: bytes
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer, 0)
(
type_,
representation,
reserved1,
name_length,
data_length,
reserved2,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
# get name
name_offset = ObjectHeader.SIZE + cls._FORMAT.size
name = buffer[name_offset : name_offset + name_length].decode("cp1252")
# get data
data_offset = name_offset + name_length
data = buffer[data_offset : data_offset + data_length]
return cls(
header,
SysVarType(type_),
representation,
reserved1,
name_length,
data_length,
reserved2,
name,
data,
)
def pack(self) -> bytes:
buffer = bytearray(self.header.base.object_size)
# write header
self.header.pack_into(buffer, 0)
# write fixed size values
self._FORMAT.pack_into(
buffer,
ObjectHeader.SIZE,
self.type,
self.representation,
self.reserved1,
self.name_length,
self.data_length,
self.reserved2,
)
# write name
name_offset = ObjectHeader.SIZE + self._FORMAT.size
buffer[name_offset : name_offset + self.name_length] = self.name.encode("cp1252")
# write data
data_offset = name_offset + self.name_length
buffer[data_offset : data_offset + self.data_length] = self.data
return bytes(buffer)
@dataclass
class RealTimeClock(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("QQ")
header: ObjectHeader
time: int
logging_offset: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer, 0)
time, logging_offset = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(header, time, logging_offset)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(self.time, self.logging_offset)
@dataclass
class DriverOverrun(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("IHH")
header: ObjectHeader
bus_type: BusType
channel: int
reserved: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer, 0)
(
bus_type,
channel,
reserved,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(
header,
BusType(bus_type),
channel,
reserved,
)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(
self.bus_type,
self.channel,
self.reserved,
)
@dataclass
class EventComment(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("IIQ")
header: ObjectHeader
commented_event_type: int
text_length: int
reserved: int
text: str
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
(
commented_event_type,
text_length,
reserved,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
text_offset = ObjectHeader.SIZE + cls._FORMAT.size
text = buffer[text_offset : text_offset + text_length].decode("cp1252")
return cls(
header,
commented_event_type,
text_length,
reserved,
text,
)
def pack(self) -> bytes:
buffer = bytearray(self.header.base.object_size)
self.header.pack_into(buffer, 0)
self._FORMAT.pack_into(
buffer,
ObjectHeader.SIZE,
self.commented_event_type,
self.text_length,
self.reserved,
)
encoded_text = self.text.encode("cp1252")
text_offset = ObjectHeader.SIZE + self._FORMAT.size
buffer[text_offset : text_offset + self.text_length] = encoded_text
return bytes(buffer)
@dataclass
class GlobalMarker(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("IIIBBHIIIIQ")
header: ObjectHeader
commented_event_type: int
foreground_color: int
background_color: int
is_relocatable: int
reserved1: int
reserved2: int
group_name_length: int
marker_name_length: int
description_length: int
reserved3: int
reserved4: int
group_name: str
marker_name: str
description: str
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer, 0)
(
commented_event_type,
foreground_color,
background_color,
is_relocatable,
reserved1,
reserved2,
group_name_length,
marker_name_length,
description_length,
reserved3,
reserved4,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
# get group_name
group_name_offset = ObjectHeader.SIZE + cls._FORMAT.size
_group_name = buffer[group_name_offset : group_name_offset + group_name_length]
group_name = _group_name.decode("cp1252")
# get marker_name
marker_name_offset = group_name_offset + group_name_length
_marker_name = buffer[marker_name_offset : marker_name_offset + marker_name_length]
marker_name = _marker_name.decode("cp1252")
# get marker_name
description_offset = marker_name_offset + marker_name_length
_description = buffer[description_offset : description_offset + description_length]
description = _description.decode("cp1252")
return cls(
header,
commented_event_type,
foreground_color,
background_color,
is_relocatable,
reserved1,
reserved2,
group_name_length,
marker_name_length,
description_length,
reserved3,
reserved4,
group_name,
marker_name,
description,
)
def pack(self) -> bytes:
return (
self.header.pack()
+ self._FORMAT.pack(
self.commented_event_type,
self.foreground_color,
self.background_color,
self.is_relocatable,
self.reserved1,
self.reserved2,
self.group_name_length,
self.marker_name_length,
self.description_length,
self.reserved3,
self.reserved4,
)
+ self.group_name.encode("cp1252")
+ self.marker_name.encode("cp1252")
+ self.description.encode("cp1252")
)
@dataclass
class FunctionBus(ObjectWithHeader[VarObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("IIII")
header: VarObjectHeader
object_type: FunctionBusType
ve_type: int
name_length: int
data_length: int
name: str
data: bytes
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = VarObjectHeader.unpack_from(buffer, 0)
(
object_type,
ve_type,
name_length,
data_length,
) = cls._FORMAT.unpack_from(buffer, VarObjectHeader.SIZE)
# get name
name_offset = ObjectHeader.SIZE + cls._FORMAT.size
_name = buffer[name_offset : name_offset + name_length]
name = _name.decode("cp1252")
# get data
data_offset = name_offset + name_length
data = buffer[data_offset : data_offset + data_length]
return cls(
header,
FunctionBusType(object_type),
ve_type,
name_length,
data_length,
name,
data,
)
def pack(self) -> bytes:
return (
self.header.pack()
+ self._FORMAT.pack(
self.object_type,
self.ve_type,
self.name_length,
self.data_length,
)
+ self.name.encode("cp1252")
+ self.data
)
@dataclass
class TriggerCondition(ObjectWithHeader[VarObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("III")
header: VarObjectHeader
state: TriggerConditionStatus
trigger_block_name_length: int
trigger_condition_length: int
trigger_block_name: str
trigger_condition: str
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = VarObjectHeader.unpack_from(buffer, 0)
(
state,
trigger_block_name_length,
trigger_condition_length,
) = cls._FORMAT.unpack_from(buffer, VarObjectHeader.SIZE)
# get trigger_block_name
trigger_block_name_offset = ObjectHeader.SIZE + cls._FORMAT.size
_trigger_block_name = buffer[
trigger_block_name_offset : trigger_block_name_offset + trigger_block_name_length
]
trigger_block_name = _trigger_block_name.decode("cp1252")
# get trigger_condition
trigger_condition_offset = trigger_block_name_offset + trigger_block_name_length
_trigger_condition = buffer[
trigger_condition_offset : trigger_condition_offset + trigger_condition_length
]
trigger_condition = _trigger_condition.decode("cp1252")
return cls(
header,
TriggerConditionStatus(state),
trigger_block_name_length,
trigger_condition_length,
trigger_block_name,
trigger_condition,
)
def pack(self) -> bytes:
return (
self.header.pack()
+ self._FORMAT.pack(
self.state,
self.trigger_block_name_length,
self.trigger_condition_length,
)
+ self.trigger_block_name.encode("cp1252")
+ self.trigger_condition.encode("cp1252")
)
vblf-0.3.0/src/vblf/lin.py 0000664 0000000 0000000 00000021307 15207004341 0015316 0 ustar 00root root 0000000 0000000 import struct
from dataclasses import dataclass
from typing import ClassVar
from typing_extensions import Self
from vblf.general import ObjectHeader, ObjectWithHeader
@dataclass
class LinMessage(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HBB8sBBBBHB5s")
header: ObjectHeader
channel: int
id: int
dlc: int
data: bytes
fsm_id: int
fsm_state: int
header_time: int
full_time: int
crc: int
dir: int
reserved: bytes
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
(
channel,
lin_id,
dlc,
data_,
fsm_id,
fsm_state,
header_time,
full_time,
crc,
direction,
reserved,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
return cls(
header,
channel,
lin_id,
dlc,
data_,
fsm_id,
fsm_state,
header_time,
full_time,
crc,
direction,
reserved,
)
def pack(self) -> bytes:
return self.header.pack() + self._FORMAT.pack(
self.channel,
self.id,
self.dlc,
self.data,
self.fsm_id,
self.fsm_state,
self.header_time,
self.full_time,
self.crc,
self.dir,
self.reserved,
)
@dataclass
class LinBusEvent:
_FORMAT: ClassVar[struct.Struct] = struct.Struct("QIH2s")
SIZE: ClassVar[int] = _FORMAT.size
sof: int
event_baudrate: int
channel: int
reserved: bytes
@classmethod
def unpack_from(cls, buffer: bytes, offset: int = 0) -> Self:
(
sof,
event_baudrate,
channel,
reserved,
) = cls._FORMAT.unpack_from(buffer, offset)
return cls(
sof,
event_baudrate,
channel,
reserved,
)
def pack_into(self, buffer: bytearray, offset: int) -> None:
self._FORMAT.pack_into(
buffer, offset, self.sof, self.event_baudrate, self.channel, self.reserved
)
@dataclass
class LinSynchFieldEvent:
_FORMAT: ClassVar[struct.Struct] = struct.Struct("QQ")
SIZE: ClassVar[int] = LinBusEvent.SIZE + _FORMAT.size
lin_bus_event: LinBusEvent
synch_break_length: int
synch_del_length: int
@classmethod
def unpack_from(cls, buffer: bytes, offset: int = 0) -> Self:
lin_bus_event = LinBusEvent.unpack_from(buffer, offset)
(
synch_break_length,
synch_del_length,
) = cls._FORMAT.unpack_from(buffer, offset + LinBusEvent.SIZE)
return cls(
lin_bus_event,
synch_break_length,
synch_del_length,
)
def pack_into(self, buffer: bytearray, offset: int) -> None:
self.lin_bus_event.pack_into(buffer, offset)
self._FORMAT.pack_into(
buffer, offset + LinBusEvent.SIZE, self.synch_break_length, self.synch_del_length
)
@dataclass
class LinMessageDescriptor:
_FORMAT: ClassVar[struct.Struct] = struct.Struct("HHBBBB")
SIZE: ClassVar[int] = LinSynchFieldEvent.SIZE + _FORMAT.size
lin_synch_field_event: LinSynchFieldEvent
supplier_id: int
message_id: int
nad: int
id: int
dlc: int
checksum_model: int
@classmethod
def unpack_from(cls, buffer: bytes, offset: int = 0) -> Self:
lin_synch_field_event = LinSynchFieldEvent.unpack_from(buffer, offset)
(
supplier_id,
message_id,
nad,
lin_id,
dlc,
checksum_model,
) = cls._FORMAT.unpack_from(buffer, offset + LinSynchFieldEvent.SIZE)
return cls(
lin_synch_field_event,
supplier_id,
message_id,
nad,
lin_id,
dlc,
checksum_model,
)
def pack_into(self, buffer: bytearray, offset: int) -> None:
self.lin_synch_field_event.pack_into(buffer, offset)
self._FORMAT.pack_into(
buffer,
offset + LinSynchFieldEvent.SIZE,
self.supplier_id,
self.message_id,
self.nad,
self.id,
self.dlc,
self.checksum_model,
)
@dataclass
class LinDatabyteTimestampEvent:
_FORMAT: ClassVar[struct.Struct] = struct.Struct("9Q")
SIZE: ClassVar[int] = LinMessageDescriptor.SIZE + _FORMAT.size
lin_msg_descr_event: LinMessageDescriptor
databyte_timestamps: tuple[int]
@classmethod
def unpack_from(cls, buffer: bytes, offset: int = 0) -> Self:
lin_msg_descr_event = LinMessageDescriptor.unpack_from(buffer, offset)
databyte_timestamps = cls._FORMAT.unpack_from(buffer, offset + LinMessageDescriptor.SIZE)
return cls(
lin_msg_descr_event,
databyte_timestamps,
)
def pack_into(self, buffer: bytearray, offset: int) -> None:
self.lin_msg_descr_event.pack_into(buffer, offset)
self._FORMAT.pack_into(
buffer, offset + LinMessageDescriptor.SIZE, *self.databyte_timestamps
)
@dataclass
class LinMessage2(ObjectWithHeader[ObjectHeader]):
_FORMAT_V1: ClassVar[struct.Struct] = struct.Struct("8sHBBBBBBB3s")
_FORMAT_V2: ClassVar[struct.Struct] = struct.Struct("I")
_FORMAT_V3: ClassVar[struct.Struct] = struct.Struct("dII")
_V1_SIZE: ClassVar[int] = ObjectHeader.SIZE + LinDatabyteTimestampEvent.SIZE + _FORMAT_V1.size
_V2_SIZE: ClassVar[int] = _V1_SIZE + _FORMAT_V2.size
_V3_SIZE: ClassVar[int] = _V2_SIZE + _FORMAT_V3.size
header: ObjectHeader
# V1
lin_timestamp_event: LinDatabyteTimestampEvent
data: bytes
crc: int
direction: int
simulated: int
is_etf: int
etf_assoc_index: int
etf_assoc_etf_id: int
fsm_id: int
fsm_state: int
reserved: bytes
# V2
resp_baudrate: int
# V3
exact_header_baudrate: float
early_stopbit_offset: int
early_stopbit_offset_response: int
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer)
lin_timestamp_event = LinDatabyteTimestampEvent.unpack_from(buffer, ObjectHeader.SIZE)
(
data_,
crc,
direction,
simulated,
is_etf,
etf_assoc_index,
etf_assoc_etf_id,
fsm_id,
fsm_state,
reserved,
) = cls._FORMAT_V1.unpack_from(buffer, ObjectHeader.SIZE + LinDatabyteTimestampEvent.SIZE)
if header.base.object_size >= cls._V2_SIZE:
(resp_baudrate,) = cls._FORMAT_V2.unpack_from(buffer, cls._V1_SIZE)
else:
resp_baudrate = 0
if header.base.object_size >= cls._V3_SIZE:
(
exact_header_baudrate,
early_stopbit_offset,
early_stopbit_offset_response,
) = cls._FORMAT_V3.unpack_from(buffer, cls._V2_SIZE)
else:
exact_header_baudrate = 0
early_stopbit_offset = 0
early_stopbit_offset_response = 0
return cls(
header,
lin_timestamp_event,
data_,
crc,
direction,
simulated,
is_etf,
etf_assoc_index,
etf_assoc_etf_id,
fsm_id,
fsm_state,
reserved,
resp_baudrate,
exact_header_baudrate,
early_stopbit_offset,
early_stopbit_offset_response,
)
def pack(self) -> bytes:
buffer = bytearray(self.header.base.object_size)
self.header.pack_into(buffer, 0)
self.lin_timestamp_event.pack_into(buffer, ObjectHeader.SIZE)
self._FORMAT_V1.pack_into(
buffer,
ObjectHeader.SIZE + LinDatabyteTimestampEvent.SIZE,
self.data,
self.crc,
self.direction,
self.simulated,
self.is_etf,
self.etf_assoc_index,
self.etf_assoc_etf_id,
self.fsm_id,
self.fsm_state,
self.reserved,
)
if self.header.base.object_size >= self._V2_SIZE:
self._FORMAT_V2.pack_into(buffer, self._V1_SIZE, self.resp_baudrate)
if self.header.base.object_size >= self._V3_SIZE:
self._FORMAT_V3.pack_into(
buffer,
self._V2_SIZE,
self.exact_header_baudrate,
self.early_stopbit_offset,
self.early_stopbit_offset_response,
)
return bytes(buffer)
vblf-0.3.0/src/vblf/most.py 0000664 0000000 0000000 00000000000 15207004341 0015501 0 ustar 00root root 0000000 0000000 vblf-0.3.0/src/vblf/py.typed 0000664 0000000 0000000 00000000000 15207004341 0015644 0 ustar 00root root 0000000 0000000 vblf-0.3.0/src/vblf/reader.py 0000664 0000000 0000000 00000025307 15207004341 0016002 0 ustar 00root root 0000000 0000000 import logging
import os
import zlib
from collections.abc import Iterator
from contextlib import AbstractContextManager
from io import BytesIO
from types import TracebackType
from typing import Any, BinaryIO, Final, Optional, Union
from vblf.can import (
CanDriverError,
CanDriverErrorExt,
CanDriverHwSync,
CanDriverStatistic,
CanErrorFrame,
CanErrorFrameExt,
CanFdErrorFrame64,
CanFdMessage,
CanFdMessage64,
CanMessage,
CanMessage2,
CanOverloadFrame,
)
from vblf.constants import FILE_SIGNATURE, OBJ_SIGNATURE, OBJ_SIGNATURE_SIZE, ObjType
from vblf.ethernet import EthernetFrameEx, EthernetStatistic
from vblf.flexray import FlexrayVFrReceiveMsgEx
from vblf.general import (
AppText,
AppTrigger,
DriverOverrun,
EnvironmentVariable,
EventComment,
FileStatistics,
FunctionBus,
GlobalMarker,
LogContainer,
NotImplementedObject,
ObjectHeaderBase,
ObjectWithHeader,
RealTimeClock,
SystemVariable,
TriggerCondition,
)
from vblf.lin import LinMessage, LinMessage2
from vblf.tp_diag import DiagRequestInterpretation
LOG = logging.getLogger("vblf")
class BlfReader(AbstractContextManager["BlfReader"]):
"""Binary Log Format (BLF) file reader.
Reads Vector BLF log files and provides an iterator interface to access
the contained objects. Handles automatic decompression of log containers.
:param file: Path to BLF file or file-like object
:raises TypeError: If file parameter is of unsupported type
:raises ValueError: If file format is invalid
:ivar file_statistics: Statistics about the BLF file
:type file_statistics: FileStatistics
"""
def __init__(self, file: Union[str, bytes, os.PathLike[Any], BinaryIO]):
"""Initialize BLF reader.
See class documentation for details.
"""
self._file: BinaryIO
if isinstance(file, (str, bytes, os.PathLike)):
self._file = open(file, "rb") # noqa: SIM115
elif isinstance(file, bytes):
self._file = BytesIO(file)
elif hasattr(file, "read"):
self._file = file
else:
err_msg = "Unsupported type {type(file)}"
raise TypeError(err_msg)
obj_data = self._file.read(FileStatistics.SIZE)
if len(obj_data) < FileStatistics.SIZE or not obj_data.startswith(FILE_SIGNATURE):
err_msg = "Unexpected file format"
raise ValueError(err_msg)
self.file_statistics = FileStatistics.unpack(obj_data)
self._incomplete_data: bytes = b""
self._generator = self._generate_objects(self._file)
def _generate_objects(self, stream: BinaryIO) -> Iterator[ObjectWithHeader[Any]]:
"""Generate objects from the BLF stream.
:param stream: Binary stream containing BLF data
:returns: Iterator yielding parsed BLF objects
"""
while True:
# find start of next object (search for b"LOBJ")
signature = stream.read(OBJ_SIGNATURE_SIZE)
if len(signature) != OBJ_SIGNATURE_SIZE:
self._incomplete_data = signature
break
if signature != OBJ_SIGNATURE:
# skip padding byte and try again
stream.seek(1 - OBJ_SIGNATURE_SIZE, os.SEEK_CUR)
continue
# parse base header of object
header_base_data = signature + stream.read(ObjectHeaderBase.SIZE - OBJ_SIGNATURE_SIZE)
if len(header_base_data) < ObjectHeaderBase.SIZE:
self._incomplete_data = header_base_data
break
else:
header_base = ObjectHeaderBase.unpack(header_base_data)
# read object data
obj_data = header_base_data + stream.read(
header_base.object_size - ObjectHeaderBase.SIZE
)
if len(obj_data) < header_base.object_size:
self._incomplete_data = obj_data
break
# find class for given object_type
obj_class: type[ObjectWithHeader[Any]] = (
OBJ_MAP.get(header_base.object_type) or NotImplementedObject
)
if obj_class is LogContainer:
# decompress data
container = LogContainer.unpack(obj_data)
uncompressed = (
zlib.decompress(container.data)
if self.file_statistics.compression_level > 0
else container.data
)
# prepend incomplete data of previous container
uncompressed = self._incomplete_data + uncompressed
self._incomplete_data = b""
# parse LogContainer data
yield from self._generate_objects(BytesIO(uncompressed))
else:
yield obj_class.unpack(obj_data)
def read_object(self) -> Optional[ObjectWithHeader[Any]]:
"""Retrieve the next parsed object from the BLF file.
This method fetches the next object from the underlying generator that
parses the BLF file. If there are no more objects to read, it returns `None`.
:returns: The next parsed BLF object or `None` if the end of the file is reached.
"""
return next(self._generator, None)
def __iter__(self) -> Iterator[ObjectWithHeader[Any]]:
"""Iterate over objects in the BLF file.
:returns: Iterator yielding parsed BLF objects
"""
return self._generator.__iter__()
def __enter__(self) -> "BlfReader":
"""Enter context manager.
:returns: BlfReader instance
"""
return self
def __exit__(
self,
exc_type: Optional[type[BaseException]],
exc_value: Optional[BaseException],
traceback: Optional[TracebackType],
) -> None:
"""Exit context manager and close file.
:param exc_type: Exception type if an exception occurred
:param exc_value: Exception instance if an exception occurred
:param traceback: Traceback if an exception occurred
"""
self._file.close()
OBJ_MAP: Final[dict[ObjType, Optional[type[ObjectWithHeader[Any]]]]] = {
ObjType.UNKNOWN: None,
ObjType.CAN_MESSAGE: CanMessage,
ObjType.CAN_ERROR: CanErrorFrame,
ObjType.CAN_OVERLOAD: CanOverloadFrame,
ObjType.CAN_STATISTIC: CanDriverStatistic,
ObjType.APP_TRIGGER: AppTrigger,
ObjType.ENV_INTEGER: EnvironmentVariable,
ObjType.ENV_DOUBLE: EnvironmentVariable,
ObjType.ENV_STRING: EnvironmentVariable,
ObjType.ENV_DATA: EnvironmentVariable,
ObjType.LOG_CONTAINER: LogContainer,
ObjType.LIN_MESSAGE: LinMessage,
ObjType.LIN_CRC_ERROR: None,
ObjType.LIN_DLC_INFO: None,
ObjType.LIN_RCV_ERROR: None,
ObjType.LIN_SND_ERROR: None,
ObjType.LIN_SLV_TIMEOUT: None,
ObjType.LIN_SCHED_MODCH: None,
ObjType.LIN_SYN_ERROR: None,
ObjType.LIN_BAUDRATE: None,
ObjType.LIN_SLEEP: None,
ObjType.LIN_WAKEUP: None,
ObjType.MOST_SPY: None,
ObjType.MOST_CTRL: None,
ObjType.MOST_LIGHTLOCK: None,
ObjType.MOST_STATISTIC: None,
ObjType.reserved_1: None,
ObjType.reserved_2: None,
ObjType.reserved_3: None,
ObjType.FLEXRAY_DATA: None,
ObjType.FLEXRAY_SYNC: None,
ObjType.CAN_DRIVER_ERROR: CanDriverError,
ObjType.MOST_PKT: None,
ObjType.MOST_PKT2: None,
ObjType.MOST_HWMODE: None,
ObjType.MOST_REG: None,
ObjType.MOST_GENREG: None,
ObjType.MOST_NETSTATE: None,
ObjType.MOST_DATALOST: None,
ObjType.MOST_TRIGGER: None,
ObjType.FLEXRAY_CYCLE: None,
ObjType.FLEXRAY_MESSAGE: None,
ObjType.LIN_CHECKSUM_INFO: None,
ObjType.LIN_SPIKE_EVENT: None,
ObjType.CAN_DRIVER_SYNC: CanDriverHwSync,
ObjType.FLEXRAY_STATUS: None,
ObjType.GPS_EVENT: None,
ObjType.FR_ERROR: None,
ObjType.FR_STATUS: None,
ObjType.FR_STARTCYCLE: None,
ObjType.FR_RCVMESSAGE: None,
ObjType.REALTIMECLOCK: RealTimeClock,
ObjType.AVAILABLE2: None,
ObjType.AVAILABLE3: None,
ObjType.LIN_STATISTIC: None,
ObjType.J1708_MESSAGE: None,
ObjType.J1708_VIRTUAL_MSG: None,
ObjType.LIN_MESSAGE2: LinMessage2,
ObjType.LIN_SND_ERROR2: None,
ObjType.LIN_SYN_ERROR2: None,
ObjType.LIN_CRC_ERROR2: None,
ObjType.LIN_RCV_ERROR2: None,
ObjType.LIN_WAKEUP2: None,
ObjType.LIN_SPIKE_EVENT2: None,
ObjType.LIN_LONG_DOM_SIG: None,
ObjType.APP_TEXT: AppText,
ObjType.FR_RCVMESSAGE_EX: FlexrayVFrReceiveMsgEx,
ObjType.MOST_STATISTICEX: None,
ObjType.MOST_TXLIGHT: None,
ObjType.MOST_ALLOCTAB: None,
ObjType.MOST_STRESS: None,
ObjType.ETHERNET_FRAME: None,
ObjType.SYS_VARIABLE: SystemVariable,
ObjType.CAN_ERROR_EXT: CanErrorFrameExt,
ObjType.CAN_DRIVER_ERROR_EXT: CanDriverErrorExt,
ObjType.LIN_LONG_DOM_SIG2: None,
ObjType.MOST_150_MESSAGE: None,
ObjType.MOST_150_PKT: None,
ObjType.MOST_ETHERNET_PKT: None,
ObjType.MOST_150_MESSAGE_FRAGMENT: None,
ObjType.MOST_150_PKT_FRAGMENT: None,
ObjType.MOST_ETHERNET_PKT_FRAGMENT: None,
ObjType.MOST_SYSTEM_EVENT: None,
ObjType.MOST_150_ALLOCTAB: None,
ObjType.MOST_50_MESSAGE: None,
ObjType.MOST_50_PKT: None,
ObjType.CAN_MESSAGE2: CanMessage2,
ObjType.LIN_UNEXPECTED_WAKEUP: None,
ObjType.LIN_SHORT_OR_SLOW_RESPONSE: None,
ObjType.LIN_DISTURBANCE_EVENT: None,
ObjType.SERIAL_EVENT: None,
ObjType.OVERRUN_ERROR: DriverOverrun,
ObjType.EVENT_COMMENT: EventComment,
ObjType.WLAN_FRAME: None,
ObjType.WLAN_STATISTIC: None,
ObjType.MOST_ECL: None,
ObjType.GLOBAL_MARKER: GlobalMarker,
ObjType.AFDX_FRAME: None,
ObjType.AFDX_STATISTIC: None,
ObjType.KLINE_STATUSEVENT: None,
ObjType.CAN_FD_MESSAGE: CanFdMessage,
ObjType.CAN_FD_MESSAGE_64: CanFdMessage64,
ObjType.ETHERNET_RX_ERROR: None,
ObjType.ETHERNET_STATUS: None,
ObjType.CAN_FD_ERROR_64: CanFdErrorFrame64,
ObjType.LIN_SHORT_OR_SLOW_RESPONSE2: None,
ObjType.AFDX_STATUS: None,
ObjType.AFDX_BUS_STATISTIC: None,
ObjType.reserved_4: None,
ObjType.AFDX_ERROR_EVENT: None,
ObjType.A429_ERROR: None,
ObjType.A429_STATUS: None,
ObjType.A429_BUS_STATISTIC: None,
ObjType.A429_MESSAGE: None,
ObjType.ETHERNET_STATISTIC: EthernetStatistic,
ObjType.reserved_5: None,
ObjType.reserved_6: None,
ObjType.reserved_7: None,
ObjType.TEST_STRUCTURE: None,
ObjType.DIAG_REQUEST_INTERPRETATION: DiagRequestInterpretation,
ObjType.ETHERNET_FRAME_EX: EthernetFrameEx,
ObjType.ETHERNET_FRAME_FORWARDED: None,
ObjType.ETHERNET_ERROR_EX: None,
ObjType.ETHERNET_ERROR_FORWARDED: None,
ObjType.FUNCTION_BUS: FunctionBus,
ObjType.DATA_LOST_BEGIN: None,
ObjType.DATA_LOST_END: None,
ObjType.WATER_MARK_EVENT: None,
ObjType.TRIGGER_CONDITION: TriggerCondition,
}
vblf-0.3.0/src/vblf/tp_diag.py 0000664 0000000 0000000 00000006231 15207004341 0016142 0 ustar 00root root 0000000 0000000 import struct
from dataclasses import dataclass
from typing import ClassVar
from typing_extensions import Self
from vblf.general import ObjectHeader, ObjectWithHeader
@dataclass
class DiagRequestInterpretation(ObjectWithHeader[ObjectHeader]):
_FORMAT: ClassVar[struct.Struct] = struct.Struct("IIIIII")
diag_description_handle: int
diag_variant_handle: int
diag_service_handle: int
ecu_qualifier_length: int
variant_qualifier_length: int
service_qualifier_length: int
ecu_qualifier: str
variant_qualifier: str
service_qualifier: str
@classmethod
def unpack(cls, buffer: bytes) -> Self:
header = ObjectHeader.unpack_from(buffer, 0)
(
diag_description_handle,
diag_variant_handle,
diag_service_handle,
ecu_qualifier_length,
variant_qualifier_length,
service_qualifier_length,
) = cls._FORMAT.unpack_from(buffer, ObjectHeader.SIZE)
ecu_qualifier_offset = ObjectHeader.SIZE + cls._FORMAT.size
variant_qualifier_offset = ecu_qualifier_offset + ecu_qualifier_length
service_qualifier_offset = variant_qualifier_offset + variant_qualifier_length
ecu_qualifier = buffer[
ecu_qualifier_offset : ecu_qualifier_offset + ecu_qualifier_length
].decode("utf-8")
variant_qualifier = buffer[
variant_qualifier_offset : variant_qualifier_offset + variant_qualifier_length
].decode("utf-8")
service_qualifier = buffer[
service_qualifier_offset : service_qualifier_offset + service_qualifier_length
].decode("utf-8")
return cls(
header,
diag_description_handle,
diag_variant_handle,
diag_service_handle,
ecu_qualifier_length,
variant_qualifier_length,
service_qualifier_length,
ecu_qualifier,
variant_qualifier,
service_qualifier,
)
def pack(self) -> bytes:
buffer = bytearray(self.header.base.object_size)
self.header.pack_into(buffer, 0)
self._FORMAT.pack_into(
buffer,
ObjectHeader.SIZE,
self.diag_description_handle,
self.diag_variant_handle,
self.diag_service_handle,
self.ecu_qualifier_length,
self.variant_qualifier_length,
self.service_qualifier_length,
)
ecu_qualifier_offset = ObjectHeader.SIZE + self._FORMAT.size
variant_qualifier_offset = ecu_qualifier_offset + self.ecu_qualifier_length
service_qualifier_offset = variant_qualifier_offset + self.variant_qualifier_length
buffer[ecu_qualifier_offset : ecu_qualifier_offset + self.ecu_qualifier_length] = (
self.ecu_qualifier.encode("utf-8")
)
buffer[
variant_qualifier_offset : variant_qualifier_offset + self.variant_qualifier_length
] = self.variant_qualifier.encode("utf-8")
buffer[
service_qualifier_offset : service_qualifier_offset + self.service_qualifier_length
] = self.service_qualifier.encode("utf-8")
return bytes(buffer)
vblf-0.3.0/src/vblf/writer.py 0000664 0000000 0000000 00000012176 15207004341 0016054 0 ustar 00root root 0000000 0000000 import datetime
import os
import time
import zlib
from contextlib import AbstractContextManager
from typing import Any, BinaryIO, Final
from vblf.constants import Compression, ObjFlags
from vblf.general import FileStatistics, HeaderWithBase, LogContainer, ObjectWithHeader, SystemTime
BYTE_ALIGNMENT: Final = 8
class BlfWriter(AbstractContextManager["BlfWriter"]):
"""Binary Log Format (BLF) file writer.
Writes Vector BLF log files with optional compression. Handles automatic buffering
and container creation for optimal file structure.
:param file: Path to BLF file or file-like object
:param compression_level: Compression level (0-9), defaults to no compression
:param buffer_size: Size of internal buffer in bytes before flushing, defaults to 128 KiB
:raises TypeError: If file parameter is of unsupported type
"""
def __init__(
self,
file: os.PathLike[Any],
compression_level: Compression = Compression.NONE,
buffer_size: int = 128 * 1024,
) -> None:
"""Initialize BLF writer.
See class documentation for details.
"""
self._buffer_size = buffer_size
self._buffer = bytearray()
self._measurement_start_time = time.time()
self._time_of_last_object = self._measurement_start_time
self._file: BinaryIO
if isinstance(file, (str, bytes, os.PathLike)):
self._file = open(file, "wb") # noqa: SIM115
elif hasattr(file, "write"):
self._file = file
else:
err_msg = f"Unsupported type {type(file)}"
raise TypeError(err_msg)
# write file statistics
self._file_statistics = FileStatistics.new()
self._file_statistics.compression_level = compression_level
self._file.write(self._file_statistics.pack())
def write(self, obj: ObjectWithHeader[HeaderWithBase]) -> None:
"""Write an object to the BLF file.
The object is first buffered and only written to disk when the buffer is full
or when the file is closed.
:param obj: Object to write
:raises ValueError: If object size doesn't match its header
"""
# byte alignment
if rest := len(self._buffer) % BYTE_ALIGNMENT:
self._buffer.extend(b"\x00" * (BYTE_ALIGNMENT - rest))
obj_data = obj.pack()
if len(obj_data) != obj.header.base.object_size:
err_msg = f"Object size mismatch: {len(obj_data)} != {obj.header.base.object_size}"
raise ValueError(err_msg)
self._buffer.extend(obj_data)
self._file_statistics.object_count += 1
self._file_statistics.uncompressed_file_size += len(obj_data)
self._time_of_last_object = time.time()
if len(self._buffer) >= self._buffer_size:
self._flush_container()
def _flush_container(self) -> None:
"""Flush the internal buffer to disk.
Creates a LogContainer with the buffered data and writes it to the file.
Handles compression if enabled.
"""
if not self._buffer:
return
# byte alignment
if rest := self._file.tell() % BYTE_ALIGNMENT:
self._file.write(b"\x00" * (BYTE_ALIGNMENT - rest))
buffer, self._buffer = self._buffer[: self._buffer_size], self._buffer[self._buffer_size :]
if self._file_statistics.compression_level > Compression.NONE:
compressed_data = zlib.compress(buffer, level=self._file_statistics.compression_level)
else:
compressed_data = bytes(buffer)
log_container = LogContainer.new(
data=compressed_data,
time_stamp=round((time.time() - self._measurement_start_time) * 1e9),
flags=ObjFlags.TIME_ONE_NANS,
)
self._file.write(log_container.pack())
self._file_statistics.file_size = self._file.tell()
def _update_file_statistics(self) -> None:
"""Update file statistics and write them to the beginning of the file.
Updates the object count, file size and timestamps in the file header.
"""
self._file_statistics.last_object_time = SystemTime.from_datetime(
datetime.datetime.fromtimestamp(self._time_of_last_object, datetime.timezone.utc)
)
self._file.seek(0)
self._file.write(self._file_statistics.pack())
def close(self) -> None:
"""Close the BLF file.
Flushes any remaining buffered data and updates file statistics before closing.
"""
if not self._file.closed:
while self._buffer:
self._flush_container()
self._update_file_statistics()
self._file.close()
def __enter__(self) -> "BlfWriter":
"""Enter context manager.
:returns: BlfWriter instance
"""
return self
def __exit__(self, exc_type: Any, exc_value: Any, traceback: Any) -> None:
"""Exit context manager and close file.
:param exc_type: Exception type if an exception occurred
:param exc_value: Exception instance if an exception occurred
:param traceback: Traceback if an exception occurred
"""
self.close()
vblf-0.3.0/tests/ 0000775 0000000 0000000 00000000000 15207004341 0013601 5 ustar 00root root 0000000 0000000 vblf-0.3.0/tests/__init__.py 0000664 0000000 0000000 00000000104 15207004341 0015705 0 ustar 00root root 0000000 0000000 from pathlib import Path
DATA_DIR = Path(__file__).parent / "data"
vblf-0.3.0/tests/data/ 0000775 0000000 0000000 00000000000 15207004341 0014512 5 ustar 00root root 0000000 0000000 vblf-0.3.0/tests/data/A429_BUS_STATISTIC.lobj 0000664 0000000 0000000 00000001110 15207004341 0020132 0 ustar 00root root 0000000 0000000 LOBJ H p """"""""" 3333DDDDUUUUffffwwˆˆ™™ªª»»ÌÌÝÝîîÿÿ
! " # $ % &