@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.
Files changed (53) hide show
  1. package/README.md +11 -1
  2. package/agents/autosar-integrator.md +45 -2
  3. package/commands/autosar-trace.md +2 -2
  4. package/package.json +4 -1
  5. package/plugins/index.js +55 -23
  6. package/python/ecuc_engine/__init__.py +1 -1
  7. package/python/ecuc_engine/add_signal.py +351 -0
  8. package/python/ecuc_engine/arxml_edit.py +387 -0
  9. package/python/ecuc_engine/audit.py +352 -50
  10. package/python/ecuc_engine/cli.py +125 -5
  11. package/python/ecuc_engine/codegen/__init__.py +233 -0
  12. package/python/ecuc_engine/codegen/binding.py +305 -0
  13. package/python/ecuc_engine/codegen/bindings/sip_standin.json +378 -0
  14. package/python/ecuc_engine/codegen/cdiff.py +395 -0
  15. package/python/ecuc_engine/codegen/cwriter.py +150 -0
  16. package/python/ecuc_engine/codegen/emit.py +463 -0
  17. package/python/ecuc_engine/codegen/handles.py +176 -0
  18. package/python/ecuc_engine/codegen/mcal/tc4d.json +60 -0
  19. package/python/ecuc_engine/codegen/model.py +264 -0
  20. package/python/ecuc_engine/codegen/modules/__init__.py +7 -0
  21. package/python/ecuc_engine/codegen/modules/com.py +145 -0
  22. package/python/ecuc_engine/codegen/modules/comstack.py +14 -0
  23. package/python/ecuc_engine/codegen/modules/dem.py +22 -0
  24. package/python/ecuc_engine/codegen/modules/mcal.py +170 -0
  25. package/python/ecuc_engine/codegen/modules/memstack.py +24 -0
  26. package/python/ecuc_engine/codegen/registry.py +208 -0
  27. package/python/ecuc_engine/codegen/schm_memmap.py +159 -0
  28. package/python/ecuc_engine/context.py +481 -0
  29. package/python/ecuc_engine/csrc.py +189 -0
  30. package/python/ecuc_engine/defs.py +30 -0
  31. package/python/ecuc_engine/ecucjson.py +75 -22
  32. package/python/ecuc_engine/find.py +164 -0
  33. package/python/ecuc_engine/gen.py +105 -0
  34. package/python/ecuc_engine/history.py +146 -0
  35. package/python/ecuc_engine/model.py +4 -1
  36. package/python/ecuc_engine/probe.py +229 -0
  37. package/python/ecuc_engine/rte_api.py +935 -0
  38. package/python/ecuc_engine/rules/__init__.py +5 -3
  39. package/python/ecuc_engine/rules/mem.py +84 -0
  40. package/python/ecuc_engine/rules/nvm.py +131 -0
  41. package/python/ecuc_engine/rules/pdur.py +87 -0
  42. package/python/ecuc_engine/rules/system.py +162 -0
  43. package/python/ecuc_engine/specids.py +163 -0
  44. package/python/ecuc_engine/sysmodel.py +152 -0
  45. package/python/ecuc_engine/validate.py +15 -2
  46. package/python/ecuc_engine/values.py +26 -0
  47. package/python/ecuc_engine/verify_rte.py +445 -0
  48. package/python/pyproject.toml +1 -1
  49. package/skills/autosar/SKILL.md +78 -6
  50. package/skills/autosar/references/codegen.md +58 -0
  51. package/skills/autosar/references/ecuc-model.md +4 -1
  52. package/skills/autosar/references/project-layout.md +10 -1
  53. package/skills/autosar/references/runnable-code.md +50 -0
