Working with IF_DATA#

IF_DATA sections in A2L files carry vendor-specific transport-layer and protocol information (XCP, CCP, KWP2000, …). pyA2L parses these blocks automatically when an AML description is available and wraps the result in the IfData dataclass, which gives you simultaneous access to the parsed (structured) and raw (text) representation.

The IfData class#

Every inspect object that can contain IF_DATA blocks (Module, Measurement, Characteristic, AxisPts, Function, Group, Frame, Instance, Blob, and the MemoryLayout / MemorySegment entries inside ModPar) exposes an if_data attribute of type IfData.

from dataclasses import dataclass, field
from typing import Any, Dict, List, Union

@dataclass
class IfData:
    """Parsed + raw IF_DATA container.

    Parameters
    ----------
    if_data_parsed : List[Any]
        Structured representation produced by the AML-based parser.
        Each list element corresponds to one ``/begin IF_DATA … /end IF_DATA``
        block in the source file.
    if_data_raw : list
        The original model objects (``pya2l.model.IfData``) that hold the
        raw text.  Useful when you need the verbatim A2L source, e.g. for
        re-export or manual inspection.

    Attributes
    ----------
    flatmap : Dict[str, List[Any]]
        A lazily built dictionary that flattens the nested parsed structure
        into a key → [values…] mapping.  Handy for quick look-ups when
        you know the tag name but not the nesting depth.
    """
    if_data_parsed: List[Any]
    if_data_raw: list
    items: Dict[str, List[Any]] = field(default_factory=dict)

    @property
    def flatmap(self) -> Dict[str, List[Any]]: ...

Quick start#

from pya2l import DB
from pya2l.api.inspect import Measurement

db = DB()
session = db.open_create("ASAP2_Demo_V161.a2l")

meas = Measurement.get(session, "ASAM.M.SCALAR.UBYTE.IDENTICAL")

# IfData instance — always present, may be empty
ifd = meas.if_data

# Parsed data (list of dicts/nested structures)
print(ifd.if_data_parsed)          # e.g. [{'XCP': { ... }}]

# Raw model objects (for re-export or text inspection)
print(len(ifd.if_data_raw))        # number of IF_DATA blocks

# Quick key-based look-up via flatmap
if "DAQ_LIST" in ifd.flatmap:
    print(ifd.flatmap["DAQ_LIST"])

Parsed data vs. raw data#

Why both? The parsed representation is the fast path for programmatic access — you get Python dicts you can index into. The raw representation is essential when you need the original text for diagnostics, logging, or when the AML schema is unavailable (in which case if_data_parsed is empty but the raw text is still there).

Using the flatmap#

Deeply nested IF_DATA trees can be tedious to traverse. flatmap flattens every key it encounters into a dictionary of lists:

from pya2l import DB
from pya2l.api.inspect import Module

session = DB().open_create("xcp_demo_autodetect.a2l")
module = Module(session)

ifd = module.if_data    # IfData instance

# Iterate all keys the parser found
for key, values in ifd.flatmap.items():
    print(f"{key}: {len(values)} occurrence(s)")

# Direct look-up of a known tag
if "PROTOCOL_LAYER" in ifd.flatmap:
    proto = ifd.flatmap["PROTOCOL_LAYER"]
    print("Protocol layer info:", proto)

if "DAQ" in ifd.flatmap:
    daq = ifd.flatmap["DAQ"]
    print("DAQ configuration:", daq)

Note

flatmap is built lazily on first access. The cost is proportional to the depth of the parsed tree and is paid only once.

Accessing IF_DATA across all entity types#

Every entity that can carry IF_DATA in the ASAP2 standard exposes it:

from pya2l import DB
from pya2l.api.inspect import (
    Module, Measurement, Characteristic, AxisPts,
    Function, Group, Frame,
)

session = DB().open_create("my_ecu.a2l")
module = Module(session)

# --- MODULE level ---
print("Module IF_DATA:", module.if_data.if_data_parsed)

# --- MEASUREMENT ---
for meas in module.measurement.query():
    if meas.if_data.if_data_parsed:
        print(f"  {meas.name}: {meas.if_data.if_data_parsed}")

# --- CHARACTERISTIC ---
for char in module.characteristic.query():
    if char.if_data.if_data_parsed:
        print(f"  {char.name}: {char.if_data.if_data_parsed}")

