HOW‑TOs#

Quick, task‑oriented guides for common workflows.

Import once, reuse the database#

Persist a parsed A2L as SQLite (.a2ldb) so you don’t reparse on every run:

from pya2l import DB

db = DB()
session = db.import_a2l("ASAP2_Demo_V161.a2l")  # writes ASAP2_Demo_V161.a2ldb

Later, open the database without reparsing:

from pya2l import DB

db = DB()
session = db.open_existing("ASAP2_Demo_V161")  # .a2ldb suffix implied

Export back to A2L or JSON#

Export preserves all model attributes (measurements, characteristics, conversions, metadata, IF_DATA, annotations, etc.) with no data loss.

Basic A2L export#

Round‑trip a database back to A2L text format:

from pya2l import export_a2l

# Export with implicit .a2ldb extension
export_a2l("ASAP2_Demo_V161", "exported.a2l")

# Or provide full database path
export_a2l("path/to/MyProject.a2ldb", "exported.a2l")

# Export to stdout (useful for piping)
import sys
from pya2l.imex.a2l_exporter import export_a2l_to_stream

with open("MyProject.a2ldb", "rb"):
    pass  # ensure DB exists
export_a2l_to_stream("MyProject.a2ldb", sys.stdout)

JSON export#

Export to JSON for external tools, scripts, or analysis:

from pya2l.imex.json_exporter import export_json

# Export entire database to JSON
export_json("ASAP2_Demo_V161.a2ldb", "exported.json")

# JSON structure mirrors A2L hierarchy:
# {
#   "project": { ... },
#   "modules": [
#     {
#       "name": "ModuleName",
#       "measurements": [ ... ],
#       "characteristics": [ ... ],
#       "compu_methods": [ ... ],
#       ...
#     }
#   ]
# }

Export after modifications#

Typical workflow: import, modify, export:

from pya2l import DB, export_a2l
from pya2l.api.create import MeasurementCreator

# Import existing A2L
db = DB()
session = db.import_a2l("original.a2l")

# Add new measurement
mc = MeasurementCreator(session)
meas = mc.create_measurement(
    "NewSignal", "Added programmatically",
    "UWORD", "NO_COMPU_METHOD", 1, 0.1,
    0.0, 65535.0, module_name="MyModule"
)
mc.add_ecu_address(meas, 0x50000)
mc.commit()

db.close()

# Export modified database
export_a2l("original", "modified.a2l")

Export completeness guarantees#

The exporters (as of v0.10.2+) export all optional model elements:

  • AXIS_PTS: ECU_ADDRESS_EXTENSION, EXTENDED_LIMITS, FORMAT, GUARD_RAILS, MAX_REFRESH, MODEL_LINK, MONOTONY, PHYS_UNIT, SYMBOL_LINK, etc.

  • BLOB: ADDRESS_TYPE, ANNOTATION, DISPLAY_IDENTIFIER, ECU_ADDRESS_EXTENSION, IF_DATA, MAX_REFRESH, MODEL_LINK, SYMBOL_LINK, CALIBRATION_ACCESS

  • CHARACTERISTIC: All 20+ optional elements including CALIBRATION_ACCESS, COMPARISON_QUANTITY, DEPENDENT_CHARACTERISTIC, DISCRETE, DISPLAY_IDENTIFIER, ECU_ADDRESS_EXTENSION, ENCODING, EXTENDED_LIMITS, FORMAT, GUARD_RAILS, MODEL_LINK, NUMBER, SYMBOL_LINK, VIRTUAL_CHARACTERISTIC, axis descriptors

  • MEASUREMENT: ADDRESS_TYPE, BIT_OPERATION (with shifts), BYTE_ORDER, DISCRETE, DISPLAY_IDENTIFIER, ECU_ADDRESS, ECU_ADDRESS_EXTENSION, ERROR_MASK, FORMAT, FUNCTION_LIST, LAYOUT, MATRIX_DIM, MAX_REFRESH, MODEL_LINK, PHYS_UNIT, SYMBOL_LINK, VIRTUAL

  • IF_DATA: Preserved as raw blocks; custom AML parsing supported

  • Annotations: Preserved with labels, origins, and text blocks

This ensures lossless roundtrips: original.a2l → import → export → output.a2l will preserve all content (modulo whitespace/formatting).

CLI import/export (a2ldb-imex)#

Use the bundled console script for quick import/export tasks:

Basic usage#

# Show help and available options
a2ldb-imex -h

# Show version
a2ldb-imex -V

Import examples#

# Import A2L (creates .a2ldb next to input file)
a2ldb-imex -i path/to/file.a2l

# Import with explicit encoding
a2ldb-imex -i file.a2l -E utf-8

# Create .a2ldb in current working directory instead of next to input
a2ldb-imex -i path/to/file.a2l -L

