@h2loop/autosar 1.9.0 → 1.9.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@h2loop/autosar",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "AUTOSAR Classic pack for Hydron: ECUC read/edit-in-place/validate/diff on ARXML, config-to-code trace, generator and build runners",
5
5
  "type": "module",
6
6
  "private": false,
@@ -20,7 +20,7 @@ from typing import Optional
20
20
  from . import defs as D
21
21
  from .model import Elem, Project
22
22
  from .validate import _resolve_def
23
- from .values import int_forms, to_json
23
+ from .values import derived, int_forms, to_json
24
24
 
25
25
 
26
26
  def key_of(e: Elem) -> str:
@@ -42,6 +42,10 @@ def value_info(project: Project, e: Elem, pv) -> dict:
42
42
  nums = int_forms(pd, pv.value, pv.is_ref, numerical, pv.def_dest)
43
43
  if nums:
44
44
  out.update(nums)
45
+ if pd is not None and pd.kind == "FLOAT":
46
+ dv = derived(pd.name, pv.value)
47
+ if dv:
48
+ out["means"] = dv
45
49
  return out
46
50
 
47
51
 
@@ -49,6 +53,8 @@ def _put(out: dict, name: str, items: list, multi: bool):
49
53
  one = lambda xs: xs if multi else xs[0] # noqa: E731
50
54
  out[name] = one([x["value"] for x in items])
51
55
  out[f"@at:{name}"] = one([x["at"] for x in items])
56
+ if any("means" in x for x in items):
57
+ out[f"@means:{name}"] = one([x.get("means") for x in items])
52
58
  if any("hex" in x for x in items):
53
59
  out[f"@hex:{name}"] = one([x.get("hex") for x in items])
54
60
  if any("dec" in x and x["value"] != x["dec"] for x in items):
@@ -24,7 +24,7 @@ from typing import Optional
24
24
  from . import defs as D
25
25
  from .model import CONTAINER_VALUE, Elem, Project
26
26
  from .validate import _resolve_def, validate
27
- from .values import check_value, expected_value_tag, from_json
27
+ from .values import check_value, derived, expected_value_tag, from_json
28
28
  from .xmltree import Node, escape, line_indent
29
29
 
30
30
  CONTAINER_ORDER = ["DEFINITION-REF", "PARAMETER-VALUES", "REFERENCE-VALUES", "SUB-CONTAINERS", "VARIATION-POINT"]
@@ -412,6 +412,65 @@ def _unwrap_ref(v):
412
412
  return v
413
413
 
414
414
 
415
+ def _kids(project: Project, path: str) -> dict:
416
+ """{SHORT-NAME: child path} of a value path ('' = package level: the module configurations)."""
417
+ out = {}
418
+ for e in project.find(path):
419
+ for c in e.children:
420
+ out[c.name] = c.path
421
+ return out
422
+
423
+
424
+ def _child_def_names(project: Project, path: str) -> set:
425
+ """Container def names a container at ``path`` may hold (from its def, else from its present children)."""
426
+ names = set()
427
+ for e in project.find(path):
428
+ d = _resolve_def(project.defs, e.def_ref) if project.defs else None
429
+ if d is not None:
430
+ names |= {project.defs.by_path[x].name for x in d.containers if x in project.defs.by_path}
431
+ names |= {(c.def_ref or "").rsplit("/", 1)[-1] for c in e.children}
432
+ return names - {""}
433
+
434
+
435
+ def canonical_path(project: Project, path: str):
436
+ """Rewrite the path dialects agents write into the value path, or None.
437
+
438
+ Handles container DEF names used as path segments ('.../GptChannelConfigSet/GptChannelConfiguration/
439
+ GptChannel_Delay' -> '.../GptChannelConfigSet/GptChannel_Delay') and a missing module segment
440
+ ('/ActiveEcuC/GptDriverConfiguration' -> '/ActiveEcuC/Gpt/GptDriverConfiguration'). Unresolvable trailing
441
+ segments are kept (a parameter name, or the container an add creates). Returns (path, dropped_def) where
442
+ dropped_def is a def name dropped at the END of the path (the def of the container an add should create)."""
443
+ segs = [s for s in path.strip().strip("/").split("/") if s]
444
+ if len(segs) < 2:
445
+ return None
446
+ mods = {e.name: e.path for es in project.by_path.values() for e in es if e.parent is None}
447
+ pkg = "/" + segs[0]
448
+ if not any(p.startswith(pkg + "/") for p in mods.values()):
449
+ return None
450
+ cur, i, dropped, changed = None, 1, None, False
451
+ rest: list = []
452
+ while i < len(segs):
453
+ s = segs[i]
454
+ kids = {n: p for n, p in mods.items() if p.startswith(pkg + "/")} if cur is None else _kids(project, cur)
455
+ if s in kids:
456
+ cur, dropped = kids[s], None
457
+ elif cur is None:
458
+ owners = [mp for mp in kids.values() if s in _kids(project, mp)]
459
+ if len(owners) != 1:
460
+ return None
461
+ cur, changed = _kids(project, owners[0])[s], True
462
+ elif s in _child_def_names(project, cur):
463
+ dropped, changed = s, True
464
+ else:
465
+ rest = segs[i:]
466
+ break
467
+ i += 1
468
+ if not changed or cur is None:
469
+ return None
470
+ out = cur + ("/" + "/".join(rest) if rest else "")
471
+ return out, (dropped if not rest else None)
472
+
473
+
415
474
  def normalize_ops(project: Project, ops, notes: list):