# --- AXIS_PTS ---
for ax in module.axis_pts.query():
    if ax.if_data.if_data_parsed:
        print(f"  {ax.name}: {ax.if_data.if_data_parsed}")

# --- FUNCTION, GROUP, FRAME ---
for fn in module.function.query():
    if fn.if_data.if_data_parsed:
        print(f"  FUNCTION {fn.name}: {fn.if_data.if_data_parsed}")

for grp in module.group.query():
    if grp.if_data.if_data_parsed:
        print(f"  GROUP {grp.name}: {grp.if_data.if_data_parsed}")

for fr in module.frame.query():
    if fr.if_data.if_data_parsed:
        print(f"  FRAME {fr.name}: {fr.if_data.if_data_parsed}")

# --- MEMORY_LAYOUT / MEMORY_SEGMENT (inside ModPar) ---
mp = module.mod_par
if mp:
    for layout in mp.memoryLayouts:
        if layout.if_data.if_data_parsed:
            print(f"  MEMORY_LAYOUT: {layout.if_data.if_data_parsed}")
    for seg in mp.memorySegments:
        if seg.if_data.if_data_parsed:
            print(f"  MEMORY_SEGMENT {seg.name}: {seg.if_data.if_data_parsed}")

Practical examples#

Extracting XCP transport-layer parameters#

Many ECU projects use XCP on Ethernet or CAN. The transport configuration lives in the module-level IF_DATA:

from pya2l import DB
from pya2l.api.inspect import Module

session = DB().open_create("xcp_demo_autodetect.a2l")
module = Module(session)

ifd = module.if_data

# Walk the parsed tree for XCP specifics
for block in ifd.if_data_parsed:
    if not isinstance(block, dict):
        continue
    if "XCP" not in block:
        continue

    xcp = block["XCP"]

    # Protocol layer
    if "PROTOCOL_LAYER" in xcp:
        proto = xcp["PROTOCOL_LAYER"]
        print("XCP Protocol Layer:")
        print(f"  Version          : {proto.get('version', 'N/A')}")
        print(f"  T1 timeout [ms]  : {proto.get('T1', 'N/A')}")
        print(f"  Max CTO          : {proto.get('MAX_CTO', 'N/A')}")
        print(f"  Max DTO          : {proto.get('MAX_DTO', 'N/A')}")
        print(f"  Byte order       : {proto.get('BYTE_ORDER', 'N/A')}")

    # DAQ lists
    if "DAQ" in xcp:
        daq = xcp["DAQ"]
        print(f"DAQ: {daq}")

    # Transport layer (TCP/UDP)
    for tl_key in ("TRANSPORT_LAYER_CMD", "XCP_ON_TCP_IP", "XCP_ON_UDP_IP", "XCP_ON_CAN"):
        if tl_key in xcp:
            print(f"{tl_key}: {xcp[tl_key]}")

Inspecting CCP (CAN Calibration Protocol) blocks#

CCP information typically appears under measurement or characteristic IF_DATA:

from pya2l import DB
from pya2l.api.inspect import Module

session = DB().open_create("If_ccp4.a2l")
module = Module(session)

# Collect all CCP-related IF_DATA across measurements
for meas in module.measurement.query():
    for block in meas.if_data.if_data_parsed:
        if isinstance(block, dict) and "ASAP1B_CCP" in block:
            ccp = block["ASAP1B_CCP"]
            print(f"{meas.name}: CCP info = {ccp}")

# Module-level CCP parameters
for block in module.if_data.if_data_parsed:
    if isinstance(block, dict) and "ASAP1B_CCP" in block:
        print("Module CCP:", block["ASAP1B_CCP"])

Working with KWP2000 transport data#

KWP2000-based A2L files store baud rates, timing, and security parameters in IF_DATA:

from pya2l import DB
from pya2l.api.inspect import Module

session = DB().open_create("if_kwp6.a2l")
module = Module(session)

for block in module.if_data.if_data_parsed:
    if not isinstance(block, dict):
        continue
    if "ASAP1B_KWP2000" not in block:
        continue

    kwp = block["ASAP1B_KWP2000"]
    print("KWP2000 transport parameters:")

    # TP_BLOB contains baud rates, timing, SERAM, checksum config
    if "TP_BLOB" in kwp:
        tp = kwp["TP_BLOB"]
        print(f"  TP_BLOB: {tp}")

    # SOURCE blocks describe measurement channels
    if "SOURCE" in kwp:
        sources = kwp["SOURCE"]
        if not isinstance(sources, list):
            sources = [sources]
        for src in sources:
            print(f"  SOURCE: {src}")

