API Reference#

This reference covers the three public API modules:

  • Inspect (pya2l.api.inspect) — read-only access to A2L entities

  • Create (pya2l.api.create) — build or augment A2L databases

  • Validate (pya2l.api.validate) — run diagnostic checks

All APIs operate on a SessionProxy obtained via pya2l.import_a2l(), pya2l.open_existing(), or pya2l.open_create().

Session Management#

Every workflow starts by obtaining a session:

from pya2l import DB

db = DB()

# Parse A2L → SQLite  (creates .a2ldb)
session = db.import_a2l("ASAP2_Demo_V161.a2l")

# Open existing database  (fast, no reparsing)
session = db.open_existing("ASAP2_Demo_V161")

# Open-or-create  (convenience method)
session = db.open_create("ASAP2_Demo_V161.a2l")

The returned session is a SessionProxy that wraps a SQLAlchemy Session and adds IF_DATA parsing capabilities. You can use it for raw SQLAlchemy queries and for the high-level inspect/create/validate APIs.

Function-level API (no DB class needed):

from pya2l import import_a2l, open_existing, export_a2l

session = import_a2l("file.a2l", encoding="utf-8", progress_bar=False)
session = open_existing("file")
export_a2l("file", "output.a2l")

Inspect API#

The inspect API lives in pya2l.api.inspect and provides read-only, high-level wrappers around the ORM model.

Project and Module#

Project is the top-level entry point. It contains one or more Module objects, each holding all A2L entities.

from pya2l.api.inspect import Project, Module

project = Project(session)
print(project.name)            # e.g. "DemoProject"
print(project.header.version)  # e.g. "1.0"

# Iterate modules
for mod in project.module:
    print(mod.name, mod.longIdentifier)

# Direct access if you know the module name
module = Module(session, "MyModule")

Module attributes (all are FilteredList unless noted):

Attribute

Type / Description

axis_pts

FilteredList[AxisPts]

blob

FilteredList[Blob]

characteristic

FilteredList[Characteristic]

compu_method

FilteredList[CompuMethod]

compu_tab

FilteredList[CompuTab]

compu_tab_verb

FilteredList[CompuTabVerb]

compu_tab_verb_ranges

FilteredList[CompuTabVerbRanges]

frame

FilteredList[Frame]

function

FilteredList[Function]

group

FilteredList[Group]

measurement

FilteredList[Measurement]

record_layout

FilteredList[RecordLayout]

transformer

FilteredList[Transformer]

unit

FilteredList[Unit]

mod_common

ModCommon — byte order, alignment, data size

mod_par

ModPar — ECU metadata, memory segments, system constants

variant_coding

VariantCoding or None

if_data

IfData — module-level IF_DATA

FilteredList and query()#

All collections on Module are FilteredList objects. Use .query() to iterate high-level inspect wrappers:

# All measurements (generator → list)
all_meas = list(module.measurement.query())

# With predicate (applied to ORM row, not inspect object)
float_meas = list(module.measurement.query(
    lambda row: row.datatype in ("FLOAT32_IEEE", "FLOAT64_IEEE")
))

# Prefix match
engine = list(module.characteristic.query(
    lambda row: row.name.startswith("ENGINE_")
))

# Count without materialising
n = sum(1 for _ in module.measurement.query())

# Sort after materialising
by_name = sorted(module.measurement.query(), key=lambda m: m.name)

Important

The predicate receives the ORM row (pya2l.model.*), not the inspect wrapper. Use the schema field names (e.g. row.name, row.datatype, row.groupName).

CachedBase and .get()#

All entity classes inherit from CachedBase which maintains a per-session LRU cache. Always use .get() instead of the constructor:

from pya2l.api.inspect import Measurement, Characteristic, AxisPts

meas = Measurement.get(session, "ENGINE_SPEED")
char = Characteristic.get(session, "InjectionMap")
axis = AxisPts.get(session, "RPM_Axis")

# Cache is transparent — second call returns same object
assert Measurement.get(session, "ENGINE_SPEED") is meas