@@ -0,0 +1,60 @@
1
+ {
2
+ "silicon": "tc4d",
3
+ "title": "Infineon AURIX TC4Dx: Port pad configuration (register level)",
4
+ "status": "skeleton",
5
+ "statusNote": "Only public AURIX TC4Dx user manual data is in this file. It is enough to compute the GPIO direction / level / MODE=0 (GPIO) pad settings of a pin whose port and pin number are given in the project pin map. It is NOT a replacement for the Infineon MCAL: alternate-function MODE numbers per pin and mode literal, pad driver/level defaults, access-protection handling and the MCAL's own Port_ConfigType layout come from Infineon's vendor param defs (VSMD) / BSWMD and MCAL static code, which are under NDA and not used here.",
6
+ "sources": [
7
+ {"id": "UM", "doc": "AURIX TC4Dx user manual v1.1 (2025-06-26), public", "file": "tc4d/docs/infineon-infineon-aurix-tc4dx-um-v1.1-public-en_us.pdf-usermanual-en.pdf"}
8
+ ],
9
+ "registers": {
10
+ "GPIO": {"offset": "0x300", "stride": "0x10", "cite": "UM 47.4.2 Table 2432 (p. 8609) and 47.4.30 PADCFGp_GPIO (p. 8632-8633)"},
11
+ "DRVCFG": {"offset": "0x304", "stride": "0x10", "cite": "UM 47.4.2 Table 2432 (p. 8610) and 47.4.31 PADCFGp_DRVCFG (p. 8633-8637)"}
12
+ },
13
+ "fields": {
14
+ "DIR": {"reg": "DRVCFG", "lsb": 0, "width": 1, "cite": "UM p. 8634: 0B IN, 1B OUT"},
15
+ "OD": {"reg": "DRVCFG", "lsb": 1, "width": 1, "cite": "UM p. 8634: 0B push-pull, 1B open drain"},
16
+ "MODE": {"reg": "DRVCFG", "lsb": 4, "width": 4, "cite": "UM p. 8634: output: 0 = GPIO, n = alternate output function n; input: 0 tristate, 1 pull-down, 2 pull-up, 3 tristate"},
17
+ "PD": {"reg": "DRVCFG", "lsb": 8, "width": 3, "cite": "UM p. 8635: pad driver strength / edge"},
18
+ "PL": {"reg": "DRVCFG", "lsb": 12, "width": 3, "cite": "UM p. 8636: input level (AL / TTL)"},
19
+ "SET": {"reg": "GPIO", "lsb": 2, "width": 1, "cite": "UM p. 8633: write 1 drives the pin high (OUT is read-only)"},
20
+ "CLR": {"reg": "GPIO", "lsb": 3, "width": 1, "cite": "UM p. 8633: write 1 drives the pin low"}
21
+ },
22
+ "direction": {"PORT_PIN_IN": {"DIR": 0}, "PORT_PIN_OUT": {"DIR": 1}},
23
+ "level": {"PORT_PIN_LEVEL_HIGH": {"SET": 1}, "PORT_PIN_LEVEL_LOW": {"CLR": 1}},
24
+ "modes": {
25
+ "PORT_PIN_MODE_DIO": {"MODE": 0, "cite": "UM p. 8634: MODE 0 = GPIO (output) / tristate (input)"}
26
+ },
27
+ "modesNote": "Every other PortPinMode needs the alternate-function number of that pin (MODE = 1..15). Per-pin ALT numbers are listed in the TC4Dx data sheet pin tables, but the mapping from the vendor's PortPinMode literals to them is defined by Infineon's VSMD (NDA). Give them per pin in the project pin map ('modes': {'PORT_PIN_MODE_CAN': 2}).",
28
+ "ports": {
29
+ "P00": {"base": "0xF003A000", "pins": "0-15"},
30
+ "P01": {"base": "0xF003A400", "pins": "0-15"},
31
+ "P02": {"base": "0xF003A800", "pins": "0-15"},
32
+ "P03": {"base": "0xF003AC00", "pins": "0-15"},
33
+ "P04": {"base": "0xF003B000", "pins": "0-9"},
34
+ "P10": {"base": "0xF003C800", "pins": "0-11,13-15"},
35
+ "P13": {"base": "0xF003D400", "pins": "0-15"},
36
+ "P14": {"base": "0xF003D800", "pins": "0-15"},
37
+ "P15": {"base": "0xF003DC00", "pins": "0-8,10-15"},
38
+ "P16": {"base": "0xF003E000", "pins": "0-14"},
39
+ "P20": {"base": "0xF003F000", "pins": "0-3,6-14"},
40
+ "P21": {"base": "0xF003F400", "pins": "0-7"},
41
+ "P22": {"base": "0xF003F800", "pins": "0-11"},
42
+ "P23": {"base": "0xF003FC00", "pins": "0-7"},
43
+ "P25": {"base": "0xF0040400", "pins": "1"},
44
+ "P30": {"base": "0xF0041800", "pins": "0-15"},
45
+ "P31": {"base": "0xF0041C00", "pins": "6-7"},
46
+ "P32": {"base": "0xF0042000", "pins": "2,4-7"},
47
+ "P33": {"base": "0xF0042400", "pins": "0-15"},
48
+ "P34": {"base": "0xF0042800", "pins": "1-5"},
49
+ "P35": {"base": "0xF0042C00", "pins": "0-5"},
50
+ "P40": {"base": "0xF0044000", "pins": "0-14"}
51
+ },
52
+ "portsCite": "UM 47.8 Table 2448 'Registers address space - Pn' (p. 8650); available slices from Table 2446 'Port implementation in TC4DxAA-step' (p. 8645-8646)",
53
+ "needsVendorData": [
54
+ "Infineon MCAL Port/Dio vendor param defs (VSMD) and BSWMD: vendor PortPin parameters (driver strength, pad level, input select, alternate-function literals) and their config classes",
55
+ "the MCAL static code's Port_ConfigType / Dio config layout (what Port_Init actually consumes); without it only the register image below can be generated",
56
+ "per-pin alternate-function numbers for each PortPinMode literal (data sheet pin tables + the VSMD literal mapping)",
57
+ "write sequencing under the pad access protection (PADCFGp_ACCEN, ACCGRPy_*, PROTSE/Safety ENDINIT; UM 47.4.x) that the MCAL performs",
58
+ "PortPinId / DioChannelId encoding used by the Infineon MCAL (port*16+pin or otherwise); the project pin map gives port and pin explicitly instead"
59
+ ]
60
+ }
@@ -0,0 +1,264 @@
1
+ """Generator view of the ECUC model: merged containers, effective config classes, refs, symbols.
2
+
3
+ Everything here is derived from the engine's loader (`model.Project`) and the param defs
4
+ (`defs.DefIndex`); nothing is module-specific. Container order is document order (files sorted by
5
+ path, elements in file order), so every table built from it is deterministic.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ import re
10
+ from dataclasses import dataclass, field
11
+ from typing import Iterable, Optional
12
+
13
+ from .. import defs as D
14
+ from ..model import Elem, ParamVal, Project
15
+ from ..values import parse_float, parse_int
16
+
17
+ CLASS_RANK = {"PUBLISHED-INFORMATION": 0, "PRE-COMPILE": 1, "LINK": 2, "POST-BUILD": 3}
18
+ CLASS_FILE = {0: "Cfg.c", 1: "Cfg.c", 2: "Lcfg.c", 3: "PBcfg.c"}
19
+ DEFAULT_VARIANT = "VARIANT-PRE-COMPILE"
20
+
21
+ # AUTOSAR_CP_TR_ReleaseOverview R25-11, Table 1.2 "Mapping schema version to platform releases"
22
+ # (and Table 1.1 for the R4.x schemas): schema AUTOSAR_000NN -> internal release number.
23
+ SCHEMA_RELEASE = {
24
+ "00042": (4, 3, 0), "00043": (4, 3, 0), "00044": (4, 3, 1), "00045": (4, 3, 1), "00046": (4, 4, 0),
25
+ "00047": (4, 4, 0), "00048": (4, 5, 0), "00049": (4, 6, 0), "00050": (4, 7, 0), "00051": (4, 8, 0),
26
+ "00052": (4, 9, 0), "00053": (4, 10, 0), "00054": (4, 11, 0),
27
+ }
28
+
29
+
30
+ def snake(name: str) -> str:
31
+ """CamelCase -> UPPER_SNAKE (NvMDevErrorDetect -> NVM_DEV_ERROR_DETECT).
32
+
33
+ The AUTOSAR glossary terms I-PDU / N-PDU / L-PDU stay one word (ComSupportedIPduGroups ->
34
+ COM_SUPPORTED_IPDU_GROUPS), as the specifications spell them in identifiers (IPDUM, ..._IPDU_...).
35
+ """
36
+ s = re.sub(r"(?<=[a-z0-9])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])", "_", name)
37
+ s = re.sub(r"(?<![A-Za-z])([INL])_(Pdu)", r"\1\2", s)
38
+ return re.sub(r"_+", "_", s).upper()
39
+
40
+
41
+ @dataclass
42
+ class Container:
43
+ """One container value, merged across file splits (all fragments with the same AUTOSAR path)."""
44
+ path: str
45
+ name: str
46
+ defn: Optional[D.DefNode]
47
+ frags: list
48
+ module: "ModuleCfg"
49
+ parent: Optional["Container"] = None
50
+ children: list = field(default_factory=list)
51
+
52
+ @property
53
+ def def_name(self) -> str:
54
+ return self.defn.name if self.defn else (self.frags[0].def_ref or "?").rsplit("/", 1)[-1]
55
+
56
+ def values(self, name: str) -> list:
57
+ return [pv for f in self.frags for pv in f.params if pv.name == name]
58
+
59
+ def value(self, name: str) -> Optional[ParamVal]:
60
+ v = self.values(name)
61
+ return v[0] if v else None
62
+
63
+ def text(self, name: str) -> Optional[str]:
64
+ v = self.value(name)
65
+ return v.value if v is not None else None
66
+
67
+ def sub(self, def_name: str) -> list:
68
+ return [c for c in self.children if c.def_name == def_name]
69
+
70
+
71
+ @dataclass
72
+ class ModuleCfg:
73
+ name: str # def short name (the Mip unless overridden)
74
+ elem: Elem
75
+ defn: Optional[D.DefNode]
76
+ variant: str
77
+ containers: list # all Containers, document order (pre-order)
78
+ top: list # top-level Containers
79
+ mip: str = ""
80
+
81
+ @property
82
+ def MIP(self) -> str:
83
+ return self.mip.upper()
84
+
85
+
86
+ class CgModel:
87
+ def __init__(self, project: Project, mip_overrides: Optional[dict] = None):
88
+ self.p = project
89
+ self.defs = project.defs
90
+ self.mip_overrides = mip_overrides or {}
91
+ self.modules: dict[str, ModuleCfg] = {}
92
+ self.by_path: dict[str, Container] = {}
93
+ self.order: dict[str, int] = {}
94
+ self._rev: Optional[dict] = None
95
+ self._sorted: dict = {}
96
+ for m in sorted(project.modules, key=lambda e: (e.file, e.line)):
97
+ self._add_module(m)
98
+
99
+ # ---------- construction ----------
100
+ def _def(self, def_ref: Optional[str]) -> Optional[D.DefNode]:
101
+ if not def_ref:
102
+ return None
103
+ d = self.defs.get(def_ref)
104
+ if d is not None:
105
+ return d
106
+ # vendor-relocated or other-release DEFINITION-REF: match by module-relative path
107
+ rel = self.defs.rel(def_ref)
108
+ for mp, md in self.defs.modules.items():
109
+ if rel.split("/", 1)[0] == md.name:
110
+ return self.defs.get(mp + rel[len(md.name):])
111
+ return None
112
+
113
+ def _add_module(self, m: Elem):
114
+ md = self._def(m.def_ref)
115
+ name = md.name if md else (m.def_ref or m.name).rsplit("/", 1)[-1]
116
+ if name in self.modules: # split module configuration: merge into the first one
117
+ mc = self.modules[name]
118
+ else:
119
+ variant = m.node.child_text("IMPLEMENTATION-CONFIG-VARIANT") or DEFAULT_VARIANT
120
+ mc = ModuleCfg(name, m, md, variant, [], [], self.mip_overrides.get(name, name))
121
+ self.modules[name] = mc
122
+ for c in m.children:
123
+ self._add_container(mc, c, None)
124
+
125
+ def _add_container(self, mc: ModuleCfg, e: Elem, parent: Optional[Container]):
126
+ c = self.by_path.get(e.path)
127
+ if c is None:
128
+ c = Container(e.path, e.name, self._def(e.def_ref), [], mc, parent)
129
+ self.by_path[e.path] = c
130
+ self.order[e.path] = len(self.order)
131
+ mc.containers.append(c)
132
+ (parent.children if parent else mc.top).append(c)
133
+ c.frags.append(e)
134
+ for ch in e.children:
135
+ self._add_container(mc, ch, c)
136
+
137
+ # ---------- queries ----------
138
+ def by_path_index(self, c: Container) -> int:
139
+ return self.order.get(c.path, 0)
140
+
141
+ def module(self, name: str) -> Optional[ModuleCfg]:
142
+ return self.modules.get(name)
143
+
144
+ def instances(self, mc: ModuleCfg, def_name: str) -> list:
145
+ return [c for c in mc.containers if c.def_name == def_name]
146
+
147
+ def target(self, pv: Optional[ParamVal]) -> Optional[Container]:
148
+ if pv is None or not pv.value:
149
+ return None
150
+ return self.by_path.get(pv.value.strip())
151
+
152
+ def reverse(self) -> dict:
153
+ """target path -> [(Container, ref name)] for every ECUC reference value, document order."""
154
+ if self._rev is None:
155
+ self._rev = {}
156
+ for mc in self.modules.values():
157
+ for c in mc.containers:
158
+ for f in c.frags:
159
+ for pv in f.params:
160
+ if pv.is_ref and pv.value:
161
+ self._rev.setdefault(pv.value.strip(), []).append((c, pv.name))
162
+ return self._rev
163
+
164
+ def referrers(self, target: Container, module: Optional[str] = None, ref: Optional[str] = None) -> list:
165
+ out = []
166
+ for c, rname in self.reverse().get(target.path, []):
167
+ if module is not None and c.module.name != module:
168
+ continue
169
+ if ref is not None and rname != ref:
170
+ continue
171
+ out.append(c)
172
+ return out
173
+
174
+ def param_def(self, c: Container, name: str) -> Optional[D.DefNode]:
175
+ if c.defn is None:
176
+ return None
177
+ for p in c.defn.params + c.defn.refs:
178
+ if p.rsplit("/", 1)[-1] == name:
179
+ return self.defs.get(p)
180
+ return None
181
+
182
+ def eff_class(self, mc: ModuleCfg, d: Optional[D.DefNode]) -> int:
183
+ """Effective config class rank of a param/ref (or container multiplicity) for the module's variant."""
184
+ if d is None:
185
+ return CLASS_RANK["PRE-COMPILE"]
186
+ cc = d.config_classes or d.mult_classes
187
+ if not cc:
188
+ return CLASS_RANK["PRE-COMPILE"]
189
+ cls = cc.get(mc.variant)
190
+ if cls is None: # variant not listed for this param: take the least dynamic class it supports
191
+ cls = min(cc.values(), key=lambda k: CLASS_RANK.get(k, 1))
192
+ return CLASS_RANK.get(cls, 1)
193
+
194
+ def symbolic_param(self, c: Container) -> Optional[D.DefNode]:
195
+ if c.defn is None:
196
+ return None
197
+ for p in c.defn.params:
198
+ d = self.defs.get(p)
199
+ if d is not None and d.symbolic_name:
200
+ return d
201
+ return None
202
+
203
+ def symbol(self, c: Container) -> Optional[str]:
204
+ """TPS_ECUC_02108: <Mip>Conf_<ContainerDefShortName>_<ContainerValueShortName>."""
205
+ if self.symbolic_param(c) is None:
206
+ return None
207
+ return f"{c.module.mip}Conf_{c.def_name}_{c.name}"
208
+
209
+ def symbol_value(self, c: Container) -> Optional[str]:
210
+ d = self.symbolic_param(c)
211
+ return c.text(d.name) if d is not None else None
212
+
213
+ def sorted_instances(self, mc: ModuleCfg, def_name: str) -> list:
214
+ """Instances of a container def, ordered by their symbolic handle when they have one, else document order."""
215
+ key = (mc.name, def_name)
216
+ if key not in self._sorted:
217
+ insts = self.instances(mc, def_name)
218
+ if insts and self.symbolic_param(insts[0]) is not None:
219
+ def k(c):
220
+ v = parse_int(self.symbol_value(c) or "")
221
+ return (v is None, v if v is not None else 0)
222
+ insts = sorted(insts, key=k) # stable: ties stay in document order
223
+ self._sorted[key] = insts
224
+ return self._sorted[key]
225
+
226
+ def index_of(self, c: Container) -> int:
227
+ return self.sorted_instances(c.module, c.def_name).index(c)
228
+
229
+ def is_multi(self, c: Container) -> bool:
230
+ """True if this container, or any ancestor, is an instance of a def with upper multiplicity > 1."""
231
+ x: Optional[Container] = c
232
+ while x is not None:
233
+ if x.defn is None or x.defn.upper != 1:
234
+ return True
235
+ x = x.parent
236
+ return False
237
+
238
+ def release(self) -> tuple:
239
+ """AUTOSAR release of the configuration, from the schema version of its files."""
240
+ for f in sorted(self.p.files):
241
+ head = self.p.files[f].data[:2048]
242
+ m = re.search(rb"AUTOSAR_(\d{5})\.xsd", head)
243
+ if m and m.group(1).decode() in SCHEMA_RELEASE:
244
+ return SCHEMA_RELEASE[m.group(1).decode()]
245
+ return (4, 11, 0)
246
+
247
+ def rel_files(self, mc: ModuleCfg) -> list:
248
+ files = sorted({f.file for c in mc.containers for f in c.frags} | {mc.elem.file})
249
+ return [self.p.rel(f).replace("\\", "/") for f in files]
250
+
251
+
252
+ def num(v: Optional[str]) -> Optional[int]:
253
+ return parse_int(v) if v is not None else None
254
+
255
+
256
+ def flt(v: Optional[str]) -> Optional[float]:
257
+ return parse_float(v) if v is not None else None
258
+
259
+
260
+ def iter_param_defs(defs: D.DefIndex, d: D.DefNode) -> Iterable[D.DefNode]:
261
+ for p in d.params + d.refs:
262
+ x = defs.get(p)
263
+ if x is not None:
264
+ yield x
@@ -0,0 +1,7 @@
1
+ """Module generators. Every .py file here registers itself with @register("<ModuleDefName>")."""
2
+ import importlib
3
+ import os
4
+
5
+ for _f in sorted(os.listdir(os.path.dirname(__file__))):
6
+ if _f.endswith(".py") and _f != "__init__.py":
7
+ importlib.import_module(f"{__name__}.{_f[:-3]}")
@@ -0,0 +1,145 @@
1
+ """Com: Com_Cfg.h / Com_PBcfg.c.
2
+
3
+ Def-driven (generic emitter): every ComGeneral switch, the TPS_ECUC_02108 symbols of ComSignal,
4
+ ComSignalGroup, ComGroupSignal, ComIPdu and ComIPduGroup (handle IDs), and, without a static-code
5
+ binding, one table per container def (I-PDUs, signals, signal groups, group signals with bit
6
+ position / size / endianness / type / transfer property / timeouts / update bits).
7
+
8
+ The static Com's own table layout (signal and I-PDU tables, buffers, Tx-mode ticks, deadline
9
+ ticks) comes from its binding (bindings/*.json); this module only adds what neither the defs nor
10
+ the binding path language can express:
11
+ * COM_DEV_ERROR_DETECT from ComConfigurationUseDet (ECUC_Com_00141 names that output macro);
12
+ * notification callouts, resolved for the binding through `call:` steps:
13
+ - `comUserCallback`: R25-11 Com users (SWS_Com 8.6.3.1, ECUC_Com_10034/10036): the ComUserModuleCnf
14
+ referenced by ComUserModule/ComUserModuleCnfRef, its ComUserSignal / ComUserSignalGroup with the
15
+ same system signal (group) as the ComSignal (Group), and the ComUserCallback of type COM_RX_ACK
16
+ (receiver) or COM_TX_ACK (sender). These callbacks take a ComUserCbkHandleId.
17
+ - `rteSignalCallback`: the per-signal RTE callback `Rte_COMCbk_<sn>()` (receiver) /
18
+ `Rte_COMCbkTAck_<sn>()` (sender), <sn> = ComSignal short name (SWS_Rte Figure 4.42), bound only
19
+ when the RTE's callback header (Rte_Cbk.h, from the project or the output dir) declares it with
20
+ a void(void) prototype; Com_PBcfg.c then includes that header;
21
+ * the headers named by ComGeneral/ComUserCbkHeaderFile (callback prototypes) in Com_PBcfg.c;
22
+ * notes for configuration a static Com may silently not implement.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import re
27
+
28
+ from ..registry import Generator, register
29
+
30
+ RX_ACK, TX_ACK = "COM_RX_ACK", "COM_TX_ACK"
31
+
32
+
33
+ @register("Com")
34
+ class Com(Generator):
35
+ std_header = "ComStack_Types.h"
36
+
37
+ def specific(self, mo):
38
+ gen = [c for c in self.mc.containers if c.def_name == "ComGeneral"]
39
+ g = gen[0] if gen else None
40
+ det = g.text("ComConfigurationUseDet") if g is not None else None
41
+ mo.group("Development error detection (ECUC_Com_00141: ComConfigurationUseDet -> COM_DEV_ERROR_DETECT)",
42
+ [("COM_DEV_ERROR_DETECT", "STD_ON" if (det or "").strip().lower() in ("true", "1") else "STD_OFF")])
43
+ if g is not None:
44
+ for pv in g.values("ComUserCbkHeaderFile"):
45
+ h = (pv.value or "").strip().strip('"<>')
46
+ if h:
47
+ mo.include(h)
48
+ self._dir = self._directions()
49
+ self._users = self._com_user_callbacks()
50
+ self._rte_text = self.ctx.header_text("Rte_Cbk.h")
51
+ self._limits(mo)
52
+
53
+ # ---------- call: resolvers for the binding ----------
54
+
55
+ def path_steps(self, mo) -> dict:
56
+ def com_user(c, ev):
57
+ hit = self._users.get((c.path, self._ack(c)))
58
+ if hit is None:
59
+ return None
60
+ name, header = hit
61
+ if header:
62
+ mo.include(header)
63
+ return name
64
+
65
+ def rte_signal(c, ev):
66
+ if c.def_name not in ("ComSignal", "ComSignalGroup") or not self._rte_text:
67
+ return None
68
+ name = ("Rte_COMCbk_" if self._ack(c) == RX_ACK else "Rte_COMCbkTAck_") + c.name
69
+ if not re.search(rf"\b{re.escape(name)}\s*\(\s*void\s*\)", self._rte_text):
70
+ if (c.path, self._ack(c)) in self._users:
71
+ mo.notes.append(f"{c.path}: has a Com user callback ({self._users[(c.path, self._ack(c))][0]}), "
72
+ "which takes a ComUserCbkHandleId; the static code's notification type is "
73
+ "void(void), so it is not bound")
74
+ return None
75
+ mo.include("Rte_Cbk.h")
76
+ return name
77
+
78
+ return {"comUserCallback": com_user, "rteSignalCallback": rte_signal}
79
+
80
+ # ---------- helpers ----------
81
+
82
+ def _directions(self) -> dict:
83
+ """ComSignal / ComSignalGroup path -> ComIPduDirection of the I-PDU that carries it."""
84
+ out = {}
85
+ for ip in self.cg.instances(self.mc, "ComIPdu"):
86
+ d = (ip.text("ComIPduDirection") or "").strip()
87
+ for r in ("ComIPduSignalRef", "ComIPduSignalGroupRef"):
88
+ for pv in ip.values(r):
89
+ if pv.value:
90
+ out.setdefault(pv.value.strip(), d)
91
+ return out
92
+
93
+ def _ack(self, c) -> str:
94
+ return TX_ACK if self._dir.get(c.path) == "SEND" else RX_ACK
95
+
96
+ def _com_user_callbacks(self) -> dict:
97
+ """(ComSignal/ComSignalGroup path, callback type) -> (ComUserCallbackName, ComUserHeaderInclude)."""
98
+ by_sys: dict = {}
99
+ for c in self.mc.containers:
100
+ for p in ("ComSystemTemplateSystemSignalRef", "ComSystemTemplateSignalGroupRef"):
101
+ v = c.text(p)
102
+ if v and c.def_name in ("ComSignal", "ComSignalGroup"):
103
+ by_sys.setdefault(v.strip(), c)
104
+ out: dict = {}
105
+ for um in self.cg.instances(self.mc, "ComUserModule"):
106
+ cnf = self.cg.target(um.value("ComUserModuleCnfRef"))
107
+ if cnf is None:
108
+ continue
109
+ header = (cnf.text("ComUserHeaderInclude") or "").strip().strip('"<>') or None
110
+ for u in cnf.children:
111
+ sysref = u.text("ComUserSystemTemplateSystemSignalRef") or u.text("ComUserSystemTemplateSignalGroupRef")
112
+ sig = by_sys.get((sysref or "").strip())
113
+ if sig is None:
114
+ continue
115
+ for pv in u.values("ComUserCallbackRef"):
116
+ cb = self.cg.target(pv)
117
+ if cb is None:
118
+ continue
119
+ kind = (cb.text("ComUserCallbackType") or "").strip()
120
+ name = (cb.text("ComUserCallbackName") or "").strip()
121
+ if name:
122
+ out.setdefault((sig.path, kind), (name, header))
123
+ return out
124
+
125
+ def _limits(self, mo):
126
+ """Configuration that is valid ECUC but that a static Com may not implement: say so, never drop it silently."""
127
+ if mo.binding is None:
128
+ return
129
+ cnt = lambda pred: sum(1 for c in self.mc.containers if pred(c)) # noqa: E731
130
+ notes = []
131
+ n = cnt(lambda c: c.def_name == "ComIPdu" and (c.text("ComIPduSignalProcessing") or "").strip() == "DEFERRED")
132
+ if n:
133
+ notes.append(f"{n} I-PDU(s) use ComIPduSignalProcessing=DEFERRED; the binding has no field for it "
134
+ "(the static code decides when Rx notifications run)")
135
+ n = cnt(lambda c: c.def_name == "ComSignal" and (c.text("ComRxDataTimeoutAction") or "NONE").strip() != "NONE")
136
+ if n:
137
+ notes.append(f"{n} signal(s) configure ComRxDataTimeoutAction; the binding carries only the I-PDU "
138
+ "deadline (TimeoutTicks), the action is up to the static code")
139
+ n = cnt(lambda c: c.def_name == "ComTxMode" and int(float(c.text("ComTxModeNumberOfRepetitions") or 0)) > 0)
140
+ if n:
141
+ notes.append(f"{n} Tx mode(s) configure ComTxModeNumberOfRepetitions > 0; the binding has no repetition field")
142
+ n = cnt(lambda c: c.def_name == "ComTxIPdu" and float(c.text("ComMinimumDelayTime") or 0) > 0)
143
+ if n:
144
+ notes.append(f"{n} Tx I-PDU(s) configure ComMinimumDelayTime > 0; the binding has no MDT field")
145
+ mo.notes += [f"{(mo.binding or {}).get('_binding')}: {x}" for x in notes]
@@ -0,0 +1,14 @@
1
+ """PduR and CanIf. Handle IDs, routing and PDU tables are def-driven; the neighbour handles
2
+ (PduR -> CanIf/Com, CanIf -> PduR) are found through the shared EcucPduCollection/Pdu
3
+ (TPS_ECUC 3.4.1.3) by the binding path language (`peer:`). ComStack_Types.h is the base header."""
4
+ from ..registry import Generator, register
5
+
6
+
7
+ @register("PduR")
8
+ class PduR(Generator):
9
+ std_header = "ComStack_Types.h"
10
+
11
+
12
+ @register("CanIf")
13
+ class CanIf(Generator):
14
+ std_header = "ComStack_Types.h"
@@ -0,0 +1,22 @@
1
+ """Dem: event IDs and the other symbolic IDs are generic (TPS_ECUC_02108). Module-specific:
2
+ * the DTC value of every DemDTC as DEM_DTC_<NAME> (DemDtcValue is not a symbolicNameValue param,
3
+ so TPS_ECUC_02108 does not name it; this generator's convention, <MIP>_<SN> per SWS_BSW_00124);
4
+ * Dem_MainFunction, scheduled with DemGeneral/DemTaskTime (ECUC_Dem_00715)."""
5
+ from .. import cwriter as W
6
+ from ..model import snake
7
+ from ..registry import Generator, register
8
+ from ...values import parse_int
9
+
10
+
11
+ @register("Dem")
12
+ class Dem(Generator):
13
+ def specific(self, mo):
14
+ lines = []
15
+ for c in self.cg.instances(self.mc, "DemDTC"):
16
+ v = parse_int(c.text("DemDtcValue") or "")
17
+ if v is not None:
18
+ lines.append((f"DEM_DTC_{snake(c.name)}", W.c_uint(v, hexa=True), "DemDtcValue"))
19
+ mo.group("DTC values (DemDTC/DemDtcValue)", lines)
20
+ gen = [c for c in self.mc.containers if c.def_name == "DemGeneral"]
21
+ if gen and gen[0].text("DemTaskTime") is not None:
22
+ mo.main_functions = ["Dem_MainFunction"]