Autosar之根据ARXML中报文信息生成SWC(二)

集成的时候只有dbc,asw使用的时候需要手动一个个创建port interface、runnable、runnable的event、每个runnable对应的rport、pport、port initvalue太麻烦了,于是自动化脚本实现,提高开发效率。

1、gen_swc_from_can.py

python 复制代码
#!/usr/bin/env python3
"""
Script: gen_swc_from_can.py
Parse J1939 CAN database ARXML and generate DaVinci Developer SWC files.
For each ECU node, generates:
  - One SENDER-RECEIVER-INTERFACE per signal (in shared PortInterfaces file)
  - One APPLICATION-SW-COMPONENT-TYPE with:
      - PPort for TX signals, RPort for RX signals
      - One Runnable per PDU/direction based on cycle time
"""

import xml.etree.ElementTree as ET
import uuid as uuid_lib
import os
import sys
from collections import defaultdict

# ─── Configuration ───────────────────────────────────────────────────────────
INPUT_ARXML = r"d:\workspace\Demo\davinci\ImportFiles\Databases\CHA_CAN.arxml"

OUTPUT_DIR = r"d:\workspace\Demo\davinci\ImportFiles\Swcs"
PORT_IF_DIR = os.path.join(OUTPUT_DIR, "PortInterfaces_J1939.arxml")

# ARXML settings
AR_SCHEMA = "http://autosar.org/schema/r4.0"
AR_XSI = "http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd"

# ─── Helpers ──────────────────────────────────────────────────────────────────
_new_uuids = {}  # cache deterministic UUIDs by key

def make_uuid(key):
    """Generate a deterministic UUID based on a string key."""
    if key not in _new_uuids:
        _new_uuids[key] = str(uuid_lib.uuid5(uuid_lib.NAMESPACE_DNS, key + "@j1939swc")).upper()
    return _new_uuids[key]


def indent(elem, level=0, spaces_per=2):
    """Pretty-print indentation for ElementTree (in-place)."""
    s = "\n" + " " * (level * spaces_per)
    if len(elem):
        if not elem.text or not elem.text.strip():
            elem.text = s + " " * spaces_per
        if not elem.tail or not elem.tail.strip():
            elem.tail = s
        for child in elem:
            indent(child, level + 1, spaces_per)
        if not child.tail or not child.tail.strip():
            child.tail = s
    else:
        if level and (not elem.tail or not elem.tail.strip()):
            elem.tail = s


def SubElement(parent, tag, text=None, **attribs):
    """Create a sub-element and return it, with optional text and attribs."""
    elem = ET.SubElement(parent, tag, **attribs)
    if text is not None:
        elem.text = text
    return elem


def arxml_preamble():
    """Return a minimal root ET.Element for a new ARXML file."""
    root = ET.Element(
        "AUTOSAR",
        attrib={
            "{http://www.w3.org/2001/XMLSchema-instance}schemaLocation": AR_XSI,
        },
    )
    root.set("xmlns", AR_SCHEMA)
    # xmlns:xsi is already defined by the xsi:schemaLocation attribute above
    return root


def arxml_element(root, short_name, uuid_key):
    """Build the standard AR-PACKAGES > AR-PACKAGE > ... structure.
    Returns (ar_package_root, elements_parent) tuple.
    """
    ar_packages = SubElement(root, "AR-PACKAGES")
    ar_package = SubElement(ar_packages, "AR-PACKAGE")
    SubElement(ar_package, "SHORT-NAME", text=short_name)
    elements = SubElement(ar_package, "ELEMENTS")
    return ar_package, elements


def _write_arxml(root_elem, filepath):
    """Write an ARXML file from an ElementTree root element.
    Handles namespace formatting to match DaVinci Developer convention.
    """
    ET.register_namespace("", AR_SCHEMA)
    raw = ET.tostring(root_elem, encoding="utf-8")
    # ElementTree with register_namespace("", ...) uses "ns0:" prefix
    # Replace to get clean default namespace output
    raw = raw.replace(b"ns0:", b"").replace(b" xmlns:ns0=", b" xmlns=")
    # Write the file
    with open(filepath, "wb") as f:
        f.write(b'<?xml version="1.0" encoding="utf-8"?>\n')
        f.write(raw)