Measurement#

Read-only access to MEASUREMENT entities.

from pya2l.api.inspect import Measurement

m = Measurement.get(session, "ENGINE_SPEED")

# Core attributes
print(m.name)               # "ENGINE_SPEED"
print(m.longIdentifier)     # "Engine rotational speed"
print(m.datatype)           # "UWORD"
print(m.resolution)         # 1
print(m.accuracy)           # 0.5
print(m.lowerLimit)         # 0.0
print(m.upperLimit)         # 8000.0

# Address
print(f"0x{m.ecuAddress:08X}")     # e.g. 0x00100000
print(m.ecuAddressExtension)       # 0

# Conversion (CompuMethod)
print(m.compuMethod.name)          # "CM_Speed"
print(m.compuMethod.conversionType)  # "LINEAR"
raw_value = 1200
phys = m.compuMethod.int_to_physical(raw_value)
print(f"{raw_value}{phys} {m.physUnit}")  # "1200 → 300.0 rpm"

# Optional attributes
print(m.byteOrder)          # "LITTLE_ENDIAN" or None
print(m.bitMask)            # e.g. 0xFFFF or None
print(m.format)             # e.g. "%8.2" or None
print(m.displayIdentifier)  # e.g. "EngSpd" or None
print(m.symbolLink)         # SymbolLink namedtuple or None
print(m.arraySize)          # int or None
print(m.matrixDim)          # MatrixDim dataclass
print(m.discrete)           # bool
print(m.readWrite)          # bool
print(m.annotations)        # list of Annotation
print(m.functionList)       # list of function names
print(m.virtual)            # list of measuring channels
print(m.if_data)            # IfData instance

# NumPy shape (for arrays/matrices)
print(m.fnc_np_shape)       # e.g. (8, 4)

Characteristic#

Read-only access to CHARACTERISTIC entities (VALUE, CURVE, MAP, CUBOID, …).

from pya2l.api.inspect import Characteristic

c = Characteristic.get(session, "InjectionMap")

# Core
print(c.name)            # "InjectionMap"
print(c.type)            # "MAP"
print(f"0x{c.address:08X}")
print(c.maxDiff)
print(c.lowerLimit, c.upperLimit)

# Conversion
print(c.compuMethod.name)
print(c.compuMethod.int_to_physical(42))

# Record layout
rl = c.deposit           # RecordLayout instance
print(rl.name)
print(rl.fncValues)      # FncValues(position=…, data_type=…, …)
print(rl.axes)           # dict of axis components

# Axis descriptions
for i, ax in enumerate(c.axisDescriptions):
    print(f"Axis {i}: {ax.attribute}, max={ax.maxAxisPoints}")
    print(f"  CompuMethod: {ax.compuMethod.name}")
    print(f"  Limits: [{ax.lowerLimit}, {ax.upperLimit}]")

# Number of axes
print(c.num_axes)        # 2 for MAP

# Shape
print(c.fnc_np_shape)    # e.g. (16, 16)
print(c.dim)             # total number of function values

# Memory layout
print(c.record_layout_components)
print(f"Total memory: {c.total_allocated_memory} bytes")

# Optional attributes
print(c.calibrationAccess)  # e.g. "CALIBRATION"
print(c.encoding)           # e.g. "UTF8" or None
print(c.matrixDim)          # MatrixDim
print(c.extendedLimits)     # ExtendedLimits or None
print(c.if_data)            # IfData instance

# Dependent / virtual characteristics
print(c.dependent_characteristic)
print(c.virtual_characteristic)

AxisPts#

Shared axis definitions used by multiple characteristics.

from pya2l.api.inspect import AxisPts

ax = AxisPts.get(session, "RPM_Axis")

print(ax.name)              # "RPM_Axis"
print(ax.longIdentifier)
print(f"0x{ax.address:08X}")
print(ax.maxAxisPoints)     # e.g. 16
print(ax.lowerLimit, ax.upperLimit)