# Silence progress bar
a2ldb-imex -i file.a2l -p

# Combine options: UTF-8 encoding, local DB, silent mode
a2ldb-imex -i examples\\ASAP2_Demo_V161.a2l -E utf-8 -L -p

Export examples#

# Export .a2ldb back to A2L text (writes to file)
a2ldb-imex -e ASAP2_Demo_V161.a2ldb -o exported.a2l

# Export to stdout (useful for piping or inspection)
a2ldb-imex -e file.a2ldb > exported.a2l

# Export specific module (if database contains multiple)
a2ldb-imex -e file.a2ldb -m ModuleName -o module_only.a2l

Typical workflows#

Validation pipeline (import, validate, re-export):

# Import
a2ldb-imex -i original.a2l

# (Use Python API or other tools to inspect/validate the .a2ldb)

# Re-export
a2ldb-imex -e original.a2ldb -o validated.a2l

Format conversion (A2L ↔ JSON):

# Import A2L
a2ldb-imex -i input.a2l

# Export to JSON (use Python API)
python -c "from pya2l.imex.json_exporter import export_json; export_json('input.a2ldb', 'output.json')"

Batch processing:

# Windows batch
for %%f in (*.a2l) do a2ldb-imex -i "%%f" -L

# Linux/macOS shell
for f in *.a2l; do a2ldb-imex -i "$f" -L; done

Concurrent access and export safety#

The database uses SQLite’s WAL (Write-Ahead Logging) mode to support concurrent readers during export operations.

Safe concurrent export#

Multiple processes can read/export the same database simultaneously:

from pya2l import export_a2l
import multiprocessing

def export_worker(db_path, output_path):
    """Worker function for parallel exports."""
    export_a2l(db_path, output_path)

# Export the same DB to multiple formats concurrently
with multiprocessing.Pool(3) as pool:
    pool.starmap(export_worker, [
        ("project.a2ldb", "export1.a2l"),
        ("project.a2ldb", "export2.a2l"),
        ("project.a2ldb", "export3.a2l"),
    ])

Export while another process writes#

Exports can run while another process modifies the database (though the export sees a snapshot from when it started):

import threading
from pya2l import DB, export_a2l
from pya2l.api.create import MeasurementCreator

def writer_task():
    """Modify database in background."""
    db = DB()
    session = db.open_existing("project")
    mc = MeasurementCreator(session)
    # ... add measurements ...
    mc.commit()
    db.close()

def export_task():
    """Export database concurrently."""
    export_a2l("project", "concurrent_export.a2l")

# Start both tasks simultaneously
t1 = threading.Thread(target=writer_task)
t2 = threading.Thread(target=export_task)
t1.start()
t2.start()
t1.join()
t2.join()

The exporter sets query_only=ON pragma and uses a 5-second busy timeout, ensuring robust operation under concurrent load without “database is locked” errors.

Dump measurements to Excel#

Use pandas to move selected fields into Excel (install pandas and openpyxl first):

import pandas as pd
from pya2l import DB, model

session = DB().open_existing("ASAP2_Demo_V161")
q = (
    session.query(
        model.Measurement.name,
        model.Measurement.datatype,
        model.Measurement.conversion,
        model.Measurement.ecu_address,
    )
    .order_by(model.Measurement.name)
)
df = pd.read_sql(q.statement, session.bind)
df.to_excel("measurements.xlsx", index=False)

Handle encodings and quiet mode#

Override the default latin-1 import encoding and silence the progress bar:

from pya2l import DB

db = DB()
session = db.import_a2l(
    "my_file.a2l",
    encoding="utf-8",
    progress_bar=False,
    loglevel="ERROR",  # also suppresses progress
)

Creating A2L content programmatically#

Use the Creator API (pya2l.api.create) to build or augment A2L databases. All creator classes follow a consistent pattern: instantiate with a session, call create_* methods to add entities, then commit.

Basic workflow#

from pya2l import DB
from pya2l.api.create import ProjectCreator, ModuleCreator

db = DB()
session = db.open_create("MyProject.a2ldb")

# Create project
pc = ProjectCreator(session)
project = pc.create_project("DemoProject", "Example ECU calibration")

# Create module
mc = ModuleCreator(session)
module = mc.create_module("DemoModule", "Main module", project=project)

# Commit changes
mc.commit()
db.close()

Creating conversion methods (COMPU_METHOD)#

Define how raw ECU values map to physical units:

from pya2l.api.create import CompuMethodCreator

cmc = CompuMethodCreator(session)

# Linear conversion: phys = a*x + b
cm_linear = cmc.create_compu_method(
    "CM_Temperature", "Temperature conversion",
    "LINEAR", "%6.2", "°C", module_name="DemoModule"
)
cmc.add_coeffs_linear(cm_linear, offset=-40.0, factor=0.1)