def data_type_for_length(length_bits):
    """Map signal length (bits) to AUTOSAR ImplementationDataType reference."""
    if length_bits <= 8:
        return "/AUTOSAR_Platform/ImplementationDataTypes/uint8"
    elif length_bits <= 16:
        return "/AUTOSAR_Platform/ImplementationDataTypes/uint16"
    else:
        return "/AUTOSAR_Platform/ImplementationDataTypes/uint32"


def make_sender_receiver_interface(signal_name, data_type_ref):
    """Create a SENDER-RECEIVER-INTERFACE element for one signal."""
    sri = ET.Element("SENDER-RECEIVER-INTERFACE", UUID=make_uuid(f"sri_{signal_name}"))
    SubElement(sri, "SHORT-NAME", text=f"Pi{signal_name}")
    SubElement(sri, "IS-SERVICE", text="false")
    data_elems = SubElement(sri, "DATA-ELEMENTS")

    vdp = SubElement(data_elems, "VARIABLE-DATA-PROTOTYPE", UUID=make_uuid(f"vdp_{signal_name}"))
    SubElement(vdp, "SHORT-NAME", text=f"De{signal_name}")
    sdv = SubElement(vdp, "SW-DATA-DEF-PROPS")
    sdvv = SubElement(sdv, "SW-DATA-DEF-PROPS-VARIANTS")
    sdvc = SubElement(sdvv, "SW-DATA-DEF-PROPS-CONDITIONAL")
    SubElement(sdvc, "SW-CALIBRATION-ACCESS", text="NOT-ACCESSIBLE")
    SubElement(vdp, "TYPE-TREF", DEST="IMPLEMENTATION-DATA-TYPE", text=data_type_ref)
    return sri