# Conversion
print(ax.compuMethod.name)

# Record layout
print(ax.depositAttr.name)  # RecordLayout name
print(ax.record_layout_components)

# Memory
print(f"Allocated: {ax.total_allocated_memory} bytes")

# IF_DATA
print(ax.if_data)           # IfData instance

CompuMethod#

Conversion methods translate internal (raw) ECU values to physical values and back.

from pya2l.api.inspect import CompuMethod

cm = CompuMethod.get(session, "CM_Speed")

print(cm.name)              # "CM_Speed"
print(cm.conversionType)    # "LINEAR", "RAT_FUNC", "TAB_VERB", …
print(cm.format)            # "%8.2"
print(cm.unit)              # "rpm"

# Forward / inverse conversion
phys = cm.int_to_physical(1200)
raw  = cm.physical_to_int(300.0)

# Type-specific attributes
if cm.conversionType == "LINEAR":
    print(cm.coeffs_linear)  # CoeffsLinear(a=…, b=…)
elif cm.conversionType == "RAT_FUNC":
    print(cm.coeffs)         # Coeffs(a, b, c, d, e, f)
elif cm.conversionType == "FORM":
    print(cm.formula)        # {'formula': '…', 'formula_inv': '…'}
elif cm.conversionType in ("TAB_INTP", "TAB_NOINTP"):
    print(cm.tab)            # CompuTab
elif cm.conversionType == "TAB_VERB":
    print(cm.tab_verb)       # CompuTabVerb

Supported types:

Conversion Type

Description

IDENTICAL

Pass-through (phys = raw)

LINEAR

phys = a·x + b

RAT_FUNC

phys = (a·x² + b·x + c) / (d·x² + e·x + f)

FORM

Arbitrary formula string (evaluated via numexpr)

TAB_INTP

Table with interpolation

TAB_NOINTP

Table without interpolation (nearest match)

TAB_VERB

Verbal table (numeric → text)

NO_COMPU_METHOD

Explicitly no conversion

RecordLayout#

Describes the physical memory structure of characteristics and axis points.

from pya2l.api.inspect import RecordLayout

rl = RecordLayout.get(session, "RL_MAP_16x16")

print(rl.name)
print(rl.alignment)              # Alignment dataclass

# Function values
fv = rl.fncValues
if fv.valid():
    print(f"FNC_VALUES: pos={fv.position}, type={fv.data_type}")
    print(f"  NumPy dtype : {rl.fnc_np_dtype}")
    print(f"  Element size: {rl.fnc_element_size} bytes")

# Axes (dict: axis_name → dict of components)
for axis_name, components in rl.axes.items():
    print(f"Axis '{axis_name}':")
    for comp_name, comp in components.items():
        print(f"  {comp_name}: {comp}")

# Identification
if rl.identification.valid():
    print(f"ID: pos={rl.identification.position}, type={rl.identification.data_type}")

Function and Group#

Function represents logical groupings of calibration parameters. Group defines UI folder structures.

from pya2l.api.inspect import Function, Group

# --- Functions ---
fn = Function.get(session, "InjectionControl")
print(fn.name)
print(fn.longIdentifier)
print(fn.annotations)
print(fn.functionVersion)

# Associated elements
print(fn.defCharacteristics)    # list of Characteristic
print(fn.inMeasurements)        # list of Measurement
print(fn.outMeasurements)       # list of Measurement
print(fn.locMeasurements)       # list of Measurement
print(fn.subFunctions)          # list of Function

# Get all top-level functions
roots = Function.get_root_functions(session, ordered=True)

# --- Groups ---
grp = Group.get(session, "EngineParams")
print(grp.name)
print(grp.root)                 # True if top-level group
print(grp.characteristics)      # list of Characteristic/AxisPts
print(grp.measurements)         # list of Measurement
print(grp.functions)            # list of Function
print(grp.subgroups)            # list of Group

# Get all root groups
roots = Group.get_root_groups(session, ordered=True)

ModCommon and ModPar#