# Rational conversion: phys = (a*x² + b*x + c) / (d*x² + e*x + f)
cm_rational = cmc.create_compu_method(
    "CM_Pressure", "Non-linear pressure", "RAT_FUNC",
    "%8.3", "bar", module_name="DemoModule"
)
cmc.add_formula_rational(cm_rational, a=0, b=0.01, c=0, d=0, e=0, f=1)

# Tabular conversion (value pairs)
cm_tab = cmc.create_compu_method(
    "CM_GearState", "Gear position", "TAB_VERB",
    "%d", "", module_name="DemoModule"
)
cmc.add_compu_tab_verbal_range(cm_tab, [
    (0, 0, "Neutral"),
    (1, 1, "First"),
    (2, 2, "Second"),
    (3, 3, "Third"),
])

Creating measurements#

Measurements are ECU signals read by calibration tools:

from pya2l.api.create import MeasurementCreator

mec = MeasurementCreator(session)

# Scalar measurement
meas = mec.create_measurement(
    "EngineSpeed", "Engine rotational speed",
    "UWORD", "CM_Speed", resolution=1, accuracy=0.5,
    lower_limit=0.0, upper_limit=8000.0,
    module_name="DemoModule"
)
mec.add_ecu_address(meas, 0x10000)

# Measurement with matrix dimensions (e.g., 2D sensor array)
meas_matrix = mec.create_measurement(
    "SensorArray", "Temperature sensor grid",
    "SWORD", "CM_Temperature", resolution=1, accuracy=0.1,
    lower_limit=-40.0, upper_limit=150.0,
    module_name="DemoModule"
)
mec.add_matrix_dim(meas_matrix, dims=[8, 8])  # 8×8 grid
mec.add_ecu_address(meas_matrix, 0x20000)

# Measurement with symbol link (no direct address)
meas_sym = mec.create_measurement(
    "SymLinkedSignal", "Signal via symbol",
    "FLOAT32_IEEE", "CM_Voltage", resolution=1, accuracy=0.01,
    lower_limit=0.0, upper_limit=5.0,
    module_name="DemoModule"
)
mec.add_symbol_link(meas_sym, "g_sensor_voltage", offset=0)

Creating characteristics (calibration parameters)#

Characteristics are tunable parameters written by calibration tools:

from pya2l.api.create import CharacteristicCreator

cc = CharacteristicCreator(session)

# Scalar (VALUE) characteristic
char_value = cc.create_characteristic(
    "InjectionDuration", "Fuel injection time",
    "VALUE", 0x30000, "RL_UWORD", 0.0, "CM_Time",
    0.0, 100.0, module_name="DemoModule"
)

# Curve (1D lookup table)
char_curve = cc.create_characteristic(
    "ThrottleCurve", "Throttle position vs. airflow",
    "CURVE", 0x31000, "RL_CURVE_8", 0.0, "CM_Airflow",
    0.0, 1000.0, module_name="DemoModule"
)
cc.add_axis_descr(char_curve, "STD_AXIS", "NO_INPUT_QUANTITY",
                  "CM_Percent", 8, 0.0, 100.0)

# Map (2D lookup table)
char_map = cc.create_characteristic(
    "InjectionMap", "RPM vs. Load injection map",
    "MAP", 0x32000, "RL_MAP_16x16", 0.0, "CM_Time",
    0.0, 50.0, module_name="DemoModule"
)
# X-axis: RPM
cc.add_axis_descr(char_map, "STD_AXIS", "EngineSpeed",
                  "CM_Speed", 16, 0.0, 8000.0)
# Y-axis: Load
cc.add_axis_descr(char_map, "STD_AXIS", "EngineLoad",
                  "CM_Percent", 16, 0.0, 100.0)

Creating axis points (shared axes)#

AXIS_PTS define reusable axis definitions for multiple characteristics:

from pya2l.api.create import AxisPtsCreator

apc = AxisPtsCreator(session)

axis = apc.create_axis_pts(
    "RPM_Axis", "Standard RPM breakpoints",
    0x40000, "NO_INPUT_QUANTITY", "RL_AXIS_16",
    0.0, "CM_Speed", 16, 0.0, 8000.0,
    module_name="DemoModule"
)

Creating record layouts#

Record layouts describe memory structures for characteristics/measurements:

from pya2l.api.create import RecordLayoutCreator

rlc = RecordLayoutCreator(session)

# Simple scalar layout
rl = rlc.create_record_layout("RL_UWORD", module_name="DemoModule")
rlc.add_fnc_values(rl, position=1, datatype="UWORD", index_mode="ALTERNATE",
                   address_type="DIRECT")

# Curve layout (axis + values)
rl_curve = rlc.create_record_layout("RL_CURVE_8", module_name="DemoModule")
rlc.add_axis_pts_x(rl_curve, position=1, datatype="UWORD", index_incr="INDEX",
                   address_type="DIRECT")