def make_swc(ecu_name, tx_signal_names, rx_signal_names, signal_pdu_cycle_map):
    """Create APPLICATION-SW-COMPONENT-TYPE for one ECU.

    signal_pdu_cycle_map: dict { signal_name -> (pdu_name, cycle_sec, direction:'Tx'|'Rx') }
    """
    swc = ET.Element("APPLICATION-SW-COMPONENT-TYPE", UUID=make_uuid(f"swc_{ecu_name}"))
    SubElement(swc, "SHORT-NAME", text=f"CtAp{ecu_name}")

    # ── Ports ──────────────────────────────────────────────────────────────
    ports = SubElement(swc, "PORTS")

    # All unique signal names (both TX and RX) for this ECU
    all_signals = list(dict.fromkeys(tx_signal_names + rx_signal_names))  # preserve order, deduplicate
    all_signals_set = set(all_signals)

    for sig in all_signals:
        if sig in tx_signal_names:
            # P-PORT-PROTOTYPE (Provider)
            pp = SubElement(ports, "P-PORT-PROTOTYPE", UUID=make_uuid(f"pp_{ecu_name}_{sig}"))
            SubElement(pp, "SHORT-NAME", text=f"Pp{sig}")
            # COM-SPECS with init value
            pcs = SubElement(pp, "PROVIDED-COM-SPECS")
            nscs = SubElement(pcs, "NONQUEUED-SENDER-COM-SPEC")
            SubElement(nscs, "DATA-ELEMENT-REF", DEST="VARIABLE-DATA-PROTOTYPE",
                       text=f"/UserApplication/SwcJ1939/PortInterfaces/Pi{sig}/De{sig}")
            iv = SubElement(nscs, "INIT-VALUE")
            nvs = SubElement(iv, "NUMERICAL-VALUE-SPECIFICATION")
            SubElement(nvs, "VALUE", text="0")
            SubElement(pp, "PROVIDED-INTERFACE-TREF", DEST="SENDER-RECEIVER-INTERFACE",
                       text=f"/UserApplication/SwcJ1939/PortInterfaces/Pi{sig}")
        if sig in rx_signal_names:
            # R-PORT-PROTOTYPE (Require)
            rp = SubElement(ports, "R-PORT-PROTOTYPE", UUID=make_uuid(f"rp_{ecu_name}_{sig}"))
            SubElement(rp, "SHORT-NAME", text=f"Rp{sig}")
            # COM-SPECS with init value
            rcs = SubElement(rp, "REQUIRED-COM-SPECS")
            nrcs = SubElement(rcs, "NONQUEUED-RECEIVER-COM-SPEC")
            SubElement(nrcs, "DATA-ELEMENT-REF", DEST="VARIABLE-DATA-PROTOTYPE",
                       text=f"/UserApplication/SwcJ1939/PortInterfaces/Pi{sig}/De{sig}")
            SubElement(nrcs, "ALIVE-TIMEOUT", text="0")
            SubElement(nrcs, "ENABLE-UPDATE", text="true")
            flt = SubElement(nrcs, "FILTER")
            SubElement(flt, "DATA-FILTER-TYPE", text="ALWAYS")
            SubElement(nrcs, "HANDLE-NEVER-RECEIVED", text="false")
            iv = SubElement(nrcs, "INIT-VALUE")
            nvs = SubElement(iv, "NUMERICAL-VALUE-SPECIFICATION")
            SubElement(nvs, "VALUE", text="0")
            SubElement(rp, "REQUIRED-INTERFACE-TREF", DEST="SENDER-RECEIVER-INTERFACE",
                       text=f"/UserApplication/SwcJ1939/PortInterfaces/Pi{sig}")

    # ── Internal Behavior ──────────────────────────────────────────────────
    ib = SubElement(swc, "INTERNAL-BEHAVIORS")
    swc_ib = SubElement(ib, "SWC-INTERNAL-BEHAVIOR", UUID=make_uuid(f"ib_{ecu_name}"))
    SubElement(swc_ib, "SHORT-NAME", text=f"CtAp{ecu_name}_InternalBehavior")

    # ── Exclusive Areas (under SWC-INTERNAL-BEHAVIOR) ──
    eas = SubElement(swc_ib, "EXCLUSIVE-AREAS")
    ea = SubElement(eas, "EXCLUSIVE-AREA", UUID=make_uuid(f"ea_beh_{ecu_name}"))
    SubElement(ea, "SHORT-NAME", text="EaJ1939")

    # ── Events (before RUNNABLES per AUTOSAR schema) ─────────────────────
    events = SubElement(swc_ib, "EVENTS")

    # ── Runnables ──────────────────────────────────────────────────────────
    runnables = SubElement(swc_ib, "RUNNABLES")

    # Build runnables grouped by (PDU, direction) to avoid duplicates
    # Each runnable handles all signals of one PDU in one direction
    pdu_dir_signals = defaultdict(set)  # (pdu_name, direction) -> set of signal_name
    pdu_dir_cycle = {}  # (pdu_name, direction) -> cycle_sec

    for sig, entries in signal_pdu_cycle_map.items():
        for (pdu, cycle_sec, direction) in entries:
            key = (pdu, direction)
            pdu_dir_signals[key].add(sig)
            # Use the shortest cycle for this PDU+direction
            if key not in pdu_dir_cycle or cycle_sec < pdu_dir_cycle[key]:
                pdu_dir_cycle[key] = cycle_sec

    for (pdu_name, direction), signals_in_pdu in pdu_dir_signals.items():
        cycle_sec = pdu_dir_cycle[(pdu_name, direction)]
        cycle_ms = int(float(cycle_sec) * 1000)
        run_name = f"RE_{pdu_name}_{direction}_{cycle_ms:04d}ms"

        # Timing Event for this cyclic runnable
        tm_event = SubElement(events, "TIMING-EVENT", UUID=make_uuid(f"tm_evt_{ecu_name}_{pdu_name}_{direction}"))
        SubElement(tm_event, "SHORT-NAME", text=f"TMT_{run_name}")
        SubElement(tm_event, "START-ON-EVENT-REF", DEST="RUNNABLE-ENTITY",
                   text=f"/UserApplication/SwcJ1939/ComponentTypes/CtAp{ecu_name}/CtAp{ecu_name}_InternalBehavior/{run_name}")
        SubElement(tm_event, "PERIOD", text=str(float(cycle_sec)))

        re = SubElement(runnables, "RUNNABLE-ENTITY", UUID=make_uuid(f"re_{ecu_name}_{pdu_name}_{direction}"))
        SubElement(re, "SHORT-NAME", text=run_name)
        SubElement(re, "MINIMUM-START-INTERVAL", text=str(float(cycle_sec)))
        SubElement(re, "CAN-BE-INVOKED-CONCURRENTLY", text="false")

        if direction == "Rx":
            drpba = SubElement(re, "DATA-RECEIVE-POINT-BY-ARGUMENTS")
            for sig in signals_in_pdu:
                va = SubElement(drpba, "VARIABLE-ACCESS", UUID=make_uuid(f"va_rx_{ecu_name}_{pdu_name}_{sig}"))
                SubElement(va, "SHORT-NAME", text=f"REC_Pi{sig}_De{sig}")
                av = SubElement(va, "ACCESSED-VARIABLE")
                avi = SubElement(av, "AUTOSAR-VARIABLE-IREF")
                SubElement(avi, "PORT-PROTOTYPE-REF", DEST="R-PORT-PROTOTYPE",
                           text=f"/UserApplication/SwcJ1939/ComponentTypes/CtAp{ecu_name}/Rp{sig}")
                SubElement(avi, "TARGET-DATA-PROTOTYPE-REF", DEST="VARIABLE-DATA-PROTOTYPE",
                           text=f"/UserApplication/SwcJ1939/PortInterfaces/Pi{sig}/De{sig}")
        else:  # Tx
            dsp = SubElement(re, "DATA-SEND-POINTS")
            for sig in signals_in_pdu:
                va = SubElement(dsp, "VARIABLE-ACCESS", UUID=make_uuid(f"va_tx_{ecu_name}_{pdu_name}_{sig}"))
                SubElement(va, "SHORT-NAME", text=f"SEND_Pi{sig}_De{sig}")
                av = SubElement(va, "ACCESSED-VARIABLE")
                avi = SubElement(av, "AUTOSAR-VARIABLE-IREF")
                SubElement(avi, "PORT-PROTOTYPE-REF", DEST="P-PORT-PROTOTYPE",
                           text=f"/UserApplication/SwcJ1939/ComponentTypes/CtAp{ecu_name}/Pp{sig}")
                SubElement(avi, "TARGET-DATA-PROTOTYPE-REF", DEST="VARIABLE-DATA-PROTOTYPE",
                           text=f"/UserApplication/SwcJ1939/PortInterfaces/Pi{sig}/De{sig}")

        # SYMBOL must be last child per AUTOSAR schema order
        SubElement(re, "SYMBOL", text=run_name)

    # If no runnables were created (edge case), ensure RUNNABLES exists
    if len(pdu_dir_signals) == 0:
        # Remove the empty RUNNABLES
        swc_ib.remove(runnables)

    # SUPPORTS-MULTIPLE-INSTANTIATION must come after RUNNABLES per AUTOSAR schema order
    SubElement(swc_ib, "SUPPORTS-MULTIPLE-INSTANTIATION", text="false")

    return swc