Accessing raw IF_DATA text#

When the AML description is missing or you need the verbatim text:

from pya2l import DB
from pya2l.api.inspect import Measurement

session = DB().open_create("engine_ecu.a2l")
meas = Measurement.get(session, "EngineSpeed")

# Raw text of each IF_DATA block
for raw_obj in meas.if_data.if_data_raw:
    print("--- raw IF_DATA text ---")
    print(raw_obj.raw)
    print("------------------------")

Combining parsed + raw for diagnostics#

A typical diagnostic use-case: log both the parsed tree (for automated processing) and the original text (for human review):

import json
from pya2l import DB
from pya2l.api.inspect import Module

session = DB().open_create("my_project.a2l")
module = Module(session)

report = []
for meas in module.measurement.query():
    ifd = meas.if_data
    if not ifd.if_data_parsed:
        continue
    entry = {
        "name": meas.name,
        "parsed": ifd.if_data_parsed,
        "raw_texts": [obj.raw for obj in ifd.if_data_raw],
        "flatmap_keys": list(ifd.flatmap.keys()),
    }
    report.append(entry)

# Write a diagnostic JSON report
with open("ifdata_report.json", "w") as f:
    json.dump(report, f, indent=2, default=str)

print(f"Wrote {len(report)} IF_DATA entries to ifdata_report.json")

Manual parsing with IfDataParser#

If you have raw IF_DATA text from an external source (not from a parsed A2L), you can use IfDataParser directly. This requires a session whose AML schema has been loaded (i.e., the A2L file contained a /begin A2ML /end A2ML block):

from pya2l import DB
from pya2l.aml.ifdata_parser import IfDataParser

db = DB()
session = db.open_create("ASAP2_Demo_V161.a2l")

# Create the parser (reads AML from the session)
parser = IfDataParser(session)

# Parse a raw IF_DATA snippet
raw_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
    /end SEGMENT
/end IF_DATA"""

result = parser.parse(raw_text)
print(result)

Note

IfDataParser requires a valid AML schema in the database. If no /begin A2ML block was present in the original A2L, session.parse_ifdata() returns an empty list and the IfData object will have if_data_parsed == [].

Using setup_ifdata_parser on the session#

You can also set up IF_DATA parsing at the session level, which is what the inspect API does internally:

from pya2l import DB

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

# Initialize the IF_DATA parser for this session
session.setup_ifdata_parser(loglevel="DEBUG")

# Now session.parse_ifdata() is available
import pya2l.model as model
meas_obj = session.query(model.Measurement).filter_by(name="SomeMeasurement").first()
if meas_obj:
    parsed = session.parse_ifdata(meas_obj.if_data)
    print(parsed)

Empty IF_DATA and edge cases#

Not all entities have IF_DATA, and not all A2L files contain AML schemas. The IfData object handles these gracefully:

from pya2l import DB
from pya2l.api.inspect import Measurement

session = DB().open_create("simple_file.a2l")
meas = Measurement.get(session, "SomeSignal")

ifd = meas.if_data

# No IF_DATA blocks → empty lists
if not ifd.if_data_parsed:
    print("No parsed IF_DATA available")

if not ifd.if_data_raw:
    print("No raw IF_DATA blocks")

# flatmap is an empty dict
assert ifd.flatmap == {} or len(ifd.flatmap) == 0

# Safe to iterate — no exceptions
for key, vals in ifd.flatmap.items():
    print(key, vals)

Best practices#

  1. Check ``if_data_parsed`` first: It’s the convenient, structured representation. Fall back to if_data_raw only when you need the original text or the AML-based parsing is unavailable.

  2. Use ``flatmap`` for tag-based look-ups: Instead of walking nested dicts manually, use flatmap["TAG_NAME"] when you know the key.

  3. Iterate over ``if_data_raw`` for debugging: The .raw attribute gives you the exact text from the A2L file — invaluable when the parsed structure looks unexpected.

  4. Guard for empty data: Always check if ifd.if_data_parsed: before accessing elements. Empty IfData objects are normal and frequent.

  5. AML is required for parsing: Without an AML schema in the A2L file, if_data_parsed will be empty even if raw IF_DATA blocks exist. Use if_data_raw in that case.