docs(architecture): fix FrameIO / LDF / gen_lin_api layering
The previous ASCII pipeline implied a single linear stack from gen_lin_api
down through FrameIO down through ecu_framework/lin/ldf.py — and showed
a static dependency from FrameIO to that module. Both are wrong.
What the code actually says (tests/hardware/frame_io.py:34):
from ecu_framework.lin.base import LinFrame, LinInterface
That's the only ecu_framework import in FrameIO. The `ldf` constructor
parameter is duck-typed — FrameIO never imports LdfDatabase and would
work against any object exposing `.frame(name)`. So `frame_io → lin/ldf`
is an injected runtime call, not a module dependency.
Replace the linear ASCII diagram with a Mermaid parallel-paths diagram
that surfaces the three independent ways a tester can address a frame:
- gen_lin_api typed wrapper (compile-time name check)
- FrameIO stringly-typed I/O (with raw send_raw/receive_raw escape
hatches that don't touch the ldf object at all)
- LdfDatabase used directly (schema-only — pack to bytes, no I/O)
…all converging at LinInterface. The prose around the diagram is
rewritten to match: each path's affordance, and what concrete capability
is lost by removing any of the three.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7cf74312d6
commit
ec218bd5fe
@ -181,31 +181,64 @@ have no path from a frame name to the actual byte layout for the currently
|
||||
loaded LDF — and worse, the test bench would happily ship bytes encoded
|
||||
against a *stale* LDF baked into the generator's last run.
|
||||
|
||||
Concretely, a single `fio.send` call traverses both layers:
|
||||
### Three independent entry points, one wire
|
||||
|
||||
```
|
||||
test code
|
||||
|
|
||||
| AlmReqA.send(fio, AmbLightColourRed=0, ...)
|
||||
v
|
||||
tests/hardware/_generated/lin_api.py <-- typed names, compile-time check
|
||||
|
|
||||
| fio.send("ALM_Req_A", AmbLightColourRed=0, ...)
|
||||
v
|
||||
tests/hardware/frame_io.py <-- per-instance frame cache
|
||||
|
|
||||
| ldf.frame("ALM_Req_A").pack(AmbLightColourRed=0, ...)
|
||||
v
|
||||
ecu_framework/lin/ldf.py <-- runtime pack/unpack
|
||||
|
|
||||
| raw_frame.encode_raw({...})
|
||||
v
|
||||
ldfparser <-- bit-level layout from LDF on disk
|
||||
A tester has three legitimate ways to drive the bus, all converging at
|
||||
`LinInterface`. They are **parallel paths**, not a single nested stack —
|
||||
`FrameIO` deliberately has no static dependency on `ecu_framework/lin/ldf.py`
|
||||
(its only `ecu_framework` import is `LinInterface` + `LinFrame` from
|
||||
`lin/base.py`), so the `ldf` it receives can be any object with a
|
||||
`.frame(name)` method.
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
T[test code]
|
||||
|
||||
subgraph Paths[three independent ways to address a frame]
|
||||
GEN["gen_lin_api typed wrapper<br/>AlmReqA.send(fio, ...)<br/>compile-time name check"]
|
||||
FIO["FrameIO stringly-typed<br/>fio.send('ALM_Req_A', ...)<br/>per-instance frame cache"]
|
||||
LDFDIRECT["LdfDatabase directly<br/>ldf.frame('ALM_Req_A').pack(...)<br/>returns bytes, no I/O"]
|
||||
end
|
||||
|
||||
T --> GEN
|
||||
T --> FIO
|
||||
T --> LDFDIRECT
|
||||
|
||||
GEN -.delegates.-> FIO
|
||||
FIO -.duck-typed lookup.-> LDFOBJ[ldf-like object<br/>currently LdfDatabase]
|
||||
LDFDIRECT --> LDFOBJ
|
||||
LDFOBJ --> LDFPARSER[ldfparser - bit layout]
|
||||
|
||||
FIO --> LIN[LinInterface.send / receive]
|
||||
LDFDIRECT -->|caller invokes lin.send<br/>with the packed bytes| LIN
|
||||
LIN --> WIRE[wire]
|
||||
```
|
||||
|
||||
Each layer's responsibility is unique to that layer; removing either
|
||||
collapses a distinct kind of check (compile-time name validation, or
|
||||
runtime LDF-driven byte layout) that the other layer cannot provide.
|
||||
What each path buys you:
|
||||
|
||||
- **`gen_lin_api`** — compile-time name validation. Typo a frame or signal
|
||||
name and the IDE / mypy / pytest collection rejects it before any LDF
|
||||
is read. Delegates the actual packing to `fio.send`.
|
||||
- **`FrameIO`** — stringly-typed I/O over the wire. Caches frame
|
||||
lookups, supports raw escape hatches (`send_raw` / `receive_raw`) that
|
||||
bypass the LDF object entirely.
|
||||
- **`LdfDatabase` directly** — schema-only access. Useful when a test
|
||||
wants to inspect frame layout, pack a buffer without sending, or hand
|
||||
the bytes to a non-FrameIO transport.
|
||||
|
||||
The LDF object (currently `LdfDatabase`) is consumed by both `FrameIO`
|
||||
and any direct-use code path. `FrameIO`'s use is via injection — it
|
||||
never imports `LdfDatabase` and can be tested against a stub.
|
||||
|
||||
Removing any of the three entry points collapses a distinct affordance:
|
||||
|
||||
- Drop `gen_lin_api` → tests keep stringly-typed `fio.send("ALM_Req_A", …)`
|
||||
and hand-copied state constants, both of which silently drift when the
|
||||
LDF changes.
|
||||
- Drop `FrameIO` → every test that wants high-level I/O has to wire
|
||||
`LinInterface` + LDF lookup + pack/unpack itself.
|
||||
- Drop direct `LdfDatabase` usage → tests can no longer pack a frame
|
||||
without sending it, or inspect frame metadata without an I/O attempt.
|
||||
|
||||
## Extending the architecture
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user