def make_swc_implementation(ecu_name):
    """Create SWC-IMPLEMENTATION element for one ECU."""
    impl = ET.Element("SWC-IMPLEMENTATION", UUID=make_uuid(f"impl_{ecu_name}"))
    SubElement(impl, "SHORT-NAME", text=f"CtAp{ecu_name}_Implementation")
    SubElement(impl, "BEHAVIOR-REF", DEST="SWC-INTERNAL-BEHAVIOR",
               text=f"/UserApplication/SwcJ1939/ComponentTypes/CtAp{ecu_name}/CtAp{ecu_name}_InternalBehavior")
    return impl


# ─── Main parsing logic ───────────────────────────────────────────────────────
def parse_arxml(filepath):
    """Parse the CAN ARXML and extract ECU/Signal/PDU relationships."""
    tree = ET.parse(filepath)
    root = tree.getroot()
    ns = {"ar": AR_SCHEMA}

    # ── 1. Collect ECUs ────────────────────────────────────────────────────
    ecus = set()
    # Get ECUs from NM nodes
    for nm_node in root.iter("{http://autosar.org/schema/r4.0}J-1939-NM-NODE"):
        sn = nm_node.find("ar:SHORT-NAME", ns)
        if sn is not None and sn.text:
            ecus.add(sn.text)

    print(f"Found {len(ecus)} ECUs: {', '.join(sorted(ecus))}")

    # ── 2. I-SIGNAL definitions: ISignalName -> (length_bits, base_type_ref) ──
    isignal_info = {}  # ISignalShortName -> length (bits)
    for isig in root.iter("{http://autosar.org/schema/r4.0}I-SIGNAL"):
        sn = isig.find("ar:SHORT-NAME", ns)
        length_el = isig.find("ar:LENGTH", ns)
        if sn is not None and length_el is not None:
            isignal_info[sn.text] = int(length_el.text)

    print(f"Found {len(isignal_info)} I-SIGNALs")

    # ── 3. I-SIGNAL-TRIGGERING: signal -> {Tx: ecu, Rx: [ecus]} ──
    sig_triggering = {}  # ST_name -> (isignal_ref, [(ecu, direction)])
    sig_to_ecu = defaultdict(lambda: {"Tx": [], "Rx": []})  # ISignalShortName -> {Tx:[ecu], Rx:[ecus]}

    for ist in root.iter("{http://autosar.org/schema/r4.0}I-SIGNAL-TRIGGERING"):
        st_name = ist.find("ar:SHORT-NAME", ns)
        isig_ref = ist.find("ar:I-SIGNAL-REF", ns)
        if st_name is None or isig_ref is None:
            continue

        isig_name = isig_ref.text.split("/")[-1]  # Get the ISignal short name

        port_refs = ist.find("ar:I-SIGNAL-PORT-REFS", ns)
        if port_refs is None:
            continue

        ecu_dirs = []
        for port_ref in port_refs.findall("ar:I-SIGNAL-PORT-REF", ns):
            port_path = port_ref.text  # /ECU/{ECU}/CN_CHA_CAN/SP_{signal}_{Tx|Rx}
            if port_path:
                parts = port_path.split("/")
                ecu_name = parts[2]  # /ECU/{ECU}/...
                port_name = parts[4]  # SP_{signal}_{Tx|Rx}
                direction = "Tx" if port_name.endswith("_Tx") else "Rx"
                ecu_dirs.append((ecu_name, direction))
                sig_to_ecu[isig_name][direction].append(ecu_name)

        sig_triggering[st_name.text] = {
            "isignal_ref": isig_name,
            "ecu_dirs": ecu_dirs,
        }

    print(f"Found {len(sig_triggering)} I-SIGNAL-TRIGGERINGs")

    # ── 4. PDU-TRIGGERING: PDU -> {Tx: ecu, Rx: [ecus], signals: [ST_refs], PDU_ref} ──
    pdu_triggering = {}

    for pt in root.iter("{http://autosar.org/schema/r4.0}PDU-TRIGGERING"):
        pt_name = pt.find("ar:SHORT-NAME", ns)
        pdu_ref = pt.find("ar:I-PDU-REF", ns)
        if pt_name is None or pdu_ref is None:
            continue

        pdu_short = pdu_ref.text.split("/")[-1]  # e.g. ABS_DM1_oCHA_CAN

        # PDU port refs
        port_refs = pt.find("ar:I-PDU-PORT-REFS", ns)
        ecu_dirs = []
        if port_refs is not None:
            for port_ref in port_refs.findall("ar:I-PDU-PORT-REF", ns):
                port_path = port_ref.text
                if port_path:
                    parts = port_path.split("/")
                    ecu_name = parts[2]
                    port_name = parts[4]
                    direction = "Tx" if port_name.endswith("_Tx") else "Rx"
                    ecu_dirs.append((ecu_name, direction))

        # Signal triggering refs
        sig_refs = []
        sig_trig_container = pt.find("ar:I-SIGNAL-TRIGGERINGS", ns)
        if sig_trig_container is not None:
            for strc in sig_trig_container.findall("ar:I-SIGNAL-TRIGGERING-REF-CONDITIONAL", ns):
                str_ref = strc.find("ar:I-SIGNAL-TRIGGERING-REF", ns)
                if str_ref is not None and str_ref.text:
                    sig_refs.append(str_ref.text)

        pdu_triggering[pt_name.text] = {
            "pdu_ref": pdu_short,
            "ecu_dirs": ecu_dirs,
            "sig_triggering_refs": sig_refs,
        }

    print(f"Found {len(pdu_triggering)} PDU-TRIGGERINGs")

    # ── 5. I-SIGNAL-I-PDU: PDU timing ──────────────────────────────────────
    pdu_cycle_time = {}  # pdu_short_name -> cycle_time_seconds

    for pdu in root.iter("{http://autosar.org/schema/r4.0}I-SIGNAL-I-PDU"):
        pdu_sn = pdu.find("ar:SHORT-NAME", ns)
        if pdu_sn is None:
            continue

        # Get the first TIME-PERIOD.VALUE
        for tp in pdu.iter("{http://autosar.org/schema/r4.0}TIME-PERIOD"):
            val = tp.find("ar:VALUE", ns)
            if val is not None and val.text:
                pdu_cycle_time[pdu_sn.text] = float(val.text)
                break  # use the first cycle time (usually both are the same)

    print(f"Found {len(pdu_cycle_time)} PDUs with cycle time")

    return {
        "ecus": ecus,
        "isignal_info": isignal_info,
        "sig_to_ecu": sig_to_ecu,
        "sig_triggering": sig_triggering,
        "pdu_triggering": pdu_triggering,
        "pdu_cycle_time": pdu_cycle_time,
    }