Module-wide settings and ECU metadata.

from pya2l.api.inspect import Module

module = Module(session)

# --- ModCommon ---
mc = module.mod_common
print(mc.comment)
print(mc.byteOrder)        # "LITTLE_ENDIAN" or "BIG_ENDIAN"
print(mc.dataSize)         # default word width
print(mc.alignment)        # Alignment dataclass
print(mc.alignment.byte)   # e.g. 1
print(mc.alignment.word)   # e.g. 2
print(mc.alignment.dword)  # e.g. 4

# --- ModPar ---
mp = module.mod_par
if mp:
    print(mp.comment)
    print(mp.cpu)           # e.g. "TC1766"
    print(mp.customer)
    print(mp.ecu)
    print(mp.epk)           # list of EPK strings
    print(mp.version)

    # System constants
    for name, value in mp.systemConstants.items():
        print(f"  {name} = {value}")

    # Memory segments
    for seg in mp.memorySegments:
        print(f"  {seg.name}: {seg.memoryType} @ 0x{seg.address:08X}, "
              f"size={seg.size}")

Frame#

from pya2l.api.inspect import Frame

fr = Frame.get(session, "FRAME1")
print(fr.name)
print(fr.longIdentifier)
print(fr.scalingUnit)
print(fr.rate)
print(fr.frame_measurement)   # list of measurement names
print(fr.if_data)             # IfData instance

VariantCoding#

Access variant definitions for ECUs with multiple calibration data-sets:

module = Module(session)
vc = module.variant_coding

if vc and vc.variant_coded:
    print(f"Separator: {vc.separator}")
    print(f"Naming   : {vc.naming}")

    # Available criterion values
    for crit_name in vc.criterions:
        values = vc.get_citerion_values(crit_name)
        print(f"  {crit_name}: {values}")

    # All valid combinations
    combos = vc.valid_combinations(list(vc.criterions.keys()))
    for combo in combos:
        print(combo)

    # Variants of a specific characteristic
    variants = vc.variants("MyCalibrationParam")
    for v in variants:
        print(v)

IfData#

See Working with IF_DATA for a full guide. Quick reference:

from pya2l.api.inspect import Measurement

m = Measurement.get(session, "ENGINE_SPEED")

ifd = m.if_data                   # IfData instance
ifd.if_data_parsed                # List[Any] – structured dicts
ifd.if_data_raw                   # list – original model objects
ifd.flatmap                       # Dict[str, List[Any]] – lazy flat index

Create API#

The create API lives in pya2l.api.create and provides builder classes for constructing A2L databases programmatically.

All creators follow the same pattern:

  1. Instantiate with a session

  2. Call create_* / add_* methods

  3. Call commit()

ProjectCreator#

from pya2l.api.create import ProjectCreator

pc = ProjectCreator(session)
project = pc.create_project("DemoProject", "Example ECU calibration")
header = pc.add_header(project, "File comment / description")
pc.add_project_no(header, "PRJ-001")
pc.commit()

ModuleCreator#

Creates modules and most structural elements (units, groups, functions, frames, transformers, typedefs, instances, …):

from pya2l.api.create import ModuleCreator

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

# Common settings
mc.add_mod_common(module, "Module comment",
    byte_order="LITTLE_ENDIAN", data_size=32)

# Units
mc.add_unit(module, "rpm", "Revolutions per minute",
            display="rpm", type_str="DERIVED")
mc.add_unit(module, "degC", "Degrees Celsius",
            display="°C", type_str="DERIVED")

# Groups
grp = mc.add_group(module, "EngineParams", "Engine parameters")
mc.add_group_ref_measurement(grp, ["EngineSpeed", "CoolantTemp"])
mc.add_group_ref_characteristic(grp, ["IdleSpeed"])

# Frames
mc.add_frame(module, "Frame1", "CAN frame",
             scaling_unit=1, rate=10,
             measurements=["EngineSpeed"])

