Tutorial#
This tutorial provides a comprehensive guide to using the pyA2L library for working with ASAM A2L files. It covers basic usage, advanced features, and common use cases.
Basic Usage#
Importing A2L Files#
The first step in working with A2L files is to import them into a database. This allows for faster access and querying of the data.
from pya2l import DB
# Create a database instance
db = DB()
# Import an A2L file into a database
session = db.import_a2l("ASAP2_Demo_V161.a2l")
This creates a SQLite database file with the extension .a2ldb in
your working directory.
Opening Existing Databases#
If you’ve already imported an A2L file, you can open the existing database:
from pya2l import DB
db = DB()
session = db.open_existing("ASAP2_Demo_V161") # No need to specify .a2ldb extension
Alternatively, you can use open_create() which will open an existing
database if it exists, or create a new one if it doesn’t:
session = db.open_create("ASAP2_Demo_V161.a2l") # Creates database from A2L file
# or
session = db.open_create("ASAP2_Demo_V161") # Opens existing database
Accessing Project Information#
Once you have a session, you can access the project information:
from pya2l.api.inspect import Project
# Create a Project instance
project = Project(session)
# Access project attributes
print(project.name)
print(project.header.version)
# Access modules
for module in project.module:
print(module.name)
Working with Modules#
Accessing Module Elements#
You can access various elements within a module:
# Get the first module
module = project.module[0]
# Access module attributes
print(module.name)
print(module.description)
# Access module elements using query methods
measurements = list(module.measurement.query())
characteristics = list(module.characteristic.query())
axis_points = list(module.axis_pts.query())
compu_methods = list(module.compu_method.query())
Understanding query()#
All collections on Module (e.g., measurement, characteristic, axis_pts, compu_method, function, group, frame, unit, record_layout, …) are FilteredList objects. Their .query() method: - Returns a generator of high-level inspect objects (e.g., Measurement, Characteristic), not raw ORM rows. Convert to a list if you need indexing or counting: list(…), len(list(…)). - Accepts an optional predicate function predicate(row) -> bool applied to the underlying ORM row (SQLAlchemy model), not to the inspect wrapper. Use fields as they appear in the A2L schema/DB (e.g., row.name, row.datatype, row.longIdentifier). - Iterates the association in Python and does not push filters down to SQL; for very large modules, consider issuing your own SQLAlchemy queries on pya2l.model.* if you need DB-side filtering.
Common patterns:
# 1) Get the first few measurements (materialize the generator)
meas_list = list(module.measurement.query())
first_three = meas_list[:3]
# 2) Find one by exact name (fast path using predicate on ORM rows)
name = "ENGINE_SPEED"
found = next(module.measurement.query(lambda row: row.name == name), None)
if found:
print("Found:", found.name, found.datatype)
# 3) Prefix or substring match
starts = list(module.characteristic.query(lambda row: row.name.startswith("ENGINE_")))
contains = list(module.characteristic.query(lambda row: "TEMP" in row.name))
# 4) Filter by datatype/limits
float_meas = list(module.measurement.query(
lambda row: row.datatype in ("FLOAT32_IEEE", "FLOAT64_IEEE")
))
# 5) Count (remember query() is a generator)
count_meas = sum(1 for _ in module.measurement.query())
# 6) Sort client-side after materializing
sorted_meas = sorted(module.measurement.query(), key=lambda m: m.name)
# 7) Combine conditions
hi_res = list(module.measurement.query(
lambda row: row.datatype == "UWORD" and (row.upperLimit or 0) > 1000
))
Notes and gotchas: - The predicate gets ORM rows. Attributes sometimes differ from the inspect object’s property names. For example, Group rows use row.groupName and row.groupLongIdentifier; UserRights uses row.userLevelId. Module already wires these up (e.g., Module.group uses FilteredList(…, attr_name=“groupName”)) so you usually only care when writing predicates. - .query() yields inspect wrappers via Klass.get(session, key_attr). That means you can directly access high-level properties on results (e.g., m.physUnit, c.compuMethod, ax.record_layout). - For advanced filtering/ordering/pagination at the database level, query the ORM directly (session.query(pya2l.model.Measurement)…), then map names to inspect objects with Measurement.get(session, name) as needed.
Filtering Queries#
You can filter queries using lambda functions:
# Get all measurements with FLOAT32_IEEE or FLOAT64_IEEE data types
float_measurements = list(module.measurement.query(
lambda x: x.datatype in ("FLOAT32_IEEE", "FLOAT64_IEEE")
))
# Get all characteristics with a specific name pattern
specific_chars = list(module.characteristic.query(
lambda x: x.name.startswith("ENGINE_")
))
Advanced Features#
Working with IF_DATA Sections#
IF_DATA sections contain vendor-specific information. pyA2L parses these
blocks for you and exposes them uniformly on many inspect classes via
the .if_data attribute. You can also parse raw IF_DATA text manually
if needed.
Where you can find IF_DATA (inspect API): - Module.if_data — IF_DATA blocks attached to the MODULE - Measurement.if_data — IF_DATA for MEASUREMENT - Characteristic.if_data — IF_DATA for CHARACTERISTIC - AxisPts.if_data — IF_DATA for AXIS_PTS - Function.if_data — IF_DATA for FUNCTION - Group.if_data — IF_DATA for GROUP - Frame.if_data — IF_DATA for FRAME - ModPar.memoryLayouts[i].if_data and ModPar.memorySegments[i].if_data — IF_DATA under MOD_PAR MEMORY_LAYOUT and MEMORY_SEGMENT
Accessing parsed IF_DATA from inspect objects:
from pya2l.api.inspect import Module
module = Module(session) # or Module(session, "MY_MODULE")
# MODULE-level IF_DATA
print(module.if_data) # list[dict], already parsed
# MEASUREMENT IF_DATA
for meas in module.measurement.query():
if meas.if_data:
print(meas.name, meas.if_data)
# CHARACTERISTIC IF_DATA
for char in module.characteristic.query():
if char.if_data:
print(char.name, char.if_data)
# AXIS_PTS IF_DATA
for ax in module.axis_pts.query():
if ax.if_data:
print(ax.name, ax.if_data)
# FUNCTION / GROUP / FRAME IF_DATA
for fn in module.function.query():
if fn.if_data:
print(fn.name, fn.if_data)
for grp in module.group.query():
if grp.if_data:
print(grp.name, grp.if_data)
for fr in module.frame.query():
if fr.if_data:
print(fr.name, fr.if_data)
# MOD_PAR memory layouts/segments IF_DATA
mp = module.mod_par
if mp:
for i, ml in enumerate(mp.memoryLayouts):
if ml.if_data:
print(f"MEMORY_LAYOUT[{i}]", ml.if_data)
for i, ms in enumerate(mp.memorySegments):
if ms.if_data:
print(f"MEMORY_SEGMENT[{i}] {ms.name}", ms.if_data)
Parsing raw IF_DATA text manually
from pya2l.aml.ifdata_parser import IfDataParser
ifdata_parser = IfDataParser(session)
# Example raw IF_DATA snippet (e.g., from a blob or external source)
ifdata_text = """/begin IF_DATA XCP
/begin SEGMENT 0x01 0x02 0x00 0x00 0x00
/begin CHECKSUM XCP_ADD_44 MAX_BLOCK_SIZE 0xFFFF EXTERNAL_FUNCTION "" /end CHECKSUM
/begin PAGE 0x01 ECU_ACCESS_WITH_XCP_ONLY XCP_READ_ACCESS_WITH_ECU_ONLY XCP_WRITE_ACCESS_NOT_ALLOWED /end PAGE
/begin PAGE 0x00 ECU_ACCESS_WITH_XCP_ONLY XCP_READ_ACCESS_WITH_ECU_ONLY XCP_WRITE_ACCESS_WITH_ECU_ONLY /end PAGE
/end SEGMENT
/end IF_DATA"""
parsed = ifdata_parser.parse(ifdata_text)
print(parsed)
Notes: - The inspect API returns IF_DATA already parsed (list of dictionaries). Use manual parsing only when you have raw text and not a model object. - The exact structure of the parsed dictionaries depends on the vendor-specific schema (e.g., XCP, XCP SEGMENT/PAGE/CHECKSUM).
Creating New A2L Elements#
You can create new A2L elements using the creator classes:
from pya2l.api.create import CompuMethodCreator, MeasurementCreator
# Create a new computation method
cm_creator = CompuMethodCreator(session)
compu_method = cm_creator.create_compu_method(
name="CM_LINEAR",
long_identifier="Linear conversion",
conversion_type="LINEAR",
format_str="%.2f",
unit="km/h"
)
# Add coefficients to the computation method
cm_creator.add_coeffs_linear(compu_method, a=0.1, b=0.0)
# Create a new measurement
meas_creator = MeasurementCreator(session)
measurement = meas_creator.create_measurement(
name="ENGINE_SPEED",
long_identifier="Engine speed",
datatype="UWORD",
compu_method="CM_LINEAR",
lower_limit=0,
upper_limit=8000,
unit="rpm"
)
# Commit changes to the database
session.commit()
Coverage parity and additional creator examples#
The Creator API (pya2l.api.create) aims for feature parity with the Inspector API (pya2l.api.inspect): every entity you can query should be possible to create. Below are examples for some commonly used creator methods recently added.
Create COMPU_TAB, COMPU_VTAB_RANGE, FRAME, TRANSFORMER, TYPEDEFs, and INSTANCE#
from pya2l import DB
from pya2l.api.create import ModuleCreator
from pya2l.api.inspect import Module
session = DB().open_create("ASAP2_Demo_V161.a2l")
mc = ModuleCreator(session)
mod = mc.create_module("DEMO", "Demo ECU module")
# Numeric table conversion
ct = mc.add_compu_tab(
mod, name="CT_DEMO", long_identifier="Demo numeric table",
conversion_type="TAB_NOINTP",
pairs=[(0, 0.0), (100, 1.0)],
default_numeric=0.0,
)
# Verbal range conversion
vr = mc.add_compu_vtab_range(
mod, name="VR_DEMO", long_identifier="State ranges",
triples=[(0.0, 0.49, "OFF"), (0.5, 1.49, "ON"), (1.5, 10.0, "FAULT")],
default_value="OFF",
)
# Frame with measurements
fr = mc.add_frame(
mod, name="FRAME1", long_identifier="Example frame",
scaling_unit=1, rate=10, measurements=["ENGINE_SPEED"],
)
# Transformer with in/out object lists
tr = mc.add_transformer(
mod, name="TR1", version="1.0",
dllname32="tr32.dll", dllname64="tr64.dll",
timeout=1000, trigger="ON_CHANGE", reverse="NONE",
in_objects=["ENGINE_SPEED"], out_objects=["SPEED_PHYS"],
)
# Typedef structure and a component
ts = mc.add_typedef_structure(mod, name="TSig", long_identifier="Signal", size=8)
mc.add_structure_component(ts, name="raw", type_ref="UWORD", offset=0)
# Instance of the typedef
inst = mc.add_instance(mod, name="S1", long_identifier="Inst of TSig",
type_name="TSig", address=0x1000)
# Inspect what we just created
m = Module(session)
assert any(x.name == "CT_DEMO" for x in m.compu_tab.query())
assert any(x.name == "VR_DEMO" for x in m.compu_tab_verb_ranges.query())
assert any(x.name == "FRAME1" for x in m.frame.query())
assert any(x.name == "TR1" for x in m.transformer.query())
session.commit()
Working with Variant Coding#
Variant coding allows for different configurations of the same ECU:
# Access variant coding information
variant_coding = module.variant_coding
# Print variant coding details
print(variant_coding.var_characteristic)
print(variant_coding.var_criterion)
print(variant_coding.var_forbidden_comb)
Common Use Cases#
Extracting Measurement Information#
A common task is to extract information about all measurements:
# Get all measurements
measurements = list(module.measurement.query())
# Print measurement details
for meas in measurements:
print(f"Name: {meas.name}")
print(f"Description: {meas.longIdentifier}")
print(f"Data Type: {meas.datatype}")
print(f"ECU Address: 0x{meas.address:08x}")
print(f"Conversion: {meas.compuMethod.conversionType}")
print(f"Unit: {meas.physUnit}")
print("---")
Working with Characteristics#
Characteristics represent calibration parameters:
# Get all characteristics
characteristics = list(module.characteristic.query())
# Print characteristic details
for char in characteristics:
print(f"Name: {char.name}")
print(f"Type: {char.type}")
print(f"Address: 0x{char.address:08x}")
print(f"Record Layout: {char.depositAttr.name}")
print("---")
Analyzing Record Layouts#
Record layouts define how data is stored in memory:
# Get all record layouts
record_layouts = list(module.record_layout.query())
# Print record layout details
for rl in record_layouts:
print(f"Name: {rl.name}")
print(f"Alignment: {rl.alignment}")
# Print components
if rl.fnc_values:
print(f"Function Values: {rl.fnc_values.position}, {rl.fnc_values.data_type}")
if rl.axis_pts_x:
print(f"X-Axis Points: {rl.axis_pts_x.position}, {rl.axis_pts_x.data_type}")
if rl.axis_pts_y:
print(f"Y-Axis Points: {rl.axis_pts_y.position}, {rl.axis_pts_y.data_type}")
print("---")
Best Practices#
Close Sessions: Always close your database sessions when you’re done:
session.close()
Error Handling: Use try-except blocks to handle potential errors:
try:
session = db.open_existing("NonExistentFile")
except Exception as e:
print(f"Error opening database: {e}")
Commit Changes: When making changes to the database, remember to commit them:
# After making changes
session.commit()
# If something goes wrong, you can roll back
# session.rollback()
Use Query Filters: Filter your queries to improve performance:
# This is more efficient than getting all measurements and filtering in Python
float_measurements = list(module.measurement.query(
lambda x: x.datatype == "FLOAT32_IEEE"
))
Cache Results: For frequently accessed data, consider caching the results:
# Cache all measurements
all_measurements = list(module.measurement.query())
# Use the cached list instead of querying again
float_measurements = [m for m in all_measurements if m.datatype == "FLOAT32_IEEE"]
Conclusion#
This tutorial covered the basics of working with pyA2L. For more
detailed information, refer to the API reference documentation and the
example scripts in the pya2l/examples directory.