rlc.add_fnc_values(rl_curve, position=2, datatype="UWORD", index_mode="ALTERNATE",
                   address_type="DIRECT")

Organizing with groups and functions#

Group related entities for better organization:

mc = ModuleCreator(session)

# Create a function (logical grouping)
func = mc.add_function(
    module, name="InjectionControl",
    long_identifier="Fuel injection calibration parameters"
)
mc.add_def_characteristic(func, ["InjectionDuration", "InjectionMap"])
mc.add_ref_characteristic(func, ["ThrottleCurve"])  # read-only reference
mc.add_in_measurement(func, ["EngineSpeed", "EngineLoad"])

# Create a group (GUI folder structure)
grp = mc.add_group(
    module, name="EngineParams",
    long_identifier="All engine-related parameters"
)
mc.add_group_ref_characteristic(grp, ["InjectionDuration", "InjectionMap"])
mc.add_group_ref_measurement(grp, ["EngineSpeed"])
mc.add_group_sub_group(grp, ["AdvancedSettings"])  # nested groups

Adding units#

Define physical units for conversions:

mc = ModuleCreator(session)

unit_rpm = mc.add_unit(
    module, name="rpm",
    long_identifier="Revolutions per minute",
    display="rpm", type_str="DERIVED"
)

unit_celsius = mc.add_unit(
    module, name="degC",
    long_identifier="Degrees Celsius",
    display="°C", type_str="TEMPERATURE"
)

Complete example: building a minimal ECU database#

from pya2l import DB
from pya2l.api.create import (
    ProjectCreator, ModuleCreator, CompuMethodCreator,
    MeasurementCreator, CharacteristicCreator,
    RecordLayoutCreator
)

db = DB()
session = db.open_create("MinimalECU.a2ldb")

# Project and module
pc = ProjectCreator(session)
project = pc.create_project("MinimalECU", "Minimal ECU example")

mc = ModuleCreator(session)
module = mc.create_module("Engine", "Engine control", project=project)

# Unit
mc.add_unit(module, name="rpm", long_identifier="RPM",
            display="rpm", type_str="DERIVED")

# Conversion
cmc = CompuMethodCreator(session)
cm = cmc.create_compu_method(
    "CM_RPM", "RPM conversion", "LINEAR",
    "%8.2", "rpm", module_name="Engine"
)
cmc.add_coeffs_linear(cm, offset=0.0, factor=0.25)

# Record layout
rlc = RecordLayoutCreator(session)
rl = rlc.create_record_layout("RL_UWORD", module_name="Engine")
rlc.add_fnc_values(rl, position=1, datatype="UWORD",
                   index_mode="ALTERNATE", address_type="DIRECT")

# Measurement
mec = MeasurementCreator(session)
meas = mec.create_measurement(
    "EngineSpeed", "Current engine speed",
    "UWORD", "CM_RPM", resolution=1, accuracy=1.0,
    lower_limit=0.0, upper_limit=8000.0,
    module_name="Engine"
)
mec.add_ecu_address(meas, 0x100000)

# Characteristic
cc = CharacteristicCreator(session)
char = cc.create_characteristic(
    "IdleSpeed", "Target idle speed",
    "VALUE", 0x200000, "RL_UWORD", 0.0, "CM_RPM",
    500.0, 1500.0, module_name="Engine"
)

# Group
grp = mc.add_group(module, name="BasicParams",
                   long_identifier="Basic parameters")
mc.add_group_ref_characteristic(grp, ["IdleSpeed"])
mc.add_group_ref_measurement(grp, ["EngineSpeed"])

# Commit and close
mc.commit()
db.close()

# Export to A2L
from pya2l import export_a2l
export_a2l("MinimalECU", "MinimalECU.a2l")

Tips for using the Creator API#

  • Always commit: Call creator.commit() before closing the database.

  • Module names: Most create_* methods accept module_name="..." to associate entities with a specific module.

  • Referential integrity: Creators validate references (e.g., conversion names, record layout names) exist before creating entities.

  • Incremental builds: Open an existing database with open_existing() and add to it; useful for augmenting imported A2L files.

  • Inspect to verify: Use pya2l.api.inspect classes to query and validate what you’ve created.

Available creator classes#

Full list in pya2l.api.create:

  • ProjectCreator – PROJECT

  • ModuleCreator – MODULE, UNIT, GROUP, FUNCTION, FRAME, TRANSFORMER, etc.

  • CompuMethodCreator – COMPU_METHOD, COMPU_TAB, COMPU_VTAB

  • MeasurementCreator – MEASUREMENT

  • CharacteristicCreator – CHARACTERISTIC

  • AxisPtsCreator – AXIS_PTS

  • RecordLayoutCreator – RECORD_LAYOUT

See pya2l/examples/create_quickstart.py for more examples.