# Typedef structures
ts = mc.add_typedef_structure(module, "TSig", "Signal struct", size=4)
mc.add_structure_component(ts, "raw", "UWORD", offset=0)
mc.add_structure_component(ts, "status", "UBYTE", offset=2)

# Instances
mc.add_instance(module, "Signal1", "Instance of TSig",
                type_name="TSig", address=0x1000)

mc.commit()

CompuMethodCreator#

from pya2l.api.create import CompuMethodCreator

cmc = CompuMethodCreator(session)

# LINEAR: phys = factor * raw + offset
cm = cmc.create_compu_method(
    "CM_Temp", "Temperature", "LINEAR",
    "%6.1", "°C", module_name="Engine"
)
cmc.add_coeffs_linear(cm, offset=-40.0, factor=0.1)

# RAT_FUNC: rational polynomial
cm2 = cmc.create_compu_method(
    "CM_Pressure", "Pressure", "RAT_FUNC",
    "%8.3", "bar", module_name="Engine"
)
cmc.add_coeffs(cm2, a=0, b=0.01, c=-1.0, d=0, e=0, f=1)

# TAB_VERB: enumeration
cm3 = cmc.create_compu_method(
    "CM_GearPos", "Gear", "TAB_VERB",
    "%d", "", module_name="Engine"
)

# Numeric table
ct = mc.add_compu_tab(
    module, "CT_Correction", "Correction factors",
    conversion_type="TAB_NOINTP",
    pairs=[(0, 1.0), (50, 1.05), (100, 1.12)],
    default_numeric=1.0,
)

# Verbal range table
vr = mc.add_compu_vtab_range(
    module, "VR_State", "Operating states",
    triples=[(0.0, 0.9, "OFF"), (1.0, 1.9, "STANDBY"), (2.0, 10.0, "RUN")],
    default_value="UNKNOWN",
)

cmc.commit()

MeasurementCreator#

from pya2l.api.create import MeasurementCreator

mec = MeasurementCreator(session)

# Basic scalar measurement
m = mec.create_measurement(
    "EngineSpeed", "Engine RPM",
    "UWORD", "CM_Speed",
    resolution=1, accuracy=1.0,
    lower_limit=0.0, upper_limit=8000.0,
    module_name="Engine"
)
mec.add_ecu_address(m, 0x100000)
mec.add_format(m, "%8.2")
mec.add_byte_order(m, "LITTLE_ENDIAN")
mec.add_display_identifier(m, "EngSpd")

# Matrix measurement
m2 = mec.create_measurement(
    "TempGrid", "Temperature sensor array",
    "SWORD", "CM_Temp",
    resolution=1, accuracy=0.1,
    lower_limit=-40.0, upper_limit=150.0,
    module_name="Engine"
)
mec.add_matrix_dim(m2, dims=[4, 4])
mec.add_ecu_address(m2, 0x200000)

# Symbol-linked measurement (no direct address)
m3 = mec.create_measurement(
    "VirtualSignal", "Computed signal",
    "FLOAT32_IEEE", "CM_Voltage",
    resolution=1, accuracy=0.01,
    lower_limit=0.0, upper_limit=5.0,
    module_name="Engine"
)
mec.add_symbol_link(m3, "g_voltage_sensor", offset=0)

mec.commit()

CharacteristicCreator#

from pya2l.api.create import CharacteristicCreator

cc = CharacteristicCreator(session)

# VALUE (scalar)
c1 = cc.create_characteristic(
    "IdleSpeed", "Target idle RPM",
    "VALUE", 0x300000, "RL_UWORD", 0.0, "CM_Speed",
    500.0, 1500.0, module_name="Engine"
)

# CURVE (1-D)
c2 = cc.create_characteristic(
    "ThrottleCurve", "Throttle map",
    "CURVE", 0x310000, "RL_CURVE", 0.0, "CM_Percent",
    0.0, 100.0, module_name="Engine"
)
cc.add_axis_descr(c2, "STD_AXIS", "NO_INPUT_QUANTITY",
                  "CM_Percent", 8, 0.0, 100.0)

