@h2loop/autosar 1.0.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/agents/autosar-integrator.md +34 -2
- package/package.json +4 -1
- package/plugins/index.js +53 -23
- package/python/ecuc_engine/__init__.py +1 -1
- package/python/ecuc_engine/add_signal.py +351 -0
- package/python/ecuc_engine/arxml_edit.py +387 -0
- package/python/ecuc_engine/audit.py +328 -48
- package/python/ecuc_engine/cli.py +114 -5
- package/python/ecuc_engine/codegen/__init__.py +185 -0
- package/python/ecuc_engine/codegen/binding.py +210 -0
- package/python/ecuc_engine/codegen/bindings/sip_standin.json +111 -0
- package/python/ecuc_engine/codegen/cdiff.py +395 -0
- package/python/ecuc_engine/codegen/cwriter.py +150 -0
- package/python/ecuc_engine/codegen/emit.py +395 -0
- package/python/ecuc_engine/codegen/handles.py +89 -0
- package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
- package/python/ecuc_engine/codegen/model.py +259 -0
- package/python/ecuc_engine/codegen/modules/__init__.py +7 -0
- package/python/ecuc_engine/codegen/modules/comstack.py +14 -0
- package/python/ecuc_engine/codegen/modules/dem.py +22 -0
- package/python/ecuc_engine/codegen/modules/mcal.py +170 -0
- package/python/ecuc_engine/codegen/modules/memstack.py +24 -0
- package/python/ecuc_engine/codegen/registry.py +133 -0
- package/python/ecuc_engine/codegen/schm_memmap.py +159 -0
- package/python/ecuc_engine/context.py +481 -0
- package/python/ecuc_engine/csrc.py +189 -0
- package/python/ecuc_engine/defs.py +30 -0
- package/python/ecuc_engine/find.py +162 -0
- package/python/ecuc_engine/gen.py +105 -0
- package/python/ecuc_engine/history.py +146 -0
- package/python/ecuc_engine/model.py +4 -1
- package/python/ecuc_engine/probe.py +229 -0
- package/python/ecuc_engine/rte_api.py +935 -0
- package/python/ecuc_engine/rules/__init__.py +5 -3
- package/python/ecuc_engine/rules/mem.py +84 -0
- package/python/ecuc_engine/rules/nvm.py +131 -0
- package/python/ecuc_engine/rules/pdur.py +87 -0
- package/python/ecuc_engine/rules/system.py +162 -0
- package/python/ecuc_engine/sysmodel.py +152 -0
- package/python/ecuc_engine/validate.py +15 -2
- package/python/ecuc_engine/verify_rte.py +445 -0
- package/python/pyproject.toml +1 -1
- package/skills/autosar/SKILL.md +47 -1
- package/skills/autosar/references/codegen.md +30 -0
- package/skills/autosar/references/project-layout.md +7 -1
- package/skills/autosar/references/runnable-code.md +50 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
"""CLI: python -m ecuc_engine [--root DIR] <info|read|edit|validate|diff
|
|
1
|
+
"""CLI: python -m ecuc_engine [--root DIR] <info|read|edit|validate|diff|audit|codegen|codegen-diff|rte-api|verify-rte|
|
|
2
|
+
build-context> ... --json
|
|
2
3
|
|
|
3
4
|
Always prints one JSON object on stdout. {"ok": false, "error": ...} on failure (exit code 0 for
|
|
4
5
|
handled errors, so callers parse stdout rather than the status).
|
|
@@ -49,16 +50,68 @@ def main(argv=None) -> int:
|
|
|
49
50
|
v = sub.add_parser("validate")
|
|
50
51
|
v.add_argument("--file", action="append")
|
|
51
52
|
v.add_argument("--module")
|
|
53
|
+
v.add_argument("--check-generated", dest="check_generated", action="store_true", default=None,
|
|
54
|
+
help="regenerate in a scratch copy and report stale generated files (H2L-GEN-001)")
|
|
55
|
+
v.add_argument("--no-check-generated", dest="check_generated", action="store_false")
|
|
56
|
+
v.add_argument("--no-code", dest="code", action="store_false", help="skip the C-usage completeness check")
|
|
52
57
|
v.add_argument("--max", type=int, default=0, help="cap the number of diagnostics returned (0 = all)")
|
|
53
58
|
au = sub.add_parser("audit")
|
|
54
59
|
au.add_argument("paths", nargs="*", help="C files/dirs to scan (default: audit.paths in .hydron/autosar.json, else Source/)")
|
|
55
60
|
au.add_argument("--headers", action="append", help="generated header dirs for symbols and RTE macros (default Output/)")
|
|
61
|
+
asg = sub.add_parser("add-signal")
|
|
62
|
+
asg.add_argument("--name", required=True)
|
|
63
|
+
asg.add_argument("--ipdu", required=True)
|
|
64
|
+
asg.add_argument("--bit-size", dest="bit_size", type=int)
|
|
65
|
+
asg.add_argument("--type", dest="signal_type", default="UINT8")
|
|
66
|
+
asg.add_argument("--bit-position", dest="bit_position", type=int)
|
|
67
|
+
asg.add_argument("--endianness", default="LITTLE_ENDIAN")
|
|
68
|
+
asg.add_argument("--init", default="0")
|
|
69
|
+
asg.add_argument("--transfer-property", dest="transfer_property")
|
|
70
|
+
asg.add_argument("--dry-run", action="store_true")
|
|
71
|
+
ae = sub.add_parser("arxml-edit")
|
|
72
|
+
ae.add_argument("--ops", required=True)
|
|
73
|
+
ae.add_argument("--dry-run", action="store_true")
|
|
74
|
+
fd = sub.add_parser("find")
|
|
75
|
+
fd.add_argument("--module")
|
|
76
|
+
fd.add_argument("--container-def", dest="container_def")
|
|
77
|
+
fd.add_argument("--where", help='"Param=Value, Other>3" or a JSON object')
|
|
78
|
+
fd.add_argument("--ref-to", dest="ref_to")
|
|
79
|
+
fd.add_argument("--referenced-by", dest="referenced_by")
|
|
80
|
+
fd.add_argument("--limit", type=int, default=50)
|
|
81
|
+
cx = sub.add_parser("crosscheck")
|
|
82
|
+
cx.add_argument("module")
|
|
56
83
|
so = sub.add_parser("signal-owners")
|
|
57
84
|
so.add_argument("paths", nargs="+")
|
|
58
85
|
d = sub.add_parser("diff")
|
|
59
86
|
d.add_argument("a")
|
|
60
87
|
d.add_argument("b")
|
|
61
|
-
|
|
88
|
+
cg = sub.add_parser("codegen", help="deterministic config code generation")
|
|
89
|
+
cg.add_argument("--module", action="append", help="module to generate (repeatable; default: all registered ones configured)")
|
|
90
|
+
cg.add_argument("--out", help="output dir (default: .hydron/codegen/out scratch; 'project' = codegen.outDir, else Output)")
|
|
91
|
+
cg.add_argument("--no-write-back", action="store_true", help="do not write assigned handle IDs into the ECUC ARXML")
|
|
92
|
+
cg.add_argument("--binding", help="static-code binding name ('none' = generic tables only)")
|
|
93
|
+
cd = sub.add_parser("codegen-diff", help="semantic diff of generated C against a reference dir")
|
|
94
|
+
cd.add_argument("reference")
|
|
95
|
+
cd.add_argument("generated", nargs="?", help="default: the codegen scratch dir")
|
|
96
|
+
cd.add_argument("--module", action="append")
|
|
97
|
+
cd.add_argument("--max", type=int, default=50, help="max element mismatches listed per object")
|
|
98
|
+
ra = sub.add_parser("rte-api", help="expected RTE API per SW-C / runnable (SWS_RTE naming and signatures)")
|
|
99
|
+
ra.add_argument("--swc")
|
|
100
|
+
ra.add_argument("--runnable")
|
|
101
|
+
ra.add_argument("--no-header", action="store_true", help="skip matching against Rte_<Swc>.h")
|
|
102
|
+
vr = sub.add_parser("verify-rte", help="classify Rte_* calls in C per runnable: OK / NOT-ALLOWED / INVENTED")
|
|
103
|
+
vr.add_argument("paths", nargs="*", help="C files/dirs/globs (default: all in-house C)")
|
|
104
|
+
vr.add_argument("--diff-against", dest="diff_against", help="only calls on lines changed since this git ref")
|
|
105
|
+
vr.add_argument("--swc")
|
|
106
|
+
vr.add_argument("--runnable")
|
|
107
|
+
vr.add_argument("--no-check-ops", dest="check_ops", action="store_false")
|
|
108
|
+
bc = sub.add_parser("build-context", help="deterministic context for implementing/changing a runnable")
|
|
109
|
+
bc.add_argument("--swc", required=True)
|
|
110
|
+
bc.add_argument("--runnable")
|
|
111
|
+
bc.add_argument("--task", required=True, help="task text, @file, or - for stdin")
|
|
112
|
+
bc.add_argument("--budget-tokens", dest="budget_tokens", type=int, default=24000)
|
|
113
|
+
bc.add_argument("--no-history", dest="history", action="store_false")
|
|
114
|
+
for p in (r, e, v, d, au, so, fd, cx, ae, asg, cg, cd, ra, vr, bc): # allow --json after the subcommand too
|
|
62
115
|
p.add_argument("--json", action="store_true", help=argparse.SUPPRESS)
|
|
63
116
|
a = ap.parse_args(argv)
|
|
64
117
|
t0 = time.time()
|
|
@@ -74,7 +127,7 @@ def main(argv=None) -> int:
|
|
|
74
127
|
|
|
75
128
|
|
|
76
129
|
def run(a) -> dict:
|
|
77
|
-
if a.cmd
|
|
130
|
+
if a.cmd in ("edit", "arxml-edit"):
|
|
78
131
|
try:
|
|
79
132
|
a.ops = _ops(a.ops)
|
|
80
133
|
except (ValueError, OSError) as ex:
|
|
@@ -87,8 +140,64 @@ def run(a) -> dict:
|
|
|
87
140
|
return diff(a.a, a.b, rel, root)
|
|
88
141
|
except FileNotFoundError as ex:
|
|
89
142
|
return {"error": str(ex)}
|
|
143
|
+
if a.cmd == "codegen-diff":
|
|
144
|
+
from .codegen import SCRATCH
|
|
145
|
+
from .codegen.cdiff import diff_dirs
|
|
146
|
+
root = os.path.abspath(a.root)
|
|
147
|
+
ab = lambda p: p if os.path.isabs(p) else os.path.join(root, p) # noqa: E731
|
|
148
|
+
ref, gen = ab(a.reference), ab(a.generated or SCRATCH)
|
|
149
|
+
for p in (ref, gen):
|
|
150
|
+
if not os.path.exists(p):
|
|
151
|
+
return {"error": f"{p} does not exist" + (" (run codegen first)" if p == gen else "")}
|
|
152
|
+
res = diff_dirs(ref, gen, modules=a.module, max_items=a.max)
|
|
153
|
+
res.update({"reference": ref, "generated": gen})
|
|
154
|
+
return res
|
|
155
|
+
if a.cmd == "rte-api":
|
|
156
|
+
from .rte_api import rte_api
|
|
157
|
+
return rte_api(os.path.abspath(a.root), swc=a.swc, runnable=a.runnable, header=not a.no_header)
|
|
158
|
+
if a.cmd == "verify-rte":
|
|
159
|
+
from .verify_rte import verify
|
|
160
|
+
return verify(os.path.abspath(a.root), files=a.paths or None, diff_against=a.diff_against, swc=a.swc,
|
|
161
|
+
runnable=a.runnable, check_ops=a.check_ops)
|
|
162
|
+
if a.cmd == "build-context":
|
|
163
|
+
from .context import build_context
|
|
164
|
+
task = a.task
|
|
165
|
+
if task == "-":
|
|
166
|
+
task = sys.stdin.read()
|
|
167
|
+
elif task.startswith("@"):
|
|
168
|
+
with open(task[1:]) as fh:
|
|
169
|
+
task = fh.read()
|
|
170
|
+
return build_context(os.path.abspath(a.root), a.swc, task, runnable=a.runnable,
|
|
171
|
+
budget_tokens=a.budget_tokens, history=a.history)
|
|
90
172
|
from .model import Project
|
|
91
|
-
project = Project(a.root, config_files=a.config, def_files=a.defs, load_input=a.cmd in ("validate", "edit"))
|
|
173
|
+
project = Project(a.root, config_files=a.config, def_files=a.defs, load_input=a.cmd in ("validate", "edit", "find", "crosscheck", "signal-owners", "arxml-edit", "add-signal"))
|
|
174
|
+
if a.cmd == "add-signal":
|
|
175
|
+
from .add_signal import AddSignalError, add_signal
|
|
176
|
+
try:
|
|
177
|
+
return add_signal(project, a.name, a.ipdu, bit_size=a.bit_size, signal_type=a.signal_type,
|
|
178
|
+
bit_position=a.bit_position, endianness=a.endianness, init=a.init,
|
|
179
|
+
transfer_property=a.transfer_property, dry_run=a.dry_run)
|
|
180
|
+
except AddSignalError as ex:
|
|
181
|
+
return {"error": str(ex), "written": False}
|
|
182
|
+
if a.cmd == "arxml-edit":
|
|
183
|
+
from .arxml_edit import arxml_edit
|
|
184
|
+
return arxml_edit(project, a.ops, dry_run=a.dry_run)
|
|
185
|
+
if a.cmd == "find":
|
|
186
|
+
from .find import find
|
|
187
|
+
where = a.where
|
|
188
|
+
if where and where.strip().startswith("{"):
|
|
189
|
+
where = json.loads(where)
|
|
190
|
+
return find(project, module=a.module, container_def=a.container_def, where=where, ref_to=a.ref_to,
|
|
191
|
+
referenced_by=a.referenced_by, limit=a.limit)
|
|
192
|
+
if a.cmd == "crosscheck":
|
|
193
|
+
from .find import crosscheck
|
|
194
|
+
return crosscheck(project, a.module)
|
|
195
|
+
if a.cmd == "codegen":
|
|
196
|
+
from .codegen import generate
|
|
197
|
+
res = generate(project, modules=a.module, out_dir=a.out, write_back=not a.no_write_back, binding=a.binding)
|
|
198
|
+
if project.warnings:
|
|
199
|
+
res["warnings"] = project.warnings
|
|
200
|
+
return res
|
|
92
201
|
if a.cmd == "signal-owners":
|
|
93
202
|
from .audit import signal_owners
|
|
94
203
|
return {"owners": signal_owners(project, a.paths)}
|
|
@@ -115,7 +224,7 @@ def run(a) -> dict:
|
|
|
115
224
|
return res
|
|
116
225
|
if a.cmd == "validate":
|
|
117
226
|
from .validate import validate
|
|
118
|
-
res = validate(project, files=a.file, module=a.module)
|
|
227
|
+
res = validate(project, files=a.file, module=a.module, check_generated=a.check_generated, code=a.code)
|
|
119
228
|
if a.max and len(res["diagnostics"]) > a.max:
|
|
120
229
|
res["truncated"] = len(res["diagnostics"]) - a.max
|
|
121
230
|
res["diagnostics"] = res["diagnostics"][: a.max]
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"""Deterministic ECUC -> C configuration code generation.
|
|
2
|
+
|
|
3
|
+
Same ECUC ARXML + same param defs -> byte-identical C. No LLM is involved: every line comes from
|
|
4
|
+
the param defs (generic emitter), a module generator (codegen/modules/, only for what the defs
|
|
5
|
+
cannot express), or a static-code binding (codegen/bindings/*.json, the vendor struct layout).
|
|
6
|
+
|
|
7
|
+
Entry point: generate(project, modules=None, out_dir=None, write_back=True).
|
|
8
|
+
Project options (.hydron/autosar.json, key "codegen"):
|
|
9
|
+
{"outDir": "Output", # the project's generated-code folder (used when out_dir="project")
|
|
10
|
+
"binding": "sip-standin", # force a static-code binding ("none" = generic tables only)
|
|
11
|
+
"mip": {"Eep": "Eep_21_LDExt"}, # module implementation prefixes (vendorId/vendorApiInfix)
|
|
12
|
+
"memmap": {"pragmas": {"CODE": {"start": "#pragma ...", "stop": "#pragma ..."}}},
|
|
13
|
+
"mcal": {"silicon": "tc4d", "pinMap": "mcal/pins.json"}}
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import hashlib
|
|
18
|
+
import json
|
|
19
|
+
import os
|
|
20
|
+
import re
|
|
21
|
+
import time
|
|
22
|
+
from typing import Optional
|
|
23
|
+
|
|
24
|
+
from .. import paths as P
|
|
25
|
+
from ..model import PROJECT_CONFIG, Project
|
|
26
|
+
from . import binding as B
|
|
27
|
+
from . import handles as H
|
|
28
|
+
from . import schm_memmap as S
|
|
29
|
+
from .emit import Ctx
|
|
30
|
+
from .model import CgModel
|
|
31
|
+
from .registry import generator_for, registered
|
|
32
|
+
|
|
33
|
+
SCRATCH = os.path.join(".hydron", "codegen", "out")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def project_options(root: str) -> dict:
|
|
37
|
+
f = os.path.join(root, PROJECT_CONFIG)
|
|
38
|
+
if not os.path.isfile(f):
|
|
39
|
+
return {}
|
|
40
|
+
try:
|
|
41
|
+
with open(f) as fh:
|
|
42
|
+
return json.load(fh).get("codegen", {}) or {}
|
|
43
|
+
except (OSError, ValueError):
|
|
44
|
+
return {}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def _scan_c(root: str, exclude: list) -> tuple:
|
|
48
|
+
"""(headers basename->path, source files) of the project's static/in-house C, excluding generator output."""
|
|
49
|
+
from ..audit import is_generated
|
|
50
|
+
ex = [os.path.realpath(e) + os.sep for e in exclude if e]
|
|
51
|
+
w = P.walk(root, lambda n: n.endswith((".h", ".c")))
|
|
52
|
+
headers: dict = {}
|
|
53
|
+
sources: list = []
|
|
54
|
+
for f in w.files:
|
|
55
|
+
rf = os.path.realpath(f)
|
|
56
|
+
if any(rf.startswith(e) for e in ex):
|
|
57
|
+
continue
|
|
58
|
+
if f.endswith(".h"):
|
|
59
|
+
headers.setdefault(os.path.basename(f), f)
|
|
60
|
+
if not is_generated(f):
|
|
61
|
+
sources.append(f)
|
|
62
|
+
return headers, sources
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def resolve_out(root: str, out_dir: Optional[str], opts: dict) -> str:
|
|
66
|
+
if not out_dir:
|
|
67
|
+
return os.path.join(root, SCRATCH)
|
|
68
|
+
if out_dir == "project":
|
|
69
|
+
out_dir = opts.get("outDir", "Output")
|
|
70
|
+
return out_dir if os.path.isabs(out_dir) else os.path.join(root, out_dir)
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def generate(project: Project, modules: Optional[list] = None, out_dir: Optional[str] = None,
|
|
74
|
+
write_back: bool = True, binding: Optional[str] = None) -> dict:
|
|
75
|
+
t0 = time.time()
|
|
76
|
+
root = project.root
|
|
77
|
+
opts = project_options(root)
|
|
78
|
+
out = resolve_out(root, out_dir, opts)
|
|
79
|
+
binding = binding or opts.get("binding")
|
|
80
|
+
timings: dict = {}
|
|
81
|
+
|
|
82
|
+
cg = CgModel(project, opts.get("mip"))
|
|
83
|
+
configured = sorted(cg.modules)
|
|
84
|
+
if modules:
|
|
85
|
+
want = []
|
|
86
|
+
for m in modules:
|
|
87
|
+
hit = [x for x in configured if x.lower() == m.lower()]
|
|
88
|
+
if not hit:
|
|
89
|
+
return {"ok": False, "error": f"module {m!r} is not configured (configured: {', '.join(configured)})"}
|
|
90
|
+
want.append(hit[0])
|
|
91
|
+
else:
|
|
92
|
+
want = [m for m in registered() if m in cg.modules]
|
|
93
|
+
if not project.defs:
|
|
94
|
+
return {"ok": False, "error": "code generation needs the ECUC param defs (paramDefs in .hydron/autosar.json "
|
|
95
|
+
"or $AUTOSAR_PARAMDEFS): config classes and types come from them"}
|
|
96
|
+
timings["load_ms"] = int((time.time() - t0) * 1000)
|
|
97
|
+
|
|
98
|
+
# 1. handle IDs (TPS_ECUC_02107): assign, write back (or patch in memory only)
|
|
99
|
+
t1 = time.time()
|
|
100
|
+
pl = H.plan(cg, want)
|
|
101
|
+
if pl["errors"]:
|
|
102
|
+
return {"ok": False, "error": "handle-ID conflicts; fix the configuration first", "handleErrors": pl["errors"]}
|
|
103
|
+
wb = H.apply(project, pl["assign"], write=write_back)
|
|
104
|
+
if pl["assign"]:
|
|
105
|
+
cg = CgModel(project, opts.get("mip"))
|
|
106
|
+
timings["handles_ms"] = int((time.time() - t1) * 1000)
|
|
107
|
+
|
|
108
|
+
# 2. emit
|
|
109
|
+
t2 = time.time()
|
|
110
|
+
proj_out = resolve_out(root, "project", opts)
|
|
111
|
+
headers, sources = _scan_c(root, [out, proj_out, os.path.join(root, ".hydron")])
|
|
112
|
+
ctx = Ctx(cg=cg, headers=headers, bindings=B.load_all(root), binding_name=binding, release=cg.release(),
|
|
113
|
+
sources=sources)
|
|
114
|
+
ctx.options = opts
|
|
115
|
+
ctx.generating = set(want)
|
|
116
|
+
ctx.available = set(headers) | (set(os.listdir(proj_out)) if os.path.isdir(proj_out) else set())
|
|
117
|
+
files: dict = {}
|
|
118
|
+
report: dict = {}
|
|
119
|
+
memmaps = []
|
|
120
|
+
notes = list(pl["notes"])
|
|
121
|
+
for name in want:
|
|
122
|
+
mc = cg.module(name)
|
|
123
|
+
gen = generator_for(name)(ctx, mc)
|
|
124
|
+
mo = gen.run()
|
|
125
|
+
rendered = mo.render()
|
|
126
|
+
n, text = S.schm_header(ctx, mo)
|
|
127
|
+
rendered[n] = text
|
|
128
|
+
mm = f"{mo.mip}_MemMap.h"
|
|
129
|
+
if mm in headers:
|
|
130
|
+
notes.append(f"{name}: project provides {P_rel(root, headers[mm])}; {mm} not generated")
|
|
131
|
+
else:
|
|
132
|
+
n, text, sections = S.module_memmap(ctx, mo, (opts.get("memmap") or {}).get("pragmas", {}))
|
|
133
|
+
rendered[n] = text
|
|
134
|
+
memmaps.append((mo.mip, sections))
|
|
135
|
+
files.update(rendered)
|
|
136
|
+
report[name] = {
|
|
137
|
+
"variant": mc.variant,
|
|
138
|
+
"binding": (mo.binding or {}).get("_binding"),
|
|
139
|
+
"files": sorted(rendered),
|
|
140
|
+
"symbols": len(mo.symbols),
|
|
141
|
+
"notes": mo.notes,
|
|
142
|
+
}
|
|
143
|
+
if memmaps and "MemMap.h" not in headers:
|
|
144
|
+
files["MemMap.h"] = S.aggregate_memmap(ctx, memmaps)
|
|
145
|
+
elif memmaps:
|
|
146
|
+
notes.append(f"project MemMap.h ({P_rel(root, headers['MemMap.h'])}) kept; generated code includes "
|
|
147
|
+
"<Mip>_MemMap.h (SWS_MemMap_00032)")
|
|
148
|
+
timings["emit_ms"] = int((time.time() - t2) * 1000)
|
|
149
|
+
|
|
150
|
+
# 3. write
|
|
151
|
+
os.makedirs(out, exist_ok=True)
|
|
152
|
+
manifest = []
|
|
153
|
+
for name in sorted(files):
|
|
154
|
+
data = files[name].encode("utf-8")
|
|
155
|
+
path = os.path.join(out, name)
|
|
156
|
+
with open(path, "wb") as fh:
|
|
157
|
+
fh.write(data)
|
|
158
|
+
manifest.append({"file": name, "bytes": len(data), "sha256": hashlib.sha256(data).hexdigest()[:16]})
|
|
159
|
+
std = _std_types_release(headers)
|
|
160
|
+
if std and std[:2] != ctx.release[:2]:
|
|
161
|
+
notes.append(f"Std_Types.h in the project is AUTOSAR {std[0]}.{std[1]}.x but the configuration is "
|
|
162
|
+
f"{ctx.release[0]}.{ctx.release[1]}.x (schema); the release check against <Mip>.h may fail")
|
|
163
|
+
timings["total_ms"] = int((time.time() - t0) * 1000)
|
|
164
|
+
return {"ok": True, "outDir": out, "modules": report, "files": manifest,
|
|
165
|
+
"handles": {"assigned": pl["assign"], "writtenBack": wb["written"], "changes": wb["changes"]},
|
|
166
|
+
"notes": notes, "timings": timings, "release": ".".join(map(str, ctx.release))}
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def P_rel(root: str, f: str) -> str:
|
|
170
|
+
r = os.path.relpath(f, root)
|
|
171
|
+
return f if r.startswith("..") else r
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def _std_types_release(headers: dict) -> Optional[tuple]:
|
|
175
|
+
f = headers.get("Std_Types.h")
|
|
176
|
+
if not f:
|
|
177
|
+
return None
|
|
178
|
+
try:
|
|
179
|
+
with open(f, errors="replace") as fh:
|
|
180
|
+
t = fh.read()
|
|
181
|
+
except OSError:
|
|
182
|
+
return None
|
|
183
|
+
m1 = re.search(r"STD_TYPES_AR_RELEASE_MAJOR_VERSION\s+\(?(\d+)", t)
|
|
184
|
+
m2 = re.search(r"STD_TYPES_AR_RELEASE_MINOR_VERSION\s+\(?(\d+)", t)
|
|
185
|
+
return (int(m1.group(1)), int(m2.group(1))) if m1 and m2 else None
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
"""Static-code bindings: the data contract between generated tables and a BSW implementation.
|
|
2
|
+
|
|
3
|
+
The ECUC param defs say WHAT is configured; they do not say how a given implementation's static
|
|
4
|
+
code lays out its configuration structs (`NvM_BlockDescriptorType`, `PduR_RoutingPathType`, ...).
|
|
5
|
+
That layout belongs to the BSW vendor (SIP). A binding is a JSON file that maps those structs,
|
|
6
|
+
field by field, onto ECUC values with a small path language, so no layout is hard-coded in Python.
|
|
7
|
+
|
|
8
|
+
Bundled bindings live in `codegen/bindings/*.json`; a project adds or overrides them in
|
|
9
|
+
`.hydron/codegen/bindings/*.json`. A module binding is used when its `detect` header is found in
|
|
10
|
+
the project (or when the binding is named explicitly).
|
|
11
|
+
|
|
12
|
+
Path language (a list of steps, evaluated left to right from the row container):
|
|
13
|
+
param:<P> value of parameter/reference P in the current container (terminal)
|
|
14
|
+
sub:<Def> first sub-container of def <Def>
|
|
15
|
+
ref:<R> follow ECUC reference R to its target container
|
|
16
|
+
back:<Def>/<R> the container of def <Def> (any module) whose reference R points here
|
|
17
|
+
peer:<R>[@<Module>] follow R to target T, then the container that references T from another
|
|
18
|
+
module (or from <Module>): how a module finds its neighbour's PDU handle
|
|
19
|
+
(TPS_ECUC 3.4.1.3, via EcucPduCollection/Pdu)
|
|
20
|
+
sym TPS_ECUC_02108 symbol of the current container (terminal)
|
|
21
|
+
module | name | index the current container's module name / SHORT-NAME / table index (terminal)
|
|
22
|
+
Macro values: count:<Def> | max:<Def>/<Param>, optionally followed by +N or -N.
|
|
23
|
+
"""
|
|
24
|
+
from __future__ import annotations
|
|
25
|
+
|
|
26
|
+
import glob
|
|
27
|
+
import json
|
|
28
|
+
import os
|
|
29
|
+
import re
|
|
30
|
+
from typing import Optional
|
|
31
|
+
|
|
32
|
+
from ..values import parse_int
|
|
33
|
+
from . import cwriter as W
|
|
34
|
+
from .model import CgModel, Container, ModuleCfg
|
|
35
|
+
|
|
36
|
+
HERE = os.path.dirname(__file__)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class BindingError(Exception):
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def load_all(root: str) -> dict:
|
|
44
|
+
"""name -> binding dict; project bindings override bundled ones of the same name."""
|
|
45
|
+
out: dict = {}
|
|
46
|
+
for f in sorted(glob.glob(os.path.join(HERE, "bindings", "*.json"))) + sorted(
|
|
47
|
+
glob.glob(os.path.join(root, ".hydron", "codegen", "bindings", "*.json"))):
|
|
48
|
+
with open(f) as fh:
|
|
49
|
+
b = json.load(fh)
|
|
50
|
+
b["_file"] = f
|
|
51
|
+
out[b["name"]] = b
|
|
52
|
+
return out
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def select(bindings: dict, module: str, headers: dict, explicit: Optional[str]) -> Optional[dict]:
|
|
56
|
+
"""The module binding to use: the explicitly named binding, else the first whose detect header matches."""
|
|
57
|
+
if explicit == "none":
|
|
58
|
+
return None
|
|
59
|
+
names = [explicit] if explicit else sorted(bindings)
|
|
60
|
+
for n in names:
|
|
61
|
+
b = bindings.get(n)
|
|
62
|
+
if b is None:
|
|
63
|
+
raise BindingError(f"unknown binding {n!r} (known: {', '.join(sorted(bindings)) or 'none'})")
|
|
64
|
+
mb = b.get("modules", {}).get(module)
|
|
65
|
+
if mb is None:
|
|
66
|
+
continue
|
|
67
|
+
det = mb.get("detect")
|
|
68
|
+
if explicit or det is None or _detect(det, headers):
|
|
69
|
+
return dict(mb, _binding=n)
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _detect(det: dict, headers: dict) -> bool:
|
|
74
|
+
path = headers.get(det["header"])
|
|
75
|
+
if not path:
|
|
76
|
+
return False
|
|
77
|
+
try:
|
|
78
|
+
with open(path, errors="replace") as f:
|
|
79
|
+
text = f.read()
|
|
80
|
+
except OSError:
|
|
81
|
+
return False
|
|
82
|
+
return all(t in text for t in ([det["contains"]] if isinstance(det.get("contains"), str) else det.get("contains", [])))
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Eval:
|
|
86
|
+
"""Evaluates binding paths against one module; records touched params (config class) and used modules."""
|
|
87
|
+
|
|
88
|
+
def __init__(self, cg: CgModel, mc: ModuleCfg, ctx=None):
|
|
89
|
+
self.ctx = ctx
|
|
90
|
+
self.cg = cg
|
|
91
|
+
self.mc = mc
|
|
92
|
+
self.rank = 1
|
|
93
|
+
self.used_mips: set = set()
|
|
94
|
+
|
|
95
|
+
def steps(self, start: Container, steps) -> tuple:
|
|
96
|
+
"""-> ("container", Container) | ("value", (raw, def, owner)) | ("token", str) | (None, None)"""
|
|
97
|
+
if isinstance(steps, str):
|
|
98
|
+
steps = [steps]
|
|
99
|
+
cur = start
|
|
100
|
+
for i, st in enumerate(steps):
|
|
101
|
+
op, _, arg = st.partition(":")
|
|
102
|
+
last = i == len(steps) - 1
|
|
103
|
+
if op == "param":
|
|
104
|
+
pv = cur.value(arg)
|
|
105
|
+
d = self.cg.param_def(cur, arg)
|
|
106
|
+
self._touch(cur, d)
|
|
107
|
+
if pv is not None and pv.is_ref:
|
|
108
|
+
if last:
|
|
109
|
+
return ("value", (pv.value, d, cur))
|
|
110
|
+
cur = self.cg.target(pv)
|
|
111
|
+
else:
|
|
112
|
+
return ("value", (pv.value if pv is not None else None, d, cur)) if last else (None, None)
|
|
113
|
+
elif op == "sub":
|
|
114
|
+
subs = cur.sub(arg)
|
|
115
|
+
cur = subs[0] if subs else None
|
|
116
|
+
elif op == "ref":
|
|
117
|
+
self._touch(cur, self.cg.param_def(cur, arg))
|
|
118
|
+
cur = self.cg.target(cur.value(arg))
|
|
119
|
+
elif op == "back":
|
|
120
|
+
dname, _, rname = arg.partition("/")
|
|
121
|
+
hits = [c for c in self.cg.referrers(cur, ref=rname) if c.def_name == dname]
|
|
122
|
+
cur = hits[0] if hits else None
|
|
123
|
+
if cur is not None:
|
|
124
|
+
self._touch(cur, self.cg.param_def(cur, rname))
|
|
125
|
+
elif op == "peer":
|
|
126
|
+
rname, _, want = arg.partition("@")
|
|
127
|
+
self._touch(cur, self.cg.param_def(cur, rname))
|
|
128
|
+
t = self.cg.target(cur.value(rname))
|
|
129
|
+
if t is None:
|
|
130
|
+
return (None, None)
|
|
131
|
+
hits = [c for c in self.cg.referrers(t) if c is not cur and
|
|
132
|
+
(c.module.name == want if want else c.module is not cur.module)]
|
|
133
|
+
hits.sort(key=lambda c: (c.module.name, self.cg.by_path_index(c)))
|
|
134
|
+
cur = hits[0] if hits else None
|
|
135
|
+
elif op == "sym":
|
|
136
|
+
s = self.cg.symbol(cur)
|
|
137
|
+
if s and self.ctx is not None:
|
|
138
|
+
s = self.ctx.xsym(cur, self.mc, self.used_mips)
|
|
139
|
+
return ("token", s) if s else (None, None)
|
|
140
|
+
elif op == "module":
|
|
141
|
+
return ("token", cur.module.mip)
|
|
142
|
+
elif op == "name":
|
|
143
|
+
return ("token", cur.name)
|
|
144
|
+
elif op == "index":
|
|
145
|
+
return ("token", W.c_uint(self.cg.index_of(cur)))
|
|
146
|
+
else:
|
|
147
|
+
raise BindingError(f"unknown path step {st!r}")
|
|
148
|
+
if cur is None:
|
|
149
|
+
return (None, None)
|
|
150
|
+
return ("container", cur)
|
|
151
|
+
|
|
152
|
+
def _touch(self, c: Container, d):
|
|
153
|
+
if c.module is self.mc:
|
|
154
|
+
self.rank = max(self.rank, self.cg.eff_class(self.mc, d))
|
|
155
|
+
|
|
156
|
+
def field(self, row: Container, spec: dict) -> tuple:
|
|
157
|
+
"""(C initialiser, comment) for one struct field."""
|
|
158
|
+
when = spec.get("when")
|
|
159
|
+
if when is not None:
|
|
160
|
+
kind, v = self.steps(row, when)
|
|
161
|
+
ok = kind == "value" and (v[0] or "").strip().lower() in ("true", "1")
|
|
162
|
+
if not ok:
|
|
163
|
+
return (spec.get("default", "0u"), "")
|
|
164
|
+
paths = spec["path"]
|
|
165
|
+
alts = paths if isinstance(paths, list) and paths and isinstance(paths[0], list) else [paths]
|
|
166
|
+
kind, v = None, None
|
|
167
|
+
for alt in alts: # alternatives: the first path that yields a value wins
|
|
168
|
+
kind, v = self.steps(row, alt)
|
|
169
|
+
if kind is not None and not (kind == "value" and v[0] is None):
|
|
170
|
+
break
|
|
171
|
+
default = spec.get("default")
|
|
172
|
+
fmt = spec.get("fmt")
|
|
173
|
+
if kind is None or (kind == "value" and v[0] is None):
|
|
174
|
+
if default is None:
|
|
175
|
+
raise BindingError(f"{row.path}: field {spec.get('name')} ({spec['path']}) has no value and no default")
|
|
176
|
+
return (default, "")
|
|
177
|
+
if kind == "container":
|
|
178
|
+
s = self.cg.symbol(v)
|
|
179
|
+
if s is None:
|
|
180
|
+
return (W.c_uint(self.cg.index_of(v)), v.name)
|
|
181
|
+
if self.ctx is not None:
|
|
182
|
+
s = self.ctx.xsym(v, self.mc, self.used_mips)
|
|
183
|
+
return (s, "")
|
|
184
|
+
if kind == "token":
|
|
185
|
+
if fmt:
|
|
186
|
+
return (fmt.replace("{MIP}", v.upper()).replace("{Mip}", v), "")
|
|
187
|
+
return (v, "")
|
|
188
|
+
raw, d, owner = v
|
|
189
|
+
if "enum" in spec and raw is not None:
|
|
190
|
+
m = spec["enum"]
|
|
191
|
+
if raw.strip() not in m:
|
|
192
|
+
raise BindingError(f"{row.path}: {spec['path']} literal {raw} not in the binding's enum map")
|
|
193
|
+
return (str(m[raw.strip()]), raw.strip())
|
|
194
|
+
return W.table_value(d, raw, hexa=fmt == "hex")
|
|
195
|
+
|
|
196
|
+
def macro(self, expr: str) -> str:
|
|
197
|
+
m = re.match(r"^(count|max):([\w]+)(?:/(\w+))?\s*([+-]\s*\d+)?$", expr.strip())
|
|
198
|
+
if not m:
|
|
199
|
+
raise BindingError(f"bad macro expression {expr!r} (count:<Def> | max:<Def>/<Param> [+N])")
|
|
200
|
+
op, dname, pname, off = m.groups()
|
|
201
|
+
insts = self.cg.instances(self.mc, dname)
|
|
202
|
+
if op == "count":
|
|
203
|
+
v = len(insts)
|
|
204
|
+
else:
|
|
205
|
+
vals = [parse_int(c.text(pname) or "") for c in insts]
|
|
206
|
+
vals = [x for x in vals if x is not None]
|
|
207
|
+
v = max(vals) if vals else 0
|
|
208
|
+
if off:
|
|
209
|
+
v += int(off.replace(" ", ""))
|
|
210
|
+
return W.c_uint(v)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sip-standin",
|
|
3
|
+
"description": "Struct layout of the fixture's SIP stand-in static code (fixtures/*/SIP/include). Every type, field and macro below is taken from those headers; the values come from the ECUC via the path language (see codegen/binding.py). Enumeration fields without an 'enum' map use the def literal order.",
|
|
4
|
+
"modules": {
|
|
5
|
+
"NvM": {
|
|
6
|
+
"detect": {"header": "NvM.h", "contains": "NvM_BlockDescriptorType"},
|
|
7
|
+
"sourceIncludes": ["NvM.h"],
|
|
8
|
+
"macros": [
|
|
9
|
+
{"name": "NVM_TOTAL_NUM_OF_NVRAM_BLOCKS", "value": "max:NvMBlockDescriptor/NvMNvramBlockIdentifier+1"}
|
|
10
|
+
],
|
|
11
|
+
"tables": [
|
|
12
|
+
{
|
|
13
|
+
"name": "NvM_BlockDescriptorTable", "type": "NvM_BlockDescriptorType", "qualifier": "NVM_CONST",
|
|
14
|
+
"rows": "NvMBlockDescriptor", "size": "NVM_TOTAL_NUM_OF_NVRAM_BLOCKS", "designated": true,
|
|
15
|
+
"doc": "indexed by NvMNvramBlockIdentifier (0 and 1 are reserved, SWS_NvM_00478)",
|
|
16
|
+
"fields": [
|
|
17
|
+
{"name": "RamBlockDataAddress", "path": "param:NvMRamBlockDataAddress", "default": "NULL_PTR",
|
|
18
|
+
"extern": "VAR(uint8, NVM_APPL_DATA) {}[]"},
|
|
19
|
+
{"name": "NvBlockLength", "path": "param:NvMNvBlockLength"},
|
|
20
|
+
{"name": "FeeBlockNumber", "path": [["sub:NvMTargetBlockReference", "sub:NvMFeeRef", "ref:NvMNameOfFeeBlock", "param:FeeBlockNumber"],
|
|
21
|
+
["sub:NvMTargetBlockReference", "sub:NvMEaRef", "ref:NvMNameOfEaBlock", "param:EaBlockNumber"]]},
|
|
22
|
+
{"name": "ManagementType", "path": "param:NvMBlockManagementType"},
|
|
23
|
+
{"name": "UseCrc", "path": "param:NvMBlockUseCrc"},
|
|
24
|
+
{"name": "SingleBlockCallback", "path": ["sub:NvMSingleBlockCallback", "param:NvMSingleBlockCallbackFnc"],
|
|
25
|
+
"default": "NULL_PTR",
|
|
26
|
+
"extern": "FUNC(Std_ReturnType, NVM_APPL_CODE) {}(NvM_BlockRequestType BlockRequest, NvM_RequestResultType JobResult)"}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
"Dem": {
|
|
32
|
+
"detect": {"header": "Dem.h", "contains": "Dem_EventIdType"},
|
|
33
|
+
"genericTables": true,
|
|
34
|
+
"macros": [
|
|
35
|
+
{"name": "DEM_NUMBER_OF_EVENTS", "value": "max:DemEventParameter/DemEventId"}
|
|
36
|
+
]
|
|
37
|
+
},
|
|
38
|
+
"PduR": {
|
|
39
|
+
"detect": {"header": "PduR.h", "contains": "PduR_RoutingPathType"},
|
|
40
|
+
"sourceIncludes": ["PduR.h"],
|
|
41
|
+
"macros": [
|
|
42
|
+
{"name": "PDUR_NUMBER_OF_SRC_PDUS", "value": "count:PduRSrcPdu"},
|
|
43
|
+
{"name": "PDUR_NUMBER_OF_DEST_PDUS", "value": "count:PduRDestPdu"}
|
|
44
|
+
],
|
|
45
|
+
"tables": [
|
|
46
|
+
{
|
|
47
|
+
"name": "PduR_RoutingPaths", "type": "PduR_RoutingPathType", "qualifier": "PDUR_CONST", "static": true,
|
|
48
|
+
"rows": "PduRSrcPdu", "size": "PDUR_NUMBER_OF_SRC_PDUS",
|
|
49
|
+
"doc": "indexed by PduRSourcePduHandleId", "comment": "name",
|
|
50
|
+
"fields": [
|
|
51
|
+
{"name": "DestModule", "path": ["back:PduRRoutingPath/PduRSrcPduRRef", "ref:PduRDestPduRRef", "peer:PduRDestPduRef", "module"],
|
|
52
|
+
"fmt": "PDUR_DEST_{MIP}"},
|
|
53
|
+
{"name": "DestLowerPduId", "path": ["back:PduRRoutingPath/PduRSrcPduRRef", "ref:PduRDestPduRRef", "peer:PduRDestPduRef", "sym"]},
|
|
54
|
+
{"name": "DestPduRPduId", "path": ["back:PduRRoutingPath/PduRSrcPduRRef", "ref:PduRDestPduRRef", "sym"]}
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "PduR_TxConfUpperPduId", "type": "PduIdType", "qualifier": "PDUR_CONST", "static": true, "scalar": true,
|
|
59
|
+
"rows": "PduRDestPdu", "size": "PDUR_NUMBER_OF_DEST_PDUS",
|
|
60
|
+
"doc": "indexed by PduRDestPduHandleId: the upper (Com) I-PDU to confirm when the source PDU asks for it (PduRSrcPduUpTxConf)",
|
|
61
|
+
"fields": [
|
|
62
|
+
{"name": "UpperPduId", "path": ["back:PduRRoutingPath/PduRDestPduRRef", "ref:PduRSrcPduRRef", "peer:PduRSrcPduRef@Com", "sym"],
|
|
63
|
+
"when": ["back:PduRRoutingPath/PduRDestPduRRef", "ref:PduRSrcPduRRef", "param:PduRSrcPduUpTxConf"],
|
|
64
|
+
"default": "COMSTACK_INVALID_PDUID"}
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"objects": [
|
|
69
|
+
{"name": "PduR_Config", "type": "PduR_PBConfigType", "qualifier": "PDUR_CONST",
|
|
70
|
+
"init": ["PduR_RoutingPaths", "PduR_TxConfUpperPduId", "PDUR_NUMBER_OF_SRC_PDUS", "PDUR_NUMBER_OF_DEST_PDUS"]}
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
"CanIf": {
|
|
74
|
+
"detect": {"header": "CanIf.h", "contains": "CanIf_TxPduConfigType"},
|
|
75
|
+
"sourceIncludes": ["CanIf.h"],
|
|
76
|
+
"macros": [
|
|
77
|
+
{"name": "CANIF_NUMBER_OF_TX_PDUS", "value": "count:CanIfTxPduCfg"},
|
|
78
|
+
{"name": "CANIF_NUMBER_OF_RX_PDUS", "value": "count:CanIfRxPduCfg"}
|
|
79
|
+
],
|
|
80
|
+
"tables": [
|
|
81
|
+
{
|
|
82
|
+
"name": "CanIf_TxPduConfig", "type": "CanIf_TxPduConfigType", "qualifier": "CANIF_CONST", "static": true,
|
|
83
|
+
"rows": "CanIfTxPduCfg", "size": "CANIF_NUMBER_OF_TX_PDUS", "comment": "sym",
|
|
84
|
+
"doc": "indexed by CanIfTxPduId",
|
|
85
|
+
"fields": [
|
|
86
|
+
{"name": "CanId", "path": "param:CanIfTxPduCanId", "fmt": "hex"},
|
|
87
|
+
{"name": "Dlc", "path": ["ref:CanIfTxPduRef", "param:PduLength"]},
|
|
88
|
+
{"name": "Hth", "path": ["ref:CanIfTxPduBufferRef", "ref:CanIfBufferHthRef", "ref:CanIfHthIdSymRef", "sym"]},
|
|
89
|
+
{"name": "UpperPduId", "path": ["peer:CanIfTxPduRef@PduR", "sym"]}
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"name": "CanIf_RxPduConfig", "type": "CanIf_RxPduConfigType", "qualifier": "CANIF_CONST", "static": true,
|
|
94
|
+
"rows": "CanIfRxPduCfg", "size": "CANIF_NUMBER_OF_RX_PDUS", "comment": "sym",
|
|
95
|
+
"doc": "indexed by CanIfRxPduId",
|
|
96
|
+
"fields": [
|
|
97
|
+
{"name": "CanId", "path": "param:CanIfRxPduCanId", "fmt": "hex"},
|
|
98
|
+
{"name": "CanIdMask", "path": "param:CanIfRxPduCanIdMask", "fmt": "hex"},
|
|
99
|
+
{"name": "Dlc", "path": "param:CanIfRxPduDataLength"},
|
|
100
|
+
{"name": "Hrh", "path": ["ref:CanIfRxPduHrhIdRef", "ref:CanIfHrhIdSymRef", "sym"]},
|
|
101
|
+
{"name": "UpperPduId", "path": ["peer:CanIfRxPduRef@PduR", "sym"]}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
],
|
|
105
|
+
"objects": [
|
|
106
|
+
{"name": "CanIf_Config", "type": "CanIf_ConfigType", "qualifier": "CANIF_CONST",
|
|
107
|
+
"init": ["CanIf_TxPduConfig", "CanIf_RxPduConfig", "CANIF_NUMBER_OF_TX_PDUS", "CANIF_NUMBER_OF_RX_PDUS"]}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|