@h2loop/autosar 1.0.1 → 1.2.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 +11 -1
- package/agents/autosar-integrator.md +45 -2
- package/commands/autosar-trace.md +2 -2
- package/package.json +4 -1
- package/plugins/index.js +55 -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 +352 -50
- package/python/ecuc_engine/cli.py +125 -5
- package/python/ecuc_engine/codegen/__init__.py +233 -0
- package/python/ecuc_engine/codegen/binding.py +305 -0
- package/python/ecuc_engine/codegen/bindings/sip_standin.json +378 -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 +463 -0
- package/python/ecuc_engine/codegen/handles.py +176 -0
- package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
- package/python/ecuc_engine/codegen/model.py +264 -0
- package/python/ecuc_engine/codegen/modules/__init__.py +7 -0
- package/python/ecuc_engine/codegen/modules/com.py +145 -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 +208 -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/ecucjson.py +75 -22
- package/python/ecuc_engine/find.py +164 -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/specids.py +163 -0
- package/python/ecuc_engine/sysmodel.py +152 -0
- package/python/ecuc_engine/validate.py +15 -2
- package/python/ecuc_engine/values.py +26 -0
- package/python/ecuc_engine/verify_rte.py +445 -0
- package/python/pyproject.toml +1 -1
- package/skills/autosar/SKILL.md +78 -6
- package/skills/autosar/references/codegen.md +58 -0
- package/skills/autosar/references/ecuc-model.md +4 -1
- package/skills/autosar/references/project-layout.md +10 -1
- package/skills/autosar/references/runnable-code.md +50 -0
|
@@ -0,0 +1,305 @@
|
|
|
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>[/<Def>]] follow R to target T, then the container that references T from another
|
|
18
|
+
module (or from <Module>, optionally only containers of def <Def>): how a module
|
|
19
|
+
finds its neighbour's PDU handle (TPS_ECUC 3.4.1.3, via EcucPduCollection/Pdu)
|
|
20
|
+
call:<name> a module generator's resolver (Generator.path_steps()) for what the defs cannot
|
|
21
|
+
express as a path, e.g. Com's notification callouts (terminal, token)
|
|
22
|
+
sym TPS_ECUC_02108 symbol of the current container (terminal)
|
|
23
|
+
module | name | index the current container's module name / SHORT-NAME / table index (terminal)
|
|
24
|
+
Macro values: count:<Def> | count:@<Set> | max:<Def>/<Param>, optionally followed by +N or -N.
|
|
25
|
+
|
|
26
|
+
Module binding keys besides tables/objects/macros:
|
|
27
|
+
sets {"<Set>": {"rows": "<Def>", "where": [{"path": <path>, "in": [literal, ...]}]}}: named row
|
|
28
|
+
selections (e.g. the Tx signals: ComSignals whose I-PDU is SEND); tables use "rows": "@<Set>"
|
|
29
|
+
vars RAM objects per row that tables point to (e.g. I-PDU buffers), sized by a path
|
|
30
|
+
require [{"rows": "<Def>", "path": <path>, "in": [...], "why": "..."}]: what the static code supports;
|
|
31
|
+
a configuration outside it is an error, never silently mis-generated
|
|
32
|
+
Field keys: path, default, enum (literal -> C token), fmt ("hex" | "int" | template with {} / {MIP} / {Mip}),
|
|
33
|
+
divide (a path from the row whose value divides this one, rounded: seconds -> main-function ticks),
|
|
34
|
+
agg ({"over": <multi-ref>, "params": [...], "op": "min_nonzero" | "max", "skipIf": <param>}).
|
|
35
|
+
"""
|
|
36
|
+
from __future__ import annotations
|
|
37
|
+
|
|
38
|
+
import glob
|
|
39
|
+
import json
|
|
40
|
+
import os
|
|
41
|
+
import re
|
|
42
|
+
from typing import Optional
|
|
43
|
+
|
|
44
|
+
from ..values import parse_int
|
|
45
|
+
from . import cwriter as W
|
|
46
|
+
from .model import CgModel, Container, ModuleCfg
|
|
47
|
+
|
|
48
|
+
HERE = os.path.dirname(__file__)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class BindingError(Exception):
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def load_all(root: str) -> dict:
|
|
56
|
+
"""name -> binding dict; project bindings override bundled ones of the same name."""
|
|
57
|
+
out: dict = {}
|
|
58
|
+
for f in sorted(glob.glob(os.path.join(HERE, "bindings", "*.json"))) + sorted(
|
|
59
|
+
glob.glob(os.path.join(root, ".hydron", "codegen", "bindings", "*.json"))):
|
|
60
|
+
with open(f) as fh:
|
|
61
|
+
b = json.load(fh)
|
|
62
|
+
b["_file"] = f
|
|
63
|
+
out[b["name"]] = b
|
|
64
|
+
return out
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def select(bindings: dict, module: str, headers: dict, explicit: Optional[str]) -> Optional[dict]:
|
|
68
|
+
"""The module binding to use: the explicitly named binding, else the first whose detect header matches."""
|
|
69
|
+
if explicit == "none":
|
|
70
|
+
return None
|
|
71
|
+
names = [explicit] if explicit else sorted(bindings)
|
|
72
|
+
for n in names:
|
|
73
|
+
b = bindings.get(n)
|
|
74
|
+
if b is None:
|
|
75
|
+
raise BindingError(f"unknown binding {n!r} (known: {', '.join(sorted(bindings)) or 'none'})")
|
|
76
|
+
mb = b.get("modules", {}).get(module)
|
|
77
|
+
if mb is None:
|
|
78
|
+
continue
|
|
79
|
+
det = mb.get("detect")
|
|
80
|
+
if explicit or det is None or _detect(det, headers):
|
|
81
|
+
return dict(mb, _binding=n)
|
|
82
|
+
return None
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _detect(det: dict, headers: dict) -> bool:
|
|
86
|
+
path = headers.get(det["header"])
|
|
87
|
+
if not path:
|
|
88
|
+
return False
|
|
89
|
+
try:
|
|
90
|
+
with open(path, errors="replace") as f:
|
|
91
|
+
text = f.read()
|
|
92
|
+
except OSError:
|
|
93
|
+
return False
|
|
94
|
+
return all(t in text for t in ([det["contains"]] if isinstance(det.get("contains"), str) else det.get("contains", [])))
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class Eval:
|
|
98
|
+
"""Evaluates binding paths against one module; records touched params (config class) and used modules."""
|
|
99
|
+
|
|
100
|
+
def __init__(self, cg: CgModel, mc: ModuleCfg, ctx=None):
|
|
101
|
+
self.ctx = ctx
|
|
102
|
+
self.cg = cg
|
|
103
|
+
self.mc = mc
|
|
104
|
+
self.rank = 1
|
|
105
|
+
self.used_mips: set = set()
|
|
106
|
+
self.ext: dict = {} # call:<name> resolvers contributed by the module generator
|
|
107
|
+
|
|
108
|
+
def steps(self, start: Container, steps) -> tuple:
|
|
109
|
+
"""-> ("container", Container) | ("value", (raw, def, owner)) | ("token", str) | (None, None)"""
|
|
110
|
+
if isinstance(steps, str):
|
|
111
|
+
steps = [steps]
|
|
112
|
+
cur = start
|
|
113
|
+
for i, st in enumerate(steps):
|
|
114
|
+
op, _, arg = st.partition(":")
|
|
115
|
+
last = i == len(steps) - 1
|
|
116
|
+
if op == "param":
|
|
117
|
+
pv = cur.value(arg)
|
|
118
|
+
d = self.cg.param_def(cur, arg)
|
|
119
|
+
self._touch(cur, d)
|
|
120
|
+
if pv is not None and pv.is_ref:
|
|
121
|
+
if last:
|
|
122
|
+
return ("value", (pv.value, d, cur))
|
|
123
|
+
cur = self.cg.target(pv)
|
|
124
|
+
else:
|
|
125
|
+
return ("value", (pv.value if pv is not None else None, d, cur)) if last else (None, None)
|
|
126
|
+
elif op == "sub":
|
|
127
|
+
subs = cur.sub(arg)
|
|
128
|
+
cur = subs[0] if subs else None
|
|
129
|
+
elif op == "ref":
|
|
130
|
+
self._touch(cur, self.cg.param_def(cur, arg))
|
|
131
|
+
cur = self.cg.target(cur.value(arg))
|
|
132
|
+
elif op == "back":
|
|
133
|
+
dname, _, rname = arg.partition("/")
|
|
134
|
+
hits = [c for c in self.cg.referrers(cur, ref=rname) if c.def_name == dname]
|
|
135
|
+
cur = hits[0] if hits else None
|
|
136
|
+
if cur is not None:
|
|
137
|
+
self._touch(cur, self.cg.param_def(cur, rname))
|
|
138
|
+
elif op == "peer":
|
|
139
|
+
rname, _, want = arg.partition("@")
|
|
140
|
+
want, _, wdef = want.partition("/")
|
|
141
|
+
self._touch(cur, self.cg.param_def(cur, rname))
|
|
142
|
+
t = self.cg.target(cur.value(rname))
|
|
143
|
+
if t is None:
|
|
144
|
+
return (None, None)
|
|
145
|
+
hits = [c for c in self.cg.referrers(t) if c is not cur and
|
|
146
|
+
(c.module.name == want if want else c.module is not cur.module) and
|
|
147
|
+
(not wdef or c.def_name == wdef)]
|
|
148
|
+
hits.sort(key=lambda c: (c.module.name, self.cg.by_path_index(c)))
|
|
149
|
+
cur = hits[0] if hits else None
|
|
150
|
+
elif op == "sym":
|
|
151
|
+
s = self.cg.symbol(cur)
|
|
152
|
+
if s and self.ctx is not None:
|
|
153
|
+
s = self.ctx.xsym(cur, self.mc, self.used_mips)
|
|
154
|
+
return ("token", s) if s else (None, None)
|
|
155
|
+
elif op == "call":
|
|
156
|
+
fn = self.ext.get(arg)
|
|
157
|
+
if fn is None:
|
|
158
|
+
raise BindingError(f"path step call:{arg}: the {self.mc.name} generator provides no such resolver")
|
|
159
|
+
v = fn(cur, self)
|
|
160
|
+
return ("token", v) if v else (None, None)
|
|
161
|
+
elif op == "module":
|
|
162
|
+
return ("token", cur.module.mip)
|
|
163
|
+
elif op == "name":
|
|
164
|
+
return ("token", cur.name)
|
|
165
|
+
elif op == "index":
|
|
166
|
+
return ("token", W.c_uint(self.cg.index_of(cur)))
|
|
167
|
+
else:
|
|
168
|
+
raise BindingError(f"unknown path step {st!r}")
|
|
169
|
+
if cur is None:
|
|
170
|
+
return (None, None)
|
|
171
|
+
return ("container", cur)
|
|
172
|
+
|
|
173
|
+
def rows(self, spec: str, sets: dict) -> list:
|
|
174
|
+
"""Rows of a table: a container def (ordered by symbolic handle) or a named set "@<Set>"."""
|
|
175
|
+
if not spec.startswith("@"):
|
|
176
|
+
return list(self.cg.sorted_instances(self.mc, spec))
|
|
177
|
+
st = sets.get(spec[1:])
|
|
178
|
+
if st is None:
|
|
179
|
+
raise BindingError(f"unknown row set {spec} (sets: {', '.join(sorted(sets)) or 'none'})")
|
|
180
|
+
return [r for r in self.cg.sorted_instances(self.mc, st["rows"]) if self.matches(r, st.get("where", []))]
|
|
181
|
+
|
|
182
|
+
def raw(self, row: Container, path) -> Optional[str]:
|
|
183
|
+
kind, v = self.steps(row, path)
|
|
184
|
+
if kind == "value":
|
|
185
|
+
return (v[0] or "").strip() if v[0] is not None else None
|
|
186
|
+
if kind == "token":
|
|
187
|
+
return v
|
|
188
|
+
return None
|
|
189
|
+
|
|
190
|
+
def matches(self, row: Container, where: list) -> bool:
|
|
191
|
+
for w in where:
|
|
192
|
+
v = self.raw(row, w["path"])
|
|
193
|
+
if "in" in w and v not in w["in"]:
|
|
194
|
+
return False
|
|
195
|
+
if "notIn" in w and v in w["notIn"]:
|
|
196
|
+
return False
|
|
197
|
+
return True
|
|
198
|
+
|
|
199
|
+
def _touch(self, c: Container, d):
|
|
200
|
+
if c.module is self.mc:
|
|
201
|
+
self.rank = max(self.rank, self.cg.eff_class(self.mc, d))
|
|
202
|
+
|
|
203
|
+
def field(self, row: Container, spec: dict) -> tuple:
|
|
204
|
+
"""(C initialiser, comment) for one struct field."""
|
|
205
|
+
when = spec.get("when")
|
|
206
|
+
if when is not None:
|
|
207
|
+
kind, v = self.steps(row, when)
|
|
208
|
+
ok = kind == "value" and (v[0] or "").strip().lower() in ("true", "1")
|
|
209
|
+
if not ok:
|
|
210
|
+
return (spec.get("default", "0u"), "")
|
|
211
|
+
if "agg" in spec:
|
|
212
|
+
return self._agg(row, spec)
|
|
213
|
+
paths = spec["path"]
|
|
214
|
+
alts = paths if isinstance(paths, list) and paths and isinstance(paths[0], list) else [paths]
|
|
215
|
+
kind, v = None, None
|
|
216
|
+
for alt in alts: # alternatives: the first path that yields a value wins
|
|
217
|
+
kind, v = self.steps(row, alt)
|
|
218
|
+
if kind is not None and not (kind == "value" and v[0] is None):
|
|
219
|
+
break
|
|
220
|
+
default = spec.get("default")
|
|
221
|
+
fmt = spec.get("fmt")
|
|
222
|
+
if kind is None or (kind == "value" and v[0] is None):
|
|
223
|
+
if default is None:
|
|
224
|
+
raise BindingError(f"{row.path}: field {spec.get('name')} ({spec['path']}) has no value and no default")
|
|
225
|
+
return (default, "")
|
|
226
|
+
if kind == "container":
|
|
227
|
+
s = self.cg.symbol(v)
|
|
228
|
+
if s is None:
|
|
229
|
+
return (W.c_uint(self.cg.index_of(v)), v.name)
|
|
230
|
+
if self.ctx is not None:
|
|
231
|
+
s = self.ctx.xsym(v, self.mc, self.used_mips)
|
|
232
|
+
return (s, "")
|
|
233
|
+
if kind == "token":
|
|
234
|
+
if fmt and fmt not in ("hex", "int"):
|
|
235
|
+
return (fmt.replace("{MIP}", v.upper()).replace("{Mip}", v).replace("{}", v), "")
|
|
236
|
+
return (v, "")
|
|
237
|
+
raw, d, owner = v
|
|
238
|
+
if "enum" in spec and raw is not None:
|
|
239
|
+
m = spec["enum"]
|
|
240
|
+
if raw.strip() not in m:
|
|
241
|
+
raise BindingError(f"{row.path}: {spec['path']} = {raw.strip()} is not supported by the static code "
|
|
242
|
+
f"(the {self.mc.name} binding maps only {', '.join(m)})")
|
|
243
|
+
e = m[raw.strip()]
|
|
244
|
+
return (W.c_uint(e) if isinstance(e, int) else str(e), raw.strip())
|
|
245
|
+
if "divide" in spec:
|
|
246
|
+
return (self._ticks(row, raw, spec), "")
|
|
247
|
+
if fmt == "int": # a numeric value held in a STRING param (e.g. ComSignalInitValue)
|
|
248
|
+
n = parse_int(raw.strip())
|
|
249
|
+
if n is None:
|
|
250
|
+
raise BindingError(f"{row.path}: {spec['path']} = {raw.strip()!r} is not an integer; the static code's "
|
|
251
|
+
f"{spec.get('name')} field cannot hold it")
|
|
252
|
+
return (W.c_uint(n), "")
|
|
253
|
+
return W.table_value(d, raw, hexa=fmt == "hex")
|
|
254
|
+
|
|
255
|
+
def _ticks(self, row: Container, raw, spec: dict) -> str:
|
|
256
|
+
from ..values import parse_float
|
|
257
|
+
v = parse_float(str(raw).strip())
|
|
258
|
+
base = self.raw(row, spec["divide"])
|
|
259
|
+
b = parse_float(base) if base is not None else None
|
|
260
|
+
if v is None or not b:
|
|
261
|
+
raise BindingError(f"{row.path}: cannot convert {spec.get('name')} = {raw} to ticks of {spec['divide']} = {base}")
|
|
262
|
+
return W.c_uint(int(round(v / b)))
|
|
263
|
+
|
|
264
|
+
def _agg(self, row: Container, spec: dict) -> tuple:
|
|
265
|
+
"""Aggregate params over the targets of a multi-valued reference of the row (e.g. the smallest non-zero
|
|
266
|
+
timeout of an I-PDU's signals, SWS_Com_00291)."""
|
|
267
|
+
from ..values import parse_float
|
|
268
|
+
a = spec["agg"]
|
|
269
|
+
vals = []
|
|
270
|
+
for pv in row.values(a["over"]):
|
|
271
|
+
t = self.cg.target(pv)
|
|
272
|
+
if t is None or (a.get("skipIf") and t.value(a["skipIf"]) is not None):
|
|
273
|
+
continue
|
|
274
|
+
for p in a["params"]:
|
|
275
|
+
x = parse_float((t.text(p) or "").strip())
|
|
276
|
+
if x is not None:
|
|
277
|
+
vals.append(x)
|
|
278
|
+
if a["op"] == "min_nonzero":
|
|
279
|
+
vals = [x for x in vals if x > 0]
|
|
280
|
+
v = min(vals) if vals else None
|
|
281
|
+
elif a["op"] == "max":
|
|
282
|
+
v = max(vals) if vals else None
|
|
283
|
+
else:
|
|
284
|
+
raise BindingError(f"unknown aggregate {a['op']!r}")
|
|
285
|
+
if v is None:
|
|
286
|
+
return (spec.get("default", "0u"), "")
|
|
287
|
+
if "divide" in spec:
|
|
288
|
+
return (self._ticks(row, repr(v), spec), "")
|
|
289
|
+
return (W.c_float(repr(v)), "")
|
|
290
|
+
|
|
291
|
+
def macro(self, expr: str, sets: Optional[dict] = None) -> str:
|
|
292
|
+
m = re.match(r"^(count|max):(@?[\w]+)(?:/(\w+))?\s*([+-]\s*\d+)?$", expr.strip())
|
|
293
|
+
if not m:
|
|
294
|
+
raise BindingError(f"bad macro expression {expr!r} (count:<Def> | count:@<Set> | max:<Def>/<Param> [+N])")
|
|
295
|
+
op, dname, pname, off = m.groups()
|
|
296
|
+
insts = self.rows(dname, sets or {}) if dname.startswith("@") else self.cg.instances(self.mc, dname)
|
|
297
|
+
if op == "count":
|
|
298
|
+
v = len(insts)
|
|
299
|
+
else:
|
|
300
|
+
vals = [parse_int(c.text(pname) or "") for c in insts]
|
|
301
|
+
vals = [x for x in vals if x is not None]
|
|
302
|
+
v = max(vals) if vals else 0
|
|
303
|
+
if off:
|
|
304
|
+
v += int(off.replace(" ", ""))
|
|
305
|
+
return W.c_uint(v)
|