# MAP (2-D)
c3 = cc.create_characteristic(
    "InjectionMap", "Fuel injection",
    "MAP", 0x320000, "RL_MAP", 0.0, "CM_Time",
    0.0, 50.0, module_name="Engine"
)
cc.add_axis_descr(c3, "STD_AXIS", "EngineSpeed",
                  "CM_Speed", 16, 0.0, 8000.0)
cc.add_axis_descr(c3, "STD_AXIS", "EngineLoad",
                  "CM_Percent", 16, 0.0, 100.0)

cc.commit()

RecordLayoutCreator#

from pya2l.api.create import RecordLayoutCreator

rlc = RecordLayoutCreator(session)

# Scalar layout
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")

# Curve layout (axis + values)
rl2 = rlc.create_record_layout("RL_CURVE", module_name="Engine")
rlc.add_no_axis_pts_x(rl2, position=1, datatype="UBYTE")
rlc.add_axis_pts_x(rl2, position=2, datatype="UWORD",
                   index_incr="INDEX_INCR", address_type="DIRECT")
rlc.add_fnc_values(rl2, position=3, datatype="UWORD",
                   index_mode="ALTERNATE", address_type="DIRECT")

# Map layout (2 axes + values)
rl3 = rlc.create_record_layout("RL_MAP", module_name="Engine")
rlc.add_no_axis_pts_x(rl3, position=1, datatype="UBYTE")
rlc.add_no_axis_pts_y(rl3, position=2, datatype="UBYTE")
rlc.add_axis_pts_x(rl3, position=3, datatype="UWORD",
                   index_incr="INDEX_INCR", address_type="DIRECT")
rlc.add_axis_pts_y(rl3, position=4, datatype="UWORD",
                   index_incr="INDEX_INCR", address_type="DIRECT")
rlc.add_fnc_values(rl3, position=5, datatype="UWORD",
                   index_mode="ALTERNATE", address_type="DIRECT")

# Alignment settings
rlc.add_alignment_byte(rl3, 1)
rlc.add_alignment_word(rl3, 2)
rlc.add_alignment_long(rl3, 4)

rlc.commit()

FunctionCreator#

from pya2l.api.create import FunctionCreator

fc = FunctionCreator(session)

fn = fc.create_function(
    "InjectionCtrl", "Injection control",
    module_name="Engine"
)
fc.add_function_version(fn, "1.2.0")
fc.add_def_characteristic(fn, ["InjectionMap", "IdleSpeed"])
fc.add_in_measurement(fn, ["EngineSpeed", "EngineLoad"])
fc.add_out_measurement(fn, ["InjectorDuty"])
fc.add_sub_function(fn, ["ColdStartEnrich"])

fc.commit()

GroupCreator#

from pya2l.api.create import GroupCreator

gc = GroupCreator(session)

grp = gc.create_group("EngineBasic", "Basic engine parameters",
                      module_name="Engine")
gc.add_root(grp)
gc.add_ref_characteristic(grp, ["IdleSpeed", "ThrottleCurve"])
gc.add_ref_measurement(grp, ["EngineSpeed"])
gc.add_sub_group(grp, ["AdvancedParams"])

gc.commit()

Complete example: building an ECU database from scratch#

from pya2l import DB, export_a2l
from pya2l.api.create import (
    ProjectCreator, ModuleCreator, CompuMethodCreator,
    MeasurementCreator, CharacteristicCreator,
    RecordLayoutCreator, FunctionCreator, GroupCreator,
)
from pya2l.api.inspect import Project

# --- Setup ---
db = DB()
session = db.open_create("TurboECU.a2ldb")

pc = ProjectCreator(session)
project = pc.create_project("TurboECU", "Turbocharged engine ECU")
hdr = pc.add_header(project, "Created by pyA2L automation")
pc.add_project_no(hdr, "TE-2026-001")