416
475
  """Accept the shapes agents use for ECUC edits (arxml_edit's keys, SHORT-NAME given separately, refs as objects,
417
476
  params added to a container that does not exist yet) and rewrite them into ecuc_edit's own; note each rewrite."""
@@ -428,6 +487,21 @@ def normalize_ops(project: Project, ops, notes: list):
428
487
  if "path" not in op and isinstance(op.get("parent_path"), str):
429
488
  op["path"] = op.pop("parent_path")
430
489
  notes.append(f"op {i}: 'parent_path' read as 'path'")
490
+ for rk in ("refs", "references", "reference_values"):
491
+ if isinstance(op.get(rk), dict):
492
+ rv = op.pop(rk)
493
+ op["values"] = {**(op.get("values") if isinstance(op.get("values"), dict) else {}), **rv}
494
+ notes.append(f"op {i}: '{rk}' merged into values")
495
+ pth = op.get("path")
496
+ if isinstance(pth, str) and pth.startswith("/") and not project.find(pth.rstrip("/")):
497
+ cp = canonical_path(project, pth)
498
+ if cp is not None:
499
+ op["path"], ddef = cp
500
+ notes.append(f"op {i}: path {pth} read as {op['path']}")
501
+ c = op.get("container")
502
+ if ddef and op.get("op") == "add" and isinstance(c, str) and ":" not in c:
503
+ op["container"] = f"{ddef}:{c}"
504
+ notes.append(f"op {i}: container {c!r} read as {op['container']!r}")
431
505
  sn = None
432
506
  for k in ("short_name", "name", "shortName"):
433
507
  if isinstance(op.get(k), str):
@@ -560,6 +634,16 @@ def edit(project: Project, ops: list, dry_run: bool = False, force: bool = False
560
634
  try:
561
635
  ops = normalize_ops(project, ops, ed.notes)
562
636
  ed.apply(ops, force=force)
637
+ for op in ops if isinstance(ops, list) else []:
638
+ if not isinstance(op, dict):
639
+ continue
640
+ vals = dict(op.get("values") or {}) if isinstance(op.get("values"), dict) else {}
641
+ if op.get("op") == "set" and "value" in op:
642
+ vals[op.get("param") or str(op.get("path", "")).rstrip("/").rsplit("/", 1)[-1]] = op["value"]
643
+ for k, v in vals.items():
644
+ dv = derived(str(k), v)
645
+ if dv:
646
+ ed.notes.append(f"{k} = {v}: {dv}; check this matches the requirement")
563
647
  except EditError as e:
564
648
  return {"ok": False, "error": str(e), "written": False, "notes": ed.notes}
565
649
  changes = ed.diffs()
@@ -170,3 +170,26 @@ def int_forms(d: Optional[DefNode], raw: Optional[str], is_ref: bool, numerical:
170
170
  if v is None:
171
171
  return None
172
172
  return {"dec": v, "hex": hex_str(v)}
173
+
174
+
175
+ def _si(x: float, unit: str) -> str:
176
+ for f, pre in ((1e9, "G"), (1e6, "M"), (1e3, "k"), (1, ""), (1e-3, "m"), (1e-6, "\u00b5"), (1e-9, "n"), (1e-12, "p")):
177
+ if abs(x) >= f:
178
+ return f"{x / f:.4g} {pre}{unit}"
179
+ return f"{x:.4g} {unit}"
180
+
181
+
182
+ def derived(name: str, raw) -> Optional[str]:
183
+ """Human reading of a frequency / baud rate value, with its period: helps catch unit slips
184
+ (100 ns resolution is a 10 MHz tick, not 100 kHz). None when the param is not one of those."""
185
+ try:
186
+ v = float(str(raw).strip())
187
+ except (TypeError, ValueError):
188
+ return None
189
+ if v <= 0:
190
+ return None
191
+ if name.endswith("Frequency"):
192
+ return f"{_si(v, 'Hz')} = {_si(1 / v, 's')} per tick"
193
+ if name.endswith("BaudRate"): # ECUC CAN/LIN baud rates are in kbit/s
194
+ return f"{_si(v * 1e3, 'bit/s')} = {_si(1 / (v * 1e3), 's')} per bit"
195
+ return None