API Reference#
This reference covers the three public API modules:
Inspect (
pya2l.api.inspect) — read-only access to A2L entitiesCreate (
pya2l.api.create) — build or augment A2L databasesValidate (
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 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
|---|---|
|
Pass-through (phys = raw) |
|
|
|
|
|
Arbitrary formula string (evaluated via |
|
Table with interpolation |
|
Table without interpolation (nearest match) |
|
Verbal table (numeric → text) |
|
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:
Instantiate with a session
Call
create_*/add_*methodsCall
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 |
|---|---|---|
|
|
|
|
|
|
|
|
Specific diagnostic code |
|
|
Human-readable description |
Diagnostic codes#
Code |
Meaning |
|---|---|
|
Same name used twice in one module |
|
Entity exists in multiple modules |
|
Name is not a valid C identifier |
|
No byte order specified |
|
No alignment settings in MOD_COMMON |
|
No EPK (ECU Program Key) defined |
|
No ADDR_EPK specified |
|
Project has no modules |
|
Use of deprecated A2L features |
|
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}")