mc = ModuleCreator(session)
module = mc.create_module("TCU", "Turbo control unit", project=project)
mc.add_mod_common(module, "Common settings",
                  byte_order="LITTLE_ENDIAN", data_size=32)

# --- Units ---
mc.add_unit(module, "rpm",  "Revolutions per minute", "rpm", "DERIVED")
mc.add_unit(module, "bar",  "Pressure",               "bar", "DERIVED")
mc.add_unit(module, "degC", "Temperature",             "°C",  "DERIVED")

# --- Conversions ---
cmc = CompuMethodCreator(session)
cm_rpm = cmc.create_compu_method(
    "CM_RPM", "RPM", "LINEAR", "%8.0", "rpm", module_name="TCU")
cmc.add_coeffs_linear(cm_rpm, offset=0.0, factor=1.0)

cm_bar = cmc.create_compu_method(
    "CM_Boost", "Boost pressure", "LINEAR", "%5.2", "bar", module_name="TCU")
cmc.add_coeffs_linear(cm_bar, offset=-1.0, factor=0.01)

cm_temp = cmc.create_compu_method(
    "CM_Temp", "Temperature", "LINEAR", "%6.1", "°C", module_name="TCU")
cmc.add_coeffs_linear(cm_temp, offset=-40.0, factor=0.1)

# --- Record Layouts ---
rlc = RecordLayoutCreator(session)
rl_u16 = rlc.create_record_layout("RL_U16", module_name="TCU")
rlc.add_fnc_values(rl_u16, 1, "UWORD", "ALTERNATE", "DIRECT")

rl_curve = rlc.create_record_layout("RL_CURVE_12", module_name="TCU")
rlc.add_no_axis_pts_x(rl_curve, 1, "UBYTE")
rlc.add_axis_pts_x(rl_curve, 2, "UWORD", "INDEX_INCR", "DIRECT")
rlc.add_fnc_values(rl_curve, 3, "UWORD", "ALTERNATE", "DIRECT")

# --- Measurements ---
mec = MeasurementCreator(session)

m_rpm = mec.create_measurement(
    "EngineSpeed", "Current engine speed",
    "UWORD", "CM_RPM", 1, 1.0, 0.0, 8000.0, module_name="TCU")
mec.add_ecu_address(m_rpm, 0x100000)

m_boost = mec.create_measurement(
    "BoostPressure", "Turbo boost pressure",
    "UWORD", "CM_Boost", 1, 0.1, -1.0, 3.5, module_name="TCU")
mec.add_ecu_address(m_boost, 0x100002)

m_temp = mec.create_measurement(
    "ChargeAirTemp", "Charge air temperature",
    "UWORD", "CM_Temp", 1, 0.5, -40.0, 150.0, module_name="TCU")
mec.add_ecu_address(m_temp, 0x100004)

# --- Characteristics ---
cc = CharacteristicCreator(session)

c_target = cc.create_characteristic(
    "TargetBoost", "Target boost pressure",
    "VALUE", 0x200000, "RL_U16", 0.0, "CM_Boost",
    0.0, 3.0, module_name="TCU")

c_curve = cc.create_characteristic(
    "BoostCurve", "Boost vs RPM",
    "CURVE", 0x201000, "RL_CURVE_12", 0.0, "CM_Boost",
    0.0, 3.0, module_name="TCU")
cc.add_axis_descr(c_curve, "STD_AXIS", "EngineSpeed",
                  "CM_RPM", 12, 0.0, 8000.0)

# --- Organisation ---
fc = FunctionCreator(session)
fn = fc.create_function("BoostControl", "Boost control logic",
                        module_name="TCU")
fc.add_def_characteristic(fn, ["TargetBoost", "BoostCurve"])
fc.add_in_measurement(fn, ["EngineSpeed", "BoostPressure", "ChargeAirTemp"])

gc = GroupCreator(session)
grp = gc.create_group("TurboParams", "Turbo parameters",
                      module_name="TCU")
gc.add_root(grp)
gc.add_ref_characteristic(grp, ["TargetBoost", "BoostCurve"])
gc.add_ref_measurement(grp, ["EngineSpeed", "BoostPressure", "ChargeAirTemp"])