def build_ecu_data(parsed):
    """Build per-ECU data from parsed structures."""
    ecus = parsed["ecus"]
    isignal_info = parsed["isignal_info"]
    sig_to_ecu = parsed["sig_to_ecu"]
    pdu_triggering = parsed["pdu_triggering"]
    pdu_cycle_time = parsed["pdu_cycle_time"]

    # ── Build ISignal name -> system signal name mapping ──────────────────
    # ISignal names look like: ABS_Active_oABS_EBC1_oCHA_CAN
    # System signal names: ABS_Active
    # We need a shorter name for the port interface.
    # Extract signal name from ISignal triggering name.
    # The ISignal SHORT-NAME format is: {SYS_SIGNAL}_{originPDU}_{bus}
    # Simpler: from sig_triggering -> isignal_ref (ISignal short name)
    # We'll map ISignal -> port interface signal name (the system signal part)

    # Build: isignal_name -> port_signal_name
    isig_to_portsig = {}
    for isig_name in isignal_info:
        # Pattern: SIGNALNAME_oPDU_oBUS -> SIGNALNAME
        # Most J1939 signals follow this pattern
        parts = isig_name.split("_o")
        if len(parts) >= 2:
            portsig = parts[0]
        else:
            portsig = isig_name
        isig_to_portsig[isig_name] = portsig

    # ── Build PDU -> signals (ISignal names) mapping ──────────────────────
    # From ST refs -> ST name -> ISignal name
    st_to_isig = {}  # ST (Signal Triggering) name -> ISignal name
    for st_name, st_data in parsed["sig_triggering"].items():
        st_to_isig[st_name] = st_data["isignal_ref"]

    # ── For each ECU, collect TX/RX signals with PDU+cycle ────────────────
    ecu_signals = defaultdict(lambda: {"tx": set(), "rx": set()})
    # signal_name -> set of (pdu_name, cycle_sec, direction)  (no duplicates)
    signal_pdu_cycle = defaultdict(set)

    for pt_name, pt_data in pdu_triggering.items():
        pdu_ref = pt_data["pdu_ref"]
        cycle_sec = pdu_cycle_time.get(pdu_ref, 1.0)  # default 1s if not found

        # Which ECUs are TX/RX for this PDU
        pdu_tx_ecu = None
        pdu_rx_ecus = []
        for ecu, direction in pt_data["ecu_dirs"]:
            if direction == "Tx":
                pdu_tx_ecu = ecu
            else:
                pdu_rx_ecus.append(ecu)

        # Get unique ISignal names for this PDU (deduplicate)
        isig_names = set()
        for st_ref in pt_data["sig_triggering_refs"]:
            # ST ref looks like: /Cluster/CHA_CAN/CHNL/ST_ABS_Active_oABS_EBC1
            st_name = st_ref.split("/")[-1]
            isig_name = st_to_isig.get(st_name)
            if isig_name:
                isig_names.add(isig_name)

        # Map ISignal names -> port signal names
        for isig_name in isig_names:
            portsig = isig_to_portsig.get(isig_name, isig_name)

            if pdu_tx_ecu:
                ecu_signals[pdu_tx_ecu]["tx"].add(portsig)
                signal_pdu_cycle[portsig].add((pdu_ref, cycle_sec, "Tx"))

            for rx_ecu in pdu_rx_ecus:
                ecu_signals[rx_ecu]["rx"].add(portsig)
                signal_pdu_cycle[portsig].add((pdu_ref, cycle_sec, "Rx"))

    return {
        "ecu_signals": ecu_signals,
        "signal_pdu_cycle": signal_pdu_cycle,
        "isignal_info": isignal_info,
        "isig_to_portsig": isig_to_portsig,
    }


