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 (XCP, CCP, KWP2000, …). pyA2L parses these blocks and wraps the result in an IfData dataclass that gives you access to both the parsed and raw representation simultaneously.

The IfData dataclass#

Every inspect object that can carry IF_DATA (Module, Measurement, Characteristic, AxisPts, Function, Group, Frame, Instance, Blob, MemoryLayout, MemorySegment) exposes an if_data attribute of type IfData:

  • if_data.if_data_parsedList[Any]: structured dicts produced by the AML-based parser, one entry per /begin IF_DATA block.

  • if_data.if_data_rawlist: the original model objects whose .raw attribute holds the verbatim A2L text.

  • if_data.flatmapDict[str, List[Any]]: lazily built flat index over all keys found in the parsed tree. Useful for quick look-ups when you know the tag but not the nesting depth.

Where you can find IF_DATA (inspect API):

  • Module.if_data

  • Measurement.if_data

  • Characteristic.if_data

  • AxisPts.if_data

  • Function.if_data

  • Group.if_data

  • Frame.if_data

  • Instance.if_data

  • Blob.if_data

  • ModPar.memoryLayouts[i].if_data

  • ModPar.memorySegments[i].if_data

Accessing parsed IF_DATA#

from pya2l.api.inspect import Module

module = Module(session)

# MODULE-level IF_DATA
print(module.if_data.if_data_parsed)  # list of parsed dicts

# MEASUREMENT IF_DATA
for meas in module.measurement.query():
    if meas.if_data.if_data_parsed:
        print(meas.name, meas.if_data.if_data_parsed)

# Quick tag look-up via flatmap
for meas in module.measurement.query():
    if "DAQ_LIST" in meas.if_data.flatmap:
        print(f"{meas.name} has DAQ config: {meas.if_data.flatmap['DAQ_LIST']}")

# CHARACTERISTIC IF_DATA
for char in module.characteristic.query():
    if char.if_data.if_data_parsed:
        print(char.name, char.if_data.if_data_parsed)

# AXIS_PTS IF_DATA
for ax in module.axis_pts.query():
    if ax.if_data.if_data_parsed:
        print(ax.name, ax.if_data.if_data_parsed)

# FUNCTION / GROUP / FRAME IF_DATA
for fn in module.function.query():
    if fn.if_data.if_data_parsed:
        print(fn.name, fn.if_data.if_data_parsed)
for grp in module.group.query():
    if grp.if_data.if_data_parsed:
        print(grp.name, grp.if_data.if_data_parsed)
for fr in module.frame.query():
    if fr.if_data.if_data_parsed:
        print(fr.name, fr.if_data.if_data_parsed)

# MOD_PAR memory layouts/segments IF_DATA
mp = module.mod_par
if mp:
    for i, ml in enumerate(mp.memoryLayouts):
        if ml.if_data.if_data_parsed:
            print(f"MEMORY_LAYOUT[{i}]", ml.if_data.if_data_parsed)
    for i, ms in enumerate(mp.memorySegments):
        if ms.if_data.if_data_parsed:
            print(f"MEMORY_SEGMENT[{i}] {ms.name}", ms.if_data.if_data_parsed)

Accessing raw IF_DATA text#

When the AML schema is unavailable or you need the verbatim text:

for meas in module.measurement.query():
    for raw_obj in meas.if_data.if_data_raw:
        print(f"--- {meas.name} raw IF_DATA ---")
        print(raw_obj.raw)

Parsing raw IF_DATA text manually#

from pya2l.aml.ifdata_parser import IfDataParser

ifdata_parser = IfDataParser(session)

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)

Note

The inspect API always returns an IfData instance (never None). If no IF_DATA blocks exist, if_data_parsed and if_data_raw are empty lists. The flatmap property safely returns an empty dict.

For a comprehensive guide including XCP, CCP, and KWP2000 examples, see Working with IF_DATA.

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#

  1. Close Sessions: Always close your database sessions when you’re done:

session.close()
  1. 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}")
  1. 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()
  1. 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"
))
  1. 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.