# --- Commit and export ---
gc.commit()
db.close()

export_a2l("TurboECU", "TurboECU.a2l")
print("Done — exported TurboECU.a2l")

# --- Verify with inspect ---
session2 = DB().open_existing("TurboECU")
prj = Project(session2)
mod = prj.module[0]
print(f"Module: {mod.name}")
print(f"  Measurements:    {sum(1 for _ in mod.measurement.query())}")
print(f"  Characteristics: {sum(1 for _ in mod.characteristic.query())}")
print(f"  Functions:       {sum(1 for _ in mod.function.query())}")
print(f"  Groups:          {sum(1 for _ in mod.group.query())}")

Validate API#

The validation API checks A2L databases for structural issues and common mistakes.

from pya2l import DB
from pya2l.api.validate import Validator, Level, Category

session = DB().open_existing("ASAP2_Demo_V161")

validator = Validator(session)
diagnostics = validator()      # returns tuple of Message namedtuples

for msg in diagnostics:
    print(f"[{msg.type.name}] {msg.category.name}: {msg.text}")

Message fields#

Each Message is a named tuple with:

Field

Type

Description

type

Level

INFORMATION, WARNING, or ERROR

category

Category

DUPLICATE, MISSING, OBSOLETE

diag_code

Diagnostics

Specific diagnostic code

text

str

Human-readable description

Diagnostic codes#

Code

Meaning

MULTIPLE_DEFINITIONS_IN_NAMESPACE

Same name used twice in one module

DEFINITION_IN_MULTIPLE_NAMESPACES

Entity exists in multiple modules

INVALID_C_IDENTIFIER

Name is not a valid C identifier

MISSING_BYTE_ORDER

No byte order specified

MISSING_ALIGNMENT

No alignment settings in MOD_COMMON

MISSING_EPK

No EPK (ECU Program Key) defined

MISSING_ADDR_EPK

No ADDR_EPK specified

MISSING_MODULE

Project has no modules

DEPRECATED

Use of deprecated A2L features

OVERLAPPING_MEMORY

Memory ranges overlap

Practical validation workflow:

from pya2l import DB
from pya2l.api.validate import Validator, Level

session = DB().open_existing("my_project")
diags = Validator(session)()

errors   = [d for d in diags if d.type == Level.ERROR]
warnings = [d for d in diags if d.type == Level.WARNING]
info     = [d for d in diags if d.type == Level.INFORMATION]

print(f"Errors: {len(errors)}, Warnings: {len(warnings)}, Info: {len(info)}")

if errors:
    print("\n--- ERRORS ---")
    for e in errors:
        print(f"  [{e.diag_code.name}] {e.text}")

Low-Level ORM Access#

For advanced queries that the inspect API doesn’t cover, use SQLAlchemy directly with pya2l.model:

import pya2l.model as model
from pya2l import DB

session = DB().open_existing("ASAP2_Demo_V161")

# Raw SQL query
measurements = (
    session.query(model.Measurement)
    .filter(model.Measurement.datatype == "FLOAT32_IEEE")
    .order_by(model.Measurement.name)
    .all()
)
for m in measurements:
    print(f"{m.name}: {m.datatype} @ 0x{m.ecu_address.address:08X}")

# Projection (select specific columns)
names_and_types = (
    session.query(model.Measurement.name, model.Measurement.datatype)
    .filter(model.Measurement.name.like("ENGINE_%"))
    .all()
)

# Count
n = session.query(model.Measurement).count()

# Join across relationships
chars_with_axes = (
    session.query(model.Characteristic)
    .filter(model.Characteristic.type == "MAP")
    .all()
)

# Bridge from ORM to inspect
from pya2l.api.inspect import Measurement as MeasInspect
for row in measurements:
    meas_obj = MeasInspect.get(session, row.name)
    print(f"{meas_obj.name}: {meas_obj.compuMethod.conversionType}")