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
|
loaded LDF — and worse, the test bench would happily ship bytes encoded
|
||||||
against a *stale* LDF baked into the generator's last run.
|
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
|
||||||
|
|
||||||
```
|
A tester has three legitimate ways to drive the bus, all converging at
|
||||||
test code
|
`LinInterface`. They are **parallel paths**, not a single nested stack —
|
||||||
|
|
`FrameIO` deliberately has no static dependency on `ecu_framework/lin/ldf.py`
|
||||||
| AlmReqA.send(fio, AmbLightColourRed=0, ...)
|
(its only `ecu_framework` import is `LinInterface` + `LinFrame` from
|
||||||
v
|
`lin/base.py`), so the `ldf` it receives can be any object with a
|
||||||
tests/hardware/_generated/lin_api.py <-- typed names, compile-time check
|
`.frame(name)` method.
|
||||||
|
|
|
||||||
| fio.send("ALM_Req_A", AmbLightColourRed=0, ...)
|
```mermaid
|
||||||
v
|
flowchart TB
|
||||||
tests/hardware/frame_io.py <-- per-instance frame cache
|
T[test code]
|
||||||
|
|
|
||||||
| ldf.frame("ALM_Req_A").pack(AmbLightColourRed=0, ...)
|
subgraph Paths[three independent ways to address a frame]
|
||||||
v
|
GEN["gen_lin_api typed wrapper<br/>AlmReqA.send(fio, ...)<br/>compile-time name check"]
|
||||||
ecu_framework/lin/ldf.py <-- runtime pack/unpack
|
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"]
|
||||||
| raw_frame.encode_raw({...})
|
end
|
||||||
v
|
|
||||||
ldfparser <-- bit-level layout from LDF on disk
|
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
|
What each path buys you:
|
||||||
collapses a distinct kind of check (compile-time name validation, or
|
|
||||||
runtime LDF-driven byte layout) that the other layer cannot provide.
|
- **`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
|
## Extending the architecture
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user