def generate_output(parsed, ecu_data):
    """Generate ARXML output files."""
    os.makedirs(OUTPUT_DIR, exist_ok=True)

    ecus = sorted(parsed["ecus"])
    isignal_info = parsed["isignal_info"]
    isig_to_portsig = ecu_data["isig_to_portsig"]
    ecu_signals = ecu_data["ecu_signals"]
    signal_pdu_cycle = ecu_data["signal_pdu_cycle"]

    # ── Collect all unique signal names across all ECUs ────────────────────
    all_signals = set()
    for ecu in ecus:
        all_signals.update(ecu_signals[ecu]["tx"])
        all_signals.update(ecu_signals[ecu]["rx"])
    all_signals = sorted(all_signals)

    # Build ISignal name -> port signal -> ISignal length mapping
    portsig_to_isig = {}
    for isig_name, portsig in isig_to_portsig.items():
        portsig_to_isig[portsig] = isig_name

    # ── 1. Generate PortInterfaces file ────────────────────────────────────
    pi_root = arxml_preamble()
    mxl_pkg, mxl_elements = arxml_element(pi_root, "UserApplication", "mxl")
    mxl_sub_pkgs = SubElement(mxl_pkg, "AR-PACKAGES")
    swc_pkg = SubElement(mxl_sub_pkgs, "AR-PACKAGE")
    SubElement(swc_pkg, "SHORT-NAME", text="SwcJ1939")
    pi_sub_pkgs = SubElement(swc_pkg, "AR-PACKAGES")
    pi_pkg = SubElement(pi_sub_pkgs, "AR-PACKAGE")
    SubElement(pi_pkg, "SHORT-NAME", text="PortInterfaces")
    pi_elements = SubElement(pi_pkg, "ELEMENTS")

    # Remove empty ELEMENTS from UserApplication package
    mxl_pkg.remove(mxl_elements)

    for sig in all_signals:
        isig_name = portsig_to_isig.get(sig, sig)
        length = isignal_info.get(isig_name, 8)
        dt_ref = data_type_for_length(length)
        sri = make_sender_receiver_interface(sig, dt_ref)
        pi_elements.append(sri)

    indent(pi_root)

    # Write PortInterfaces file
    _write_arxml(pi_root, PORT_IF_DIR)
    print(f"[OK] Written: {PORT_IF_DIR}")
    print(f"  - {len(all_signals)} SENDER-RECEIVER-INTERFACEs")

    # ── 2. Generate ComponentType file for each ECU ────────────────────────
    for ecu in ecus:
        tx_sigs = sorted(ecu_signals[ecu]["tx"])
        rx_sigs = sorted(ecu_signals[ecu]["rx"])

        # Filter signal_pdu_cycle_map for this ECU's signals only
        ecu_sig_pdu = {}
        for sig in tx_sigs + rx_sigs:
            entries = signal_pdu_cycle.get(sig, set())
            # Only include entries where direction matches this ECU's role
            filtered = []
            for (pdu, cycle, direction) in entries:
                # For TX: only TX entries where this ECU was TX
                # For RX: only RX entries
                keep = False
                if sig in tx_sigs and direction == "Tx":
                    keep = True
                if sig in rx_sigs and direction == "Rx":
                    keep = True
                if keep:
                    filtered.append((pdu, cycle, direction))
            if filtered:
                ecu_sig_pdu[sig] = filtered

        swc_root = arxml_preamble()
        # Package: UserApplication -> SwcJ1939 -> ComponentTypes
        mxl_pkg2, mxl_elem2 = arxml_element(swc_root, "UserApplication", "mxl_ct_" + ecu)
        mxl_sub_pkgs2 = SubElement(mxl_pkg2, "AR-PACKAGES")
        swc_pkg2 = SubElement(mxl_sub_pkgs2, "AR-PACKAGE")
        SubElement(swc_pkg2, "SHORT-NAME", text="SwcJ1939")
        swc_sub_pkgs = SubElement(swc_pkg2, "AR-PACKAGES")
        ct_pkg = SubElement(swc_sub_pkgs, "AR-PACKAGE")
        SubElement(ct_pkg, "SHORT-NAME", text="ComponentTypes")
        ct_elements = SubElement(ct_pkg, "ELEMENTS")

        swc = make_swc(ecu, tx_sigs, rx_sigs, ecu_sig_pdu)
        ct_elements.append(swc)

        impl = make_swc_implementation(ecu)
        ct_elements.append(impl)

        # Remove empty ELEMENTS from UserApplication package
        mxl_pkg2.remove(mxl_elem2)

        indent(swc_root)

        comp_file = os.path.join(OUTPUT_DIR, f"CtAp{ecu}.arxml")
        _write_arxml(swc_root, comp_file)
        print(f"[OK] Written: {comp_file}")
        print(f"  - TX signals: {len(tx_sigs)}, RX signals: {len(rx_sigs)}")
        # Count runnables
        run_count = 0
        if len(ecu_sig_pdu) > 0:
            # collect unique (pdu, direction) pairs
            seen = set()
            for entries in ecu_sig_pdu.values():
                for (pdu, cycle, direction) in entries:
                    seen.add((pdu, direction))
            run_count = len(seen)
        print(f"  - Runnables: {run_count}")


