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 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 acceptmodule_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.inspectclasses to query and validate what you’ve created.
Available creator classes#
Full list in pya2l.api.create:
ProjectCreator– PROJECTModuleCreator– MODULE, UNIT, GROUP, FUNCTION, FRAME, TRANSFORMER, etc.CompuMethodCreator– COMPU_METHOD, COMPU_TAB, COMPU_VTABMeasurementCreator– MEASUREMENTCharacteristicCreator– CHARACTERISTICAxisPtsCreator– AXIS_PTSRecordLayoutCreator– RECORD_LAYOUT
See pya2l/examples/create_quickstart.py for more examples.