Architecture¶
obd-diag is built in four layers. Each layer only knows the ones below it, never the ones above (ADR 0001: Layered architecture with an exchangeable transport).
flowchart TB
UI["<b>ui/, cli.py</b><br>view models, QML, commands"]
SVC["<b>services/</b><br>scan, run_diagnosis, clear_codes, run_live"]
PROT["<b>protocol/</b><br>Elm327 with allowlist, frames, headers, obd, pids"]
TRANS["<b>transport/</b><br>Transport, SerialTransport, trace"]
DATA["<b>data/</b><br>catalog, WMI"]
EXP["<b>export/</b><br>PDF, CSV"]
UI --> SVC --> PROT --> TRANS
SVC --> DATA
UI --> EXP
TRANS --> ADAPTER(["ELM327 adapter"])
Layer |
Task |
Main parts |
|---|---|---|
|
write bytes and read up to the prompt |
|
|
send commands, clean up and decode responses |
|
|
whole procedures: scan, diagnosis, clearing, live |
|
|
display and operation |
view models, QML, |
The translation module obd_diag.i18n belongs to no layer and imports nothing from the
package; every layer may use it (Language).
Rules checked by tests¶
No imports upwards.
transportimports nothing fromprotocolorservices; higher layers only know theTransportprotocol, never pyserial.One way to the adapter. Only
Elm327.commandwrites to the transport, and only commands on the allowlist (Safety concept). An architecture test checks via the AST who reaches.transport,.write(and pyserial.Exchangeable transport. Tests run against
FakeTransport, the ELM327-emulator on a pty orReplayTransportwith real traces (Testing without a car).
User interface¶
The user interface is QML with Python view models (DiagnosisViewModel,
LiveViewModel). Every job runs in a single worker thread, opens port and catalog
there and closes them again (SQLite connections are bound to their thread). Because
there is only one thread, two actions never run on the adapter at the same time. Live
data is one long job in this thread; values come back via a signal, stopping goes
through a threading.Event.