# ─── Entry point ──────────────────────────────────────────────────────────────
def main():
    print("=" * 60)
    print("J1939 SWC Generator")
    print("=" * 60)
    print(f"Input:  {INPUT_ARXML}")
    print(f"Output: {OUTPUT_DIR}")
    print()

    parsed = parse_arxml(INPUT_ARXML)
    ecu_data = build_ecu_data(parsed)
    generate_output(parsed, ecu_data)

    print()
    print("Done! Files generated:")
    print(f"  1. {PORT_IF_DIR}")
    for ecu in sorted(parsed["ecus"]):
        print(f"     {os.path.join(OUTPUT_DIR, f'CtAp{ecu}.arxml')}")
    print()

if __name__ == "__main__":
    main()

提示:

只需指定以下三个参数内容即可:

INPUT_ARXML 输入arxml文件路径

OUTPUT_DIR 输出文件夹路径

PORT_IF_DIR 输出port文件路径

最终生成如下文件,实际根据用户arxml中ECU节点生成对应的swc,其中:

相关推荐
yangmu32031 小时前
Unity 开发过程与底层原理
java·开发语言
奈葵1 小时前
Python语句
开发语言·python
紫洋葱hh2 小时前
Go 入门基础:类型、变量与运算符
开发语言·后端·ai·golang
随性而行3602 小时前
企业微信API接口开发实战:如何实现消息回调接收与业务事件处理?
java·前端·python·微信·机器人·企业微信
美味蛋炒饭.2 小时前
java八股面试(3)
java·开发语言·面试
言乐62 小时前
Python于LOLM针对伊泽瑞尔的数据分析
开发语言·python·机器学习·数据分析·pandas
打工仔折腾 AI2 小时前
Qt网络编程实战:UDP、TCP到HTTP的落地经验与选型
网络·人工智能·后端·python·qt·udp·ai agent 实战
打工仔折腾 AI2 小时前
SQL 编辑器不是高级记事本:KStudio 与 KSQL Developer 的建表与增删改查手法
数据库·人工智能·后端·python·sql·编辑器·ai agent 实战
霸道流氓气质2 小时前
Saga模式入门与实战:编排与协同模式、补偿设计原则与Java生产级实现
java·开发语言