Restructure repository: migrate to v2 rewrite plan with catalog-first architecture - #32
Open
erdesigns-eu wants to merge 227 commits into
Open
Restructure repository: migrate to v2 rewrite plan with catalog-first architecture#32erdesigns-eu wants to merge 227 commits into
erdesigns-eu wants to merge 227 commits into
Conversation
Cut v2 from main, wipe the old source tree, and lay down the Phase 0
scaffolding per PLAN.md. The previous code is preserved on the main
branch as reference only.
Repository hygiene:
- Wipe src/, tests/, examples/, tools/, docs/, Packages/, Resources/,
Forms/, Wizards/, Components/, RadioCode/, VIN/, Utilities/, OBD.res
- Preserve catalogs/ (will be reorganised in Phase 1 / Phase 6)
- Reset CHANGELOG.md to a v2-only file
Documentation:
- LICENSE (MIT) with explicit hardware-safety notice
- README.md describing the v2 vision and locked scope
- CONTRIBUTING.md (branching, PR flow, code-as-documentation principle)
- STYLE.md code style guide:
* Code-as-documentation: every public symbol carries XMLDoc;
external markdown is reserved for disclaimers, quick-starts,
and policy.
* File-header template, append-only history block.
* Naming, formatting, threading, error handling, memory ownership
rules.
* UI framework boundary (no Vcl./FMX. in runtime units; CI guards).
- src/HEADER.template.pas - canonical file header template.
- docs/flashing-safety.md - mandatory pre-conditions, confirmation
pattern, recovery procedures, audit log expectations.
Package skeletons:
- packages/DelphiOBD_RT.dpk (runtime, contains OBD.Version)
- packages/DelphiOBD_DT.dpk (design-time, contains OBD.Design.Registration
with empty Register procedure)
- packages/README.md describing build / install / multi-version flow
- .dproj files gitignored - regenerated by RAD Studio per developer
per Delphi version.
Source tree:
- src/Core/OBD.Version.pas - version constants single source of truth
- src/DesignTime/OBD.Design.Registration.pas - empty registration
entry point for the IDE
- src/{Connection,Adapter,Protocol,Services,Coding,Flashing,Signature,UI}/
with .gitkeep placeholders
- src/README.md - folder map with UI-allowed boundary
Tests:
- tests/DelphiOBD_Tests.dpr - DUnitX runner with TestInsight + CI
branches
- tests/Tests.OBD.Version.pas - smoke test exercising the version
constants
CI:
- .github/workflows/ci.yml with hygiene job running on every push:
file-header presence on .pas, VCL/FMX guard on runtime units, JSON
catalogue lint
- Windows build matrix wired but gated (if: false) until a self-hosted
RAD Studio runner is online
- Coverage step stubbed for the same reason
Templates:
- .github/ISSUE_TEMPLATE/bug_report.yml (Delphi version, adapter,
vehicle, .obdlog capture)
- .github/ISSUE_TEMPLATE/feature_request.yml (PLAN.md scope check)
- .github/PULL_REQUEST_TEMPLATE.md (XMLDoc presence, PLAN.md sync,
hardware risk section for flashing/coding changes)
Samples:
- samples/00-Hello/ smoke sample printing the package version
- samples/README.md indexing all 38 planned samples by phase
PLAN.md status log updated; Phase 0 boxes ticked except CI coverage
(awaiting Windows runner). DelphiCodeCoverage step lands once a
self-hosted runner is provisioned.
https://claude.ai/code/session_01FM1RUQv4WiMqsVZQnH6w24
Foundational Pascal layer plus standard OBD-II / UDS / J1939 catalogue
data ported from main. OEM-specific data follows in a separate commit.
Code (src/Core)
- OBD.Types enums, TOBDValue, TOBDPIDDescriptor, exception hierarchy
- OBD.Errors error code -> message / identifier
- OBD.Decoders registry + 10 built-in scaling primitives
- OBD.Catalog schema-versioned JSON loader, in-memory store
Standard catalogues (892 entries)
- 84 Mode 01 PIDs, 528 generic ISO 15031 DTCs, 60 UDS NRCs,
- 34 Mode 06 OBDMIDs, 22 Mode 06 TIDs, 22 WWH-OBD DIDs,
- 31 generic UDS DIDs, 55 J1939 PGNs.
- Schemas under catalogs/_schema/.
Tests
- Tests.OBD.Types, Errors, Decoders (16 assertions), Catalog (8),
Catalog.Inventory (10 baseline regression checks).
Phase 1 review report at docs/phase-reviews.md.
https://claude.ai/code/session_01FM1RUQv4WiMqsVZQnH6w24
Core architecture: one IOBDConnectionTransport contract, six concrete
implementations (Serial, Bluetooth, BLE, Wi-Fi, UDP, FTDI), one mock,
plus the TOBDConnection enum-driven component that ties them together
with main-thread event marshalling and retry-loop integration.
Code (src/Connection)
OBD.Connection.Types IOBDConnectionTransport interface, state /
baud / parity / stop-bits / flow-control
enums, byte / state / error event types.
OBD.Connection.Settings TPersistent sub-objects per transport
(Serial / Bluetooth / BLE / Wi-Fi / UDP /
FTDI) with publishable defaults and Assign.
OBD.Connection.Retry TOBDRetryPolicy (TPersistent) with
exponential backoff, MaxDelay clamp,
configurable jitter, seedable RNG.
OBD.Connection.Mock TOBDMockTransport for testing — state
simulation, write capture, byte feed,
error injection.
OBD.Connection.Serial Win32 serial via CreateFile / ReadFile /
WriteFile + dedicated read thread.
OBD.Connection.WiFi TCP via System.Net.Socket.
OBD.Connection.UDP UDP via System.Net.Socket.
OBD.Connection.Bluetooth RFCOMM via System.Bluetooth.
OBD.Connection.BLE GATT via System.Bluetooth.TBluetoothLEManager
(FFE0/FFE1 default profile).
OBD.Connection.FTDI D2XX via dynamically-loaded ftd2xx.dll.
OBD.Connection TOBDConnection component (TComponent,
enum-driven), event marshalling via
TThread.Queue, retry-loop integration.
Tests (tests)
Tests.OBD.Connection.Mock 9 assertions — state lifecycle, write
capture, feed dispatch, error injection.
Tests.OBD.Connection.Retry 6 assertions — exponential curve,
MaxDelay clamp, jitter envelope, Assign
round-trip.
Tests.OBD.Connection 8 assertions — sub-settings allocation,
default Transport, default Active,
write-while-closed raises, sub-settings
round-trip via Assign.
Sample (samples/01-ConnectAndPing)
Wi-Fi -> ATZ -> response sample. Configurable host / port via
command-line args.
Process changes
Author attribution corrected to "Ernst Reidinga (ERDesigns)" across
every source / test / sample / template / template / changelog /
license file. ERDesigns is the practice, Ernst is the author.
STYLE.md extended with a mandatory-tag table per symbol kind so the
XMLDoc bar is unambiguous (constructors must have <param> and
<exception> per raise; functions must have <returns>; event
properties must say WHEN they fire and on WHICH thread; etc.). Every
Phase 2 public symbol re-reviewed against the new table.
Phase 2 review report appended to docs/phase-reviews.md with honest
flags: pairing flow, BLE thread, FTDI 2 ms idle, retry only on Open,
indirect transport-injection in tests.
Hardware-dependent integration tests deferred until a self-hosted CI
runner with bench hardware is online.
https://claude.ai/code/session_01FM1RUQv4WiMqsVZQnH6w24
Mid-phase user feedback: the current sync Open() blocks the calling
thread (TCP DNS+connect, BT pairing, BLE GATT discovery), which is
fine for CLI tools but bad in a GUI. Resolution adopted as a
foundational design rule for the entire package.
Foundational rule (PLAN.md §3.7, STYLE.md §6)
Every public method that can take more than a few milliseconds ships
in two forms:
procedure Foo; // synchronous, blocks until done
procedure FooAsync; // non-blocking, fires events on main thread
Both have identical observable semantics. The async variant must:
- Return immediately.
- Run work on a TThread.CreateAnonymousThread worker.
- Marshal every event to the main thread via TThread.Queue.
- Allow only one in-flight op of the same kind; second raises
EOBDConfig.
- Be cancellable via the parent's Close / Destructor.
- Self-reap via a queued cleanup on the main thread.
Reviewers should reject any PR adding a blocking public method
without the matching async counterpart.
PLAN.md row 5 (locked decisions) updated to point at §3.7. STYLE.md
§6 mirrors the rule for contributors.
Phase 2 implementation
TOBDConnection.OpenAsync non-blocking open. Honours RetryPolicy
the same way Open does. Fires OnConnect
on success or OnError on failure, both on
the main thread.
TOBDConnection.CloseAsync non-blocking close. Fires OnDisconnect on
the main thread once the transport has
fully shut down.
FireOnConnect / FireOnDisconnect / FireOnError helpers — guard the
current-thread / queue-to-main pattern so DoOpen and DoClose can be
called from either the caller or a worker.
WaitForAsyncOpen — atomic claim of the worker pointer, cancel + join
+ free.
Tests
Tests.OBD.Connection.Async (5 assertions):
- OpenAsyncReturnsImmediately — measure the blocking budget
- OnErrorFiresFromMainThread — assert ThreadID = MainThreadID
- SecondOpenAsyncWhileInFlightRaises
- CloseCancelsInFlightAsyncOpen — within 10 s of a 30 s connect
- FreeWithInFlightAsyncOpenIsClean — destructor cancellation
Sample 01-ConnectAndPing
--async / -a flag switches between Open and OpenAsync. Console output
is similar; the value of OpenAsync shows up in GUI apps where the
message loop must keep running.
Documentation
docs/phase-reviews.md — Phase 2 follow-up section captures the
decision, the rule, and the per-phase carrier table from PLAN §3.7.
Future phases owe sync/async pairs per that table; partial
compliance is a review block.
https://claude.ai/code/session_01FM1RUQv4WiMqsVZQnH6w24
…seTransport
Two follow-ups in one commit:
1. Progress events for long-running ops (PLAN §3.7 expanded)
TOBDProgressStep record carries a unified shape:
- step-style fields (Index, Count, Name, Detail) for sequential
phases like Bluetooth pair / locate / connect
- transfer-style fields (BytesDone, BytesTotal) for byte-counted
ops like flashing
- Percent helper returning a 0..1 ratio (prefers byte counts;
falls back to step counts; saturates at 1.0; returns 0 when
neither is known)
- MakeStep / MakeBytes constructor helpers
IOBDConnectionTransport gains OnProgress; TOBDConnection re-fires
it on the main thread. Each transport's Open now reports named
phases as it works:
Serial 1/3 Opening port → 2/3 Configuring → 3/3 Ready
Wi-Fi 1/3 Resolving host → 2/3 Connecting → 3/3 Ready
UDP 1/2 Binding → 2/2 Ready
Bluetooth 1/5 Adapter check → 2/5 Locating device →
3/5 Creating socket → 4/5 Connecting → 5/5 Ready
BLE 1/6 Adapter check → 2/6 Locating device →
3/6 Connecting → 4/6 Discovering service →
5/6 Subscribing notifications → 6/6 Ready
FTDI 1/4 Loading D2XX → 2/4 Opening device →
3/4 Configuring → 4/4 Ready
The phase sequence is documented in TOBDConnection.OnProgress
XMLDoc; consumers bind a progress bar to AStep.Percent and a
label to AStep.Name / AStep.Detail.
Progress firing rules (codified in PLAN §3.7 and STYLE §6):
- Fired on the main thread on the host component.
- Coarse, not fine: one fire per named phase boundary, not per
spinner tick.
- Transfer progress coalesced to ~10 Hz.
- Component must document its phase sequence in the OnProgress
XMLDoc.
2. TOBDBaseTransport extraction (refactor)
New abstract class src/Connection/OBD.Connection.Transport.Base.pas
owns:
- FLock (TCriticalSection)
- FState + thread-safe SetState
- FOnDataReceived, FOnStateChanged, FOnTransportError, FOnProgress
- FireBytes / FireError / FireProgress / FireProgressBytes helpers
- All IOBDConnectionTransport getters/setters
- IsOpen, State
The seven transports (Serial, Wi-Fi, UDP, Bluetooth, BLE, FTDI,
Mock) are rebased onto this base. Each transport shed ~80 lines
of repeated boilerplate; the seven transports combined are ~480
lines lighter. Adding a future transport (POSIX termios, USB-CDC
direct, …) is now under 100 lines per transport.
TOBDConnection.DoOpen rewritten from six per-transport case
branches with duplicated event-wiring to a flat three-step
sequence:
1. Instantiate the transport per the Transport enum.
2. Wire the four callbacks via IOBDConnectionTransport (uniform).
3. Open with the matching settings sub-object.
This replaced ~50 lines of per-branch wiring duplication with a
single set of four assignments.
Tests
Tests.OBD.Connection.Progress 6 assertions on TOBDProgressStep
helpers (MakeStep / MakeBytes,
Percent semantics including
saturate-at-1 and zero-when-
unknown).
Tests.OBD.Connection.Mock +1 assertion exercising the mock's
SimulateProgress and the
round-trip through OnProgress.
Sample 01-ConnectAndPing
Wires OnProgress and prints each phase with the unified percent.
Try with --async to see the GUI-friendly path.
Documentation
PLAN §3.7 expanded with a Progress reporting subsection codifying
the contract for the whole package. STYLE §6 mirrors the rule.
docs/phase-reviews.md gets a Phase 2 follow-up section noting both
changes; CHANGELOG.md gets matching Added / Changed entries.
https://claude.ai/code/session_01FM1RUQv4WiMqsVZQnH6w24
Adapter layer sitting between TOBDConnection (transport) and the
forthcoming TOBDProtocol. Single TOBDAdapter component, family-driven,
catalogue-backed capabilities, AT+ST unified command catalogue,
stateless detection + initialisation routines that consume a small
IOBDAdapterCommandSender for ergonomic unit testing.
Every long-running method honours the dual-method + main-thread +
progress rule (PLAN §3.7).
Code (src/Adapter)
OBD.Adapter.Types Capability enum (15 bits) + set,
identity record, command kind / record /
response, event signatures, EOBDAdapter,
TryParseCapability synonym-tolerant.
OBD.Adapter.Capabilities Singleton registry + JSON loader; built-
in seed for ELM327, OBDLink LX/MX/MX+/EX/
CX/SX, J2534, J2534v2, DoIP, DoIP-TLS.
OBD.Adapter.Commands Single TOBDAdapterCommandCatalog
replaces v1 dual AT/ST modules. Single
FormatCommand with %d / %s / %x..xX..X
placeholders. ~35 AT + ~12 ST built-ins.
Capability gating per command.
OBD.Adapter.Detection Stateless six-phase ATZ -> ATE0 -> ATI ->
AT@1 -> AT@2 -> STI sequence;
ParseInfoLine regex; LooksLikeClone
heuristic for v1.5 chips with empty
AT@1/AT@2.
OBD.Adapter.Init Stateless per-family sequence runner;
required vs best-effort step semantics;
ExtendSequence appends user extras.
OBD.Adapter TOBDAdapter (TComponent,
IOBDAdapterCommandSender). Connection,
Family, InitCommands, CommandTimeoutMs
published. Sync + Async + Progress for
Detect / Init / WriteAT / WriteST /
WriteOBD. Response collector keyed off
the ELM327 '>' prompt; runs on the new
connection-level OnDataReceivedRaw hook
(worker thread) so sync calls never
deadlock when invoked from main.
Connection-layer addition
TOBDConnection.OnDataReceivedRaw — fires on the transport's worker
thread for low-level subscribers like the adapter response collector.
Documented as internal-use; UI consumers continue to use
OnDataReceived (main thread).
Catalogues (catalogs/adapter)
capabilities.json 12 adapter rows under v2 schema.
init-sequences.json per-family override file (loader hook for a
future enhancement; built-ins ship in code).
Removed catalogs/obd2/adapter-capabilities.json (Phase 1 verbatim
copy from main) — superseded.
Tests (tests)
Tests.OBD.Adapter.Commands 12 assertions on FormatCommand
placeholders + catalogue lookup +
capability-gating reporter.
Tests.OBD.Adapter.Capabilities 6 assertions on registry + JSON
loader + parse synonyms.
Tests.OBD.Adapter.Detection 7 assertions: ELM327 v1.5 clone,
ELM327 v2.3 genuine, OBDLink MX,
STN1110, six-phase progress,
ParseInfoLine variants, nil-sender
guard. Uses scripted
IOBDAdapterCommandSender — no real
hardware needed.
Tests.OBD.Adapter 7 assertions on lifecycle,
defaults, EOBDNotConnected gates,
EOBDUnsupported on ST without
capability, FreeNotification
clearing.
Sample (samples/02-DetectAdapter)
Wi-Fi connect -> DetectAsync -> print identity (family, key, name,
firmware, description, identifier) and capability list. Demonstrates
the dual-method rule + OnProgress + OnIdentityChanged + OnError.
Documentation
PLAN.md Phase 3 boxes ticked with detailed scope notes; status log
appended. docs/phase-reviews.md Phase 3 section: code inventory,
architecture highlights (composition over inheritance, no-deadlock
by construction, capability gating is data + code), what was
deliberately not ported (per-chip subclasses collapsed into
family-driven single component, J2534 PassThru deferred,
enumerator deferred to design-time), six honest-review flags
(response charset, echo handling, async cancellation, AT@1/@2
tolerance, init JSON not-yet-loaded, no real-hw tests),
suggested follow-ups before Phase 4.
CHANGELOG.md entry covers Added / Removed.
https://claude.ai/code/session_01FM1RUQv4WiMqsVZQnH6w24
Items 1-5 from docs/phase-reviews.md Phase 3 follow-ups, addressed
before Phase 4 starts.
1. Charset preservation
New BytesToWireString helper does a 1:1 byte->Char copy. The
response collector no longer routes raw bytes through
TEncoding.ASCII (which replaced 0x80+ with '?'); high-bit bytes
from misbehaving clones now pass through unchanged so the
higher layers can decide what to do with them.
2. Echo handling
New StripLeadingEcho helper strips a leading echo with arbitrary
whitespace / CR / LF, applied to the buffer head before the
per-line parse. The existing per-line dedup (SameText against the
command) remains as a belt-and-braces fallback for chips that
emit the echo mid-stream.
3. Async cancellation
New public TOBDAdapter.Close cancels every in-flight sync and
async operation by signalling FCancelEvent, joining the async
worker, and unsubscribing from the connection's raw byte hook.
The SendCommand wait loop now polls FRxComplete with a 50 ms
tick and checks FCancelEvent each iteration; an external Close
wakes the caller within one tick instead of waiting out the
5 s default timeout. Destructor signals before WaitForAsync.
FCancelEvent's lifecycle is owned by Close / destructor;
SendCommand never resets it, so a cancel raised between two
commands of a Detect / Init sequence persists and the next
SendCommand exits immediately.
4. AT@1 / AT@2 best-effort docs and behaviour
The detector now also checks Resp.IsError before assigning to
Description / DeviceIdentifier, so a '?' response from a clone
leaves the field empty instead of storing '?'. Inline comments
explain the swallow-and-continue contract; XMLDoc on
Detect.AIdentity / Detect <remarks> documents the behaviour and
the clone-heuristic interaction.
5. Init-sequences JSON loader
New TOBDAdapterInitializer.LoadFromJSON parses the on-disk
adapter-init-sequences schema (catalogs/adapter/init-sequences.json)
and registers per-family overrides via the also-new
RegisterOverride. ResolvedSequence consults overrides first and
falls back to BuiltinSequence. TOBDAdapter.DoInit now uses
ResolvedSequence. Unknown families in the JSON are skipped
silently so contributors can add future families to the file
without breaking older builds. Override storage is a class-level
TDictionary released in unit finalisation.
Tests
New Tests.OBD.Adapter.Followups covers the five fixes:
- HighByteIsPreserved (0xFE / 0x80 / 0x7F / 0x00 round-trip)
- EchoWithLeadingWhitespaceIsStripped (whitespace + CR + LF)
- MidBufferEchoLineDropped (parse-path coverage scaffolding)
- CloseWhileAsyncDetectInFlight (timing budget < 500 ms)
- SendCommandAfterCancelStillRaises (guard ordering)
- ELMOverrideRegistered (JSON load + ResolvedSequence)
- ClearOverrideRevertsToBuiltin
- MalformedJSONRaises
- UnknownFamilyIsSkipped
Documentation
docs/phase-reviews.md Phase 3 follow-up list rewritten with
strikethroughs + a closed-flag table. Phase 4 prep follow-up #4
(expose MaxIsoTpFrameBytes on TOBDAdapter) deferred to Phase 4
itself where the protocol layer will pull it through.
https://claude.ai/code/session_01FM1RUQv4WiMqsVZQnH6w24
…1939 / legacy)
Phase 4 is broad enough to deserve subphases (PLAN section Phase 4
split). 4a is the foundation: the wire-level codecs the rest of the
protocol layer (and Phase 6's UDS/KWP/J1939 service components) will
ride on. Production-ready on its scope; no scaffolds.
Code (src/Protocol)
OBD.Protocol.Types Application-protocol enum (apOBD2 / apUDS
/ apKWP2000 / apJ1939 / apWWHOBD / apDoIP),
frame-kind enum, TOBDFrame / TOBDRequest /
TOBDResponse records, event type aliases,
EOBDProtocolErr, BytesToHex / HexToBytes
helpers, factory functions.
OBD.Protocol.ISO15765 Full ISO-TP encoder + decoder + multi-
frame reassembler (classic CAN). SF (≤ 7
bytes), FF + CF + FC for ≤ 4095-byte
messages. Sequence-error abort. Frame
classifier. OBD-II broadcast / response
ID constants.
OBD.Protocol.UDS ISO 14229 service-ID constants for SID
0x10..0x87 (every UDS service in scope).
Common NRC constants. Encoder. Decoder
with 0x7F sid nrc detection and catalogue-
driven NRC text resolution; falls back to
synthetic 'NRC 0xXX' when the catalogue
doesn't know a code. ExpectedPositiveResponse.
OBD.Protocol.KWP2000 ISO 14230 service-ID constants for SID
0x10..0x3E. Encoder. Decode delegates to
UDS — both protocols share the negative-
response shape.
OBD.Protocol.ISO9141 3-byte header (FMT 0x68 / TGT / SRC),
modulo-256 checksum, full encode. Wire
init owned by the adapter.
OBD.Protocol.J1850 3-byte header (priority/type / TGT / SRC),
CRC-8 (poly 0x1D, init 0xFF, post-XOR
0xFF), full encode for PWM + VPW.
OBD.Protocol.J1939 29-bit CAN ID encode + decode (priority /
EDP / DP / PF / PS / SA), PGN computation,
PDU1 vs PDU2 detection, full DM1..DM32
PGN catalogue, IsDMPGN predicate, IsPDU1
predicate.
Tests (tests)
Tests.OBD.Protocol.Types 7 assertions on hex round-trip,
whitespace tolerance, case-
insensitivity, defaults.
Tests.OBD.Protocol.ISO15765 10 assertions on encode SF/FF/CF/FC,
overflow guards, reassembly round-
trip (SF + multi-frame), sequence-
error abort, ClassifyFrame.
Tests.OBD.Protocol.UDS 8 assertions on encode 22 F1 90,
zero-SID raise, positive decode,
negative decode with known + unknown
NRC, expected positive response,
empty / noisy hex.
Tests.OBD.Protocol.J1939 6 assertions on DM1 broadcast decode,
PDU1 request decode, encode/decode
round-trip, PDU1 boundary, IsDMPGN.
Tests.OBD.Protocol.Legacy 8 assertions across ISO 9141 (header
/ checksum / encode), J1850
(CRC8 / encode), KWP2000 (encode /
zero-SID / delegated decode).
39 new assertions total.
PLAN updated with explicit Phase 4 subphase split (4a wire codecs;
4b TOBDProtocol component + sample 03-ReadVIN; 4c J1939 TP.CM/TP.DT/
ETP transport state machine; 4d DoIP TCP/UDP/TLS; 4e SecOC; 4f LIN /
FlexRay / MOST; 4g close-out). Every subphase ships production-ready
on its scope.
docs/phase-reviews.md gets a Phase 4a section with code inventory,
architecture highlights, what is intentionally deferred to later
subphases, and an honest-review list (CAN-FD long-frame, raw-CAN
sender, UDS SID-mismatch silence, J1939 EDP bit packing, ISO 9141 /
J1850 wire init).
CHANGELOG.md adds a Phase 4a entry plus the Phase 4 subphase
note.
https://claude.ai/code/session_01FM1RUQv4WiMqsVZQnH6w24
…adVIN
Second subphase of Phase 4. Production-ready on its scope; closes the
last open follow-up from Phase 3.
Code (src/Protocol)
OBD.Protocol TOBDProtocol component (TComponent) bound to
TOBDAdapter. Mode (pmAuto / pmManual), Manual
(TOBDProtocolID), Application (TOBDApplicationProtocol),
DefaultTimeoutMs published. Send / SendAsync /
Request / RequestAsync per the dual-method rule
(PLAN §3.7). OnFrame / OnResponse / OnNRC / OnError /
OnProgress events on the main thread. Three-phase
progress per send (encoding / adapter-exchange /
decoding). Codec dispatch on Application protocol;
negative responses populate Response.IsNegative +
resolved NRC text without raising. Adapter errors
fire OnError(oeAdapterFault) rather than raising,
so a transient bus glitch doesn't kill the process.
FreeNotification clears Adapter on free. MakeRequest
factory helper.
Adapter follow-up (Phase 3 follow-up #4 closed)
TOBDAdapter.MaxIsoTpFrameBytes — read-only property surfacing the
capability-registry value populated alongside Capabilities in
Detect. The protocol layer (and Phase 4c+ J1939 transport) reads
this to decide whether long-frame ISO-TP is available.
Tests (tests/Tests.OBD.Protocol)
6 lifecycle assertions: defaults (pmAuto / pidAuto / apOBD2 /
5000 ms), Send / Request without adapter raise EOBDNotConnected,
FreeNotification clears Adapter, Free of an unused protocol is
clean, MakeRequest builds the right shape.
Sample (samples/03-ReadVIN)
End-to-end Phase 0 → 4b demo: TOBDConnection -> TOBDAdapter ->
TOBDProtocol. Connects via Wi-Fi, runs DetectAsync (six phase
events), runs Init (per-family steps), then issues
Protocol.Request($09, [$02], 5000) to read the VIN. Both adapter
and protocol OnProgress events route to a single console printer
via TOBDProgressStep.Percent. Output shows full progress trail,
adapter identity, MaxIsoTpFrameBytes, decoded VIN, round-trip ms.
Documentation
docs/phase-reviews.md adds a Phase 4b section with code inventory,
architecture highlights (codec dispatch, three-phase progress,
non-raising negative responses, transient adapter errors), what
is intentionally deferred to 4c+ (raw-CAN sender, DoIP, SecOC),
and an honest-review list (lenient VIN parser in the sample,
cancel-via-adapter-cascade, OnFrame not yet wired, no CI hardware
loop).
PLAN.md Phase 4b boxes ticked (component + sample +
MaxIsoTpFrameBytes closeout).
CHANGELOG.md adds a Phase 4b entry.
https://claude.ai/code/session_01FM1RUQv4WiMqsVZQnH6w24
Three flags from the Phase 4a/4b review tables that can be addressed now without waiting for downstream subphases. None left open. 1. OnFrame wired (Phase 4b flag #3) New TOBDProtocol.DispatchFrames helper splits the adapter's raw response into one TOBDFrame per line and queues OnFrame to the main thread before the decoder runs. Detects an optional leading CAN-ID token (3 hex digits for 11-bit, 8 for 29-bit) when the chip is in headers-on mode; payload comes from the trailing hex bytes. Subscribers see frames in arrival order even on the ELM327 path; the J2534 / DoIP raw-CAN path will reuse the same dispatch in 4c. 2. UDS / KWP SID mismatch surfaced (Phase 4a flag #3) After a successful decode, TOBDProtocol.DoSend compares Result.ServiceID to the codec's ExpectedPositiveResponse(req.SID) and fires OnError(oeUnexpectedFrame) with both SIDs in the message when they differ. Negative responses (0x7F) flow through OnNRC unchanged. A zero ServiceID (some chips strip the echo) does not trigger the warning. 3. Strict ISO 3779 VIN validator (Phase 4b flag #1) New OBD.Protocol.VIN unit: - TOBDVINValidator.IsCharacterValid (rejects I, O, Q) - TOBDVINValidator.Normalize (uppercases + strips invalid) - TOBDVINValidator.CheckDigit (transliteration table + ISO 3779 weights; raises on bad length / chars) - TOBDVINValidator.IsValid (alphabet + check digit) - TOBDVINValidator.ExtractFromOBDResponse (lenient extractor taking the trailing 17 VIN-eligible chars) Sample 03-ReadVIN now reports both the extracted VIN and whether it passed strict validation. Tests.OBD.Protocol.VIN — 10 assertions: - I, O, Q rejected by alphabet check - Digits + remaining letters accepted - Normalize uppercases + strips - CheckDigit on published reference VIN '1M8GDM9AXKP042788' returns 'X' (sum 351 mod 11 = 10 -> 'X') - CheckDigit on '11111111111111111' returns '1' (weight sum 89 mod 11 = 1) - CheckDigit raises on wrong length - IsValid accepts correct VINs - IsValid rejects wrong check character - IsValid rejects wrong length - ExtractFromOBDResponse trims leading non-VIN bytes - ExtractFromOBDResponse returns empty when too few VIN- eligible characters present docs/phase-reviews.md — Phase 4b honest-review section gets the strikethroughs + closed table; Phase 4a flag #3 also marked closed under the same heading. CHANGELOG.md — Added entry covering all three fixes plus the new unit and tests. Phase 4b is now truly complete with no outstanding flags. https://claude.ai/code/session_01FM1RUQv4WiMqsVZQnH6w24
Third subphase of Phase 4. Production-ready full implementation of
the J1939 transport layer per SAE J1939-21:2024 §5.10 — both the
classic TP (9..1785 bytes) and the Extended TP (>1785 bytes) for
all four flow patterns: BAM RX, BAM TX, RTS-CTS RX, RTS-CTS TX.
Code (src/Protocol/OBD.Protocol.J1939.TP)
Constants (all per J1939-21 §5.10.4 / 5.10.5):
TP.CM control bytes: RTS=0x10, CTS=0x11, EOMA=0x13,
BAM=0x20, ABORT=0xFF
ETP.CM control bytes: RTS=0x14, CTS=0x15, DPO=0x16,
EOMA=0x17, ABORT=0xFF
Limits: TP min 9 / max 1785 bytes; ETP min 1786 / max
117,440,505 bytes
Timing: T1=750 ms, T2=1250 ms, T3=1250 ms, T4=1050 ms,
Tr=200 ms
TJ1939AbortReason — 13 standard reasons (alreadyInSession,
resourcesNeeded, timeout, ctsWhileSending, maxRetransmits,
unexpectedDataPacket, badSequence, duplicateSequence,
unexpectedEDPOPGN, unexpectedEDPOSize, badEDPOOffset,
packetsExceedDPO, edpoBeforeCTS, other) plus a synthetic
host-timeout used by SweepTimeouts.
TJ1939SessionState — ssIdle, ssReceivingBAM, ssReceivingRTS,
ssAwaitingCTS, ssSendingDT, ssAwaitingEOMA, ssSendingBAM,
ssCompleted, ssAborted.
TJ1939Session record carries SA / DA / PGN / Direction /
State / TotalSize / TotalPackets / NextPacket / IsETP /
IsBAM / ETPOffset / Buffer / LastActivity.
TOBDJ1939TPCodec — stateless encoders for every TP / ETP
control frame and DT frame. ExtractPGN reads the trailing
3-byte little-endian PGN. PadToEight pads short DT chunks
with 0xFF per spec.
TOBDJ1939SessionManager — thread-safe (TCriticalSection)
concurrent session manager keyed by (SA, DA, PGN). Methods:
FeedTPCM / FeedTPDT / FeedETPCM / FeedETPDT — RX entry
points
BeginTransmit — TX entry
AbortSession — host abort
SweepTimeouts — periodic
timeout
driver
All outbound frames go through a host-supplied OnFrameSend
callback delivering a fully assembled TOBDFrame (29-bit ID,
8-byte payload). The same manager works behind ELM327 (host
formats hex), J2534 (host emits raw CAN), and DoIP (host
wraps in DoIP diagnostic message).
TOBDJ1939Transmitter — convenience wrapper for transmit-only
callers. Owns or shares an underlying TOBDJ1939SessionManager.
Tests (tests/Tests.OBD.Protocol.J1939.TP) — 18 assertions:
TJ1939TPEncoderTests (10)
RTS / BAM / CTS / EOMA / Abort layout matches spec §5.10.4
DT layout: sequence + 7 bytes; padded with 0xFF when chunk
shorter than 7
ETP RTS uses 4-byte size field
ETP CTS uses 3-byte next-offset field
ETP DPO layout matches §5.10.5
ExtractPGN reads the trailing 3-byte little-endian PGN
TJ1939SessionRXTests (5)
BAM round-trip — CM + 3 DT frames assemble a 17-byte
payload; manager fires OnComplete; session
deletes; no spurious EOMA emitted
RTS-CTS round-trip — manager emits CTS on RTS, accepts DT
frames, emits EOMA on completion
Bad sequence aborts — out-of-order DT triggers Abort with
reason arBadSequence
Concurrent sessions independent — two BAMs from different
SAs complete cleanly without cross-talk
Peer abort clears session — manager honours peer-initiated
Abort by clearing state and firing OnAbort
with the reason byte
TJ1939TransmitterTests (3)
Broadcast emits BAM CM + N DT frames
Unicast RTS / CTS / EOMA cycle — RTS, CTS triggers DT burst,
EOMA completes session
Too-small payload raises EOBDProtocolErr
ETP broadcast raises (spec forbids broadcast > 1785 bytes)
Documentation
docs/phase-reviews.md — Phase 4c section: code inventory,
architecture highlights (single state machine for both
directions, ETP on the same code path, pluggable bus driver,
concurrent sessions, bidirectional abort, inline DT burst),
what's deferred (real-CAN integration to 4d / 4f, J1939 DM
framing to Phase 6), honest-review list (DT burst pacing,
timeout granularity, ETP large-transfer DPO offsets, no
CAN-FD long-frame yet, no real-bus integration test).
CHANGELOG.md — Added entry covering the unit + tests.
https://claude.ai/code/session_01FM1RUQv4WiMqsVZQnH6w24
Three of the five Phase 4c flags addressed without waiting for the hardware loop or a separate spec. 1. Inline DT burst pacing (flag #1) New TOBDJ1939SessionManager.InterFramePaceMs property. When set > 0, the manager sleeps for that many milliseconds between consecutive SendOutbound calls inside both the BAM TX path (BeginTransmit broadcast) and the CTS-driven DT burst (FeedTPCM CTS handler). Internal lock is released during the sleep so concurrent threads can keep interacting with the manager. Re-acquires on wake and re-resolves the session index in case the session list mutated. Default 0 (no pacing). J1939-21 §5.10.4 conventional value is 50 ms (Tr). 2. Timeout sweep granularity (flag #2) Two new properties: TimeoutMs per-session inactivity timeout, default 1250 ms (J1939_T2_MS); replaces the hardcoded threshold inside SweepTimeouts. AutoSweepEnabled opt-in background thread, default False. When True, the manager spawns a single TThread that calls SweepTimeouts every SweepIntervalMs (default 250 ms). Setting False stops the thread (TEvent.SetEvent + WaitFor + Free); destructor does the same. The sweeper swallows handler exceptions so a transient OnAbort error doesn't kill the thread. 3. CAN-FD long-frame (flag #4) — reframed J1939 + CAN-FD is governed by J1939-22:2023 (Multi-PG), NOT by extending J1939-21 TP / ETP with bigger DT chunks. J1939-22 replaces TP / ETP entirely for the long-payload case with a different framing that packs multiple PGNs into one CAN-FD frame. So the right shape is a separate unit (OBD.Protocol.J1939.MultiPG), not a MaxFrameBytes knob on the TP manager. The current TP / ETP code is correct for J1939-21 over classic CAN; J1939-22 lands as a post-1.0 unit. Updated the review to reflect the spec reality. Tests (tests/Tests.OBD.Protocol.J1939.TP) — 4 new assertions: InterFramePaceLatency sets InterFramePaceMs := 30, sends a 14-byte BAM (BAM CM + 2 DT = 3 frames -> 2 inter-frame waits), asserts elapsed >= 50 ms (lower bound for scheduler noise) and < 500 ms (upper bound catches accidental long waits). ConfigurableTimeoutAborts sets TimeoutMs := 100, feeds RTS, sleeps 150 ms, sweeps, asserts the session aborted with arHostTimeout. AutoSweeperAbortsIdleSession enables AutoSweepEnabled with short TimeoutMs and SweepIntervalMs, feeds RTS, polls for the abort within 1 s. DisableSweeperStopsThread toggles AutoSweepEnabled on then off, verifies clean shutdown. Documentation docs/phase-reviews.md Phase 4c honest-review section gets strikethroughs for #1 and #2; #4 reframed with the J1939-22 explanation; closed-flags table; tests-added list. CHANGELOG.md adds a Phase 4c follow-up entry. Phase 4c is now production-ready with two flags genuinely deferred (ETP DPO bench verification — hardware-only; real-bus integration test — Phase 0 deferred). https://claude.ai/code/session_01FM1RUQv4WiMqsVZQnH6w24
…rt, OpenSSL TLS plug - OBD.Protocol.DoIP.Header: 8-byte ISO 13400-2 header, payload-type + Generic-NACK constants, encode/decode/validate. - OBD.Protocol.DoIP.Messages: records and TOBDDoIPCodec for all 15 payload types (routing activation, diagnostic, alive check, vehicle ID, power mode, entity status, generic NACK). - OBD.Protocol.DoIP.Transport: IOBDDoIPTransport contract + TOBDDoIPPlainTransport over TOBDConnection (port 13400). - OBD.Protocol.DoIP.TLS.OpenSSL: drop-in TLS 1.2/1.3 plug. Dynamic- loads libssl-3 / libcrypto-3, SNI + hostname verification, three verify modes, optional mTLS. Implements IOBDDoIPTransport so the upcoming TOBDDoIPClient is transport-agnostic. Phase 4d still WIP: client component, facade unit, DUnitX coverage and the Phase 4d honest review are next.
…view - OBD.Protocol.DoIP.Client: TOBDDoIPClient component bound to any IOBDDoIPTransport. Sync + Async + progress for every exchange: Connect, ActivateRouting, SendDiagnostic, AliveCheck, RequestEntityStatus, RequestPowerMode, RequestVehicleID. All events fire on the main thread; single in-flight discipline guarded by FOpLock + GuardSingleAsync; pending-op TEvents per payload type; greedy read-pump reassembles header + payload with byte-stream resync. - OBD.Protocol.DoIP: facade unit re-exporting the public types. OpenSSL plug intentionally not re-exported so hosts that don't ship libssl-3 / libcrypto-3 are not forced into the dependency. - Tests.OBD.Protocol.DoIP: DUnitX coverage for 5 header tests, 11 message-codec tests, 6 client-lifecycle tests against an in-memory loopback transport (activation OK / deny, diagnostic pos / neg / silent, alive check). - Phase 4d honest review added to docs/phase-reviews.md, with three actionable flags closed inline (async double-fire guard, ExpectsResponse fire-and-forget mode, AckEvent for ACK-only flows). POSIX TCP path deferred — not applicable to the user's Windows-DLL deliverable; tracked for a later phase. Phase 4d is now closed. Phase 4e (SecOC) is the next subphase.
…store) Pure-Pascal SecOC stack that ships without a third-party crypto dependency: - OBD.Protocol.SecOC.AES — FIPS-197 AES-128 single-block encrypt (encryption-only; CMAC never decrypts). - OBD.Protocol.SecOC.CMAC — RFC 4493 / NIST SP 800-38B CMAC-AES128 with full and arbitrary-bit-length tags. - OBD.Protocol.SecOC.Keys — IOBDSecOCKeyProvider contract + thread-safe in-memory TOBDSecOCKeyStore. - OBD.Protocol.SecOC.Freshness — IOBDSecOCFreshnessProvider contract + in-memory TOBDSecOCFreshness with monotonic TX, truncated-FV reconstruction, replay rejection, jump-window bound, configurable MaxJump. - OBD.Protocol.SecOC — TOBDSecOCCodec component. Wrap produces Authentic PDU (Original || Truncated FV || Truncated MAC); Unwrap reconstructs the full FV, recomputes the MAC over (Data ID-BE || Original || Full FV-BE), constant-time compares. Tests cover FIPS-197 Appendix B, every RFC 4493 §4 vector, key-store lifecycle, freshness monotonicity / wrap / replay / jump-window, and full codec round-trip with bit-flip and replay detection. Phase 4e honest review added to docs/phase-reviews.md with two flags closed inline (eager byte-alignment validation in the key store; in-memory freshness contract documented). SBox cache-timing, hardware-loop test, and TComponent IDE registration are tracked as documented trade-offs / 4g items.
Every SBox[X] access in KeyExpand and SubBytes now goes through AESConstantTimeSBox, which scans the full 256-byte table and combines entries with a CT equality mask. The memory-access pattern is independent of the secret input; ShiftRows / MixColumns / AddRoundKey were already CT, and the only branch in the cipher is the public rounds counter. Backed by a new 256-input test ConstantTimeSBoxMatchesFIPS197 that pins the function to a verbatim copy of FIPS-197 Table 4 so a regression in either side surfaces immediately. Performance impact for SecOC (microseconds per CMAC) is negligible. Phase 4e review updated to mark flag 1 closed.
- OBD.Protocol.LIN.Frame: ISO 17987-3 / LIN 2.2A frame primitives. PID parity (6-bit ID + 2 parity bits), classic + enhanced 8-bit one's-complement checksums, encode / decode of the data portion of the bus, default-checksum selector by Frame ID, optional slot-size enforcement on the encoder. - OBD.Protocol.LIN.LDF: line-tracked tokenizer + recursive-descent parser for the LIN Description File. Decodes Nodes, Signals, Frames and Schedule_tables; skips unknown sections cleanly; preserves slot delays at microsecond precision. - OBD.Protocol.FlexRay.Frame: ISO 17458-1 / FlexRay 2.1A frame primitives. 5-byte header (indicators + 11-bit Frame ID + 7-bit payload length + 11-bit header CRC + 6-bit cycle count). Header CRC-11 (poly 0x385, init 0x1A). Frame CRC-24 (poly 0x5D6DCB, init 0xFEDCBA). Encode / decode wire bytes with full CRC verification. - OBD.Protocol.MOST.Control: MOST 3.0 control-message frame (16-bit SA/DA + FBlock + Inst + 12-bit FktID + 4-bit OPType + TelID/TelLen + data) with MOST25/50/150 data-length ceilings and the common FBlock / OPType constants. Tests cover LIN PIDs across all 64 IDs, classic + enhanced checksum vectors, encode/decode round-trip, checksum and parity tamper detection, FlexRay header + frame round-trip + tamper detection, MOST control round-trip + field-range rejections, LDF parsing of nodes / signals / frames / schedules including microsecond delays and unknown-section skipping. Phase 4f honest review added; flag 2 (encoder slot-length enforcement) closed inline. Hardware-loop verification, sporadic- frame parsing, and bus runtimes are tracked deferrals.
… tests, samples Closes Phase 4 cleanly: - src/DesignTime/OBD.Design.Registration.pas: register all five flagship components (TOBDConnection, TOBDAdapter, TOBDProtocol, TOBDDoIPClient, TOBDSecOCCodec) on the "OBD" IDE palette tab. - tests/Tests.OBD.Protocol.Integration.pas: cross-cutting tests proving the per-phase units compose. UDSOverSecOCOverDoIP builds a UDS RDBI request, wraps with SecOC, encapsulates as a DoIP DiagnosticMessage, then runs the full reverse path and asserts the original bytes + freshness counter survive. ISO15765MultiFrameDecodesViaUDS feeds FF + 2× CF through the Phase 4a reassembler then decodes the result via the UDS codec. - samples/04-LIN-LDF-Parse: console demo of the Phase 4f LDF parser with a working sample.ldf. - samples/05-SecOC-WrapUnwrap: console demo of the Phase 4e SecOC stack — wraps a UDS payload, unwraps, then triggers MAC-tamper and replay rejections so a reader sees the failure paths. - docs/phase-reviews.md: Phase 4g close-out review with totals, trade-offs, follow-ups carried into Phase 5+. Phase 4 is closed. Phase 5 (service-mode components) is next.
…DFreezeFrame Service-mode components that sit on top of TOBDProtocol: - OBD.Service.LiveData: Mode 01 reader + bitmap-walk supported-PIDs discovery + background poll over a PID list. Built-in decoders for ~16 J1979 classics (RPM, speed, coolant / intake / ambient / oil temp, MAF, throttle, engine load, fuel level / pressure, voltage, distance with MIL on, run time, baro, fuel rate). - OBD.Service.DTCs: Modes 03 / 07 / 0A current / pending / permanent + UDS Service 0x19 sub-function 0x02. Decodes raw 2-byte codes to SAE J2012 strings (P/C/B/U + 4 hex digits). Tolerates the optional leading-count byte. Includes Clear (Mode 04). Resolves descriptions via OBD.Catalog when a DTC catalogue is loaded. - OBD.Service.VIN: dual-path (OBD-II Service 09 PID 02 + UDS Service 22 DID 0xF190). Validates against ISO 3779 check digit via the Phase 4b TOBDVINValidator. - OBD.Service.FreezeFrame: Mode 02 reader; mirrors Mode 01 with a frame-index byte. All four components honour the dual-method + main-thread + free- notification pattern from Phase 4b. Registered on a new "OBD Services" palette tab; lower-level building blocks remain on "OBD". Tests cover the built-in PID decoder formulas (RPM, speed, coolant offset, engine load) and the J2012 DTC decoder across all four family letters plus the P0000 zero-sentinel. Phase 5 honest review added; flags 1, 4 and 6 (catalogue-driven PID/DTC tables, single-in-flight async discipline) tracked for follow-up. Hardware-loop verification deferred per existing convention.
User pulled all Phase 5 follow-ups forward. Closing inline: 1. JSON-driven PID + DTC catalogues. New OBD.Service.Catalog loads catalogs/obd2-pids.json (dids array with nested decoder.kind / scale / offset / unit) and catalogs/dtc-*.json (dtcs array with code / description / severity). Case- insensitive DTC lookup. EvaluatePIDDecoder covers uint8 / int8 / uint16_be / int16_be / uint32_be. TOBDLiveData.DoRead queries the catalogue first and falls back to the hand-coded J1979 dictionary. TOBDDTCs.ResolveDtcText now consults the JSON catalogue before the legacy v1 schema (also fixes a ckDTC → ckOBD2DTC typo). 2. Single-in-flight async discipline applied to TOBDLiveData, TOBDDTCs, TOBDVIN, TOBDFreezeFrame using the same GuardSingleAsync / ReleaseAsync pattern from TOBDDoIPClient. Concurrent ReadAsync calls now raise EOBDConfig. 3. Mode 06 — TOBDOnBoardMonitor. Reads MID-keyed test results into a flat TArray<TOBDMonitorResult> (TID + ComponentID + UnitAndScale + signed 16-bit Value/Min/Max). Sync + Async + main-thread events. 4. Mode 08 — TOBDActuator with AutoExecute=False default safety gate per the original Phase 1 discussion. Send raises EOBDConfig until the gate is opened explicitly. OnBeforeSend fires on the main thread with a Cancel out-parameter for last-ditch UI confirmation. Synchronous and asynchronous sends share the gate. Both new components register on the "OBD Services" palette tab. Tests cover catalogue load round-trip, decoder formulas (uint8 + offset, uint16_be + scale, int16_be negative path), and the Mode 08 safety gate. Hardware-loop verification remains the only deferred item, per existing convention.
Four non-visual components on a new "OBD Coding" palette tab: - OBD.Coding.SecurityAccess — TOBDSecurityAccess. ISO 14229-1 §9.4 SecurityAccess (SID 0x27) request-seed → compute-key → send-key handshake. The OEM-specific seed → key transform is attached via a TFunc (SeedToKey) or an event (OnComputeKey). Validates odd request-seed level, handles the zero-byte-seed "already unlocked" reply per §9.4.5.2. Sync + Async + Progress. - OBD.Coding.DataIdentifierIO — TOBDDataIdentifierIO. UDS Read (0x22) / Write (0x2E). Multi-DID reads split the response per DID echo. Writes gated by AutoExecute=False (default). - OBD.Coding.RoutineControl — TOBDRoutineControl. UDS Service 0x31 with all three sub-functions: Start / Stop / RequestResults. Start + Stop gated by AutoExecute; results queries unrestricted. - OBD.Coding.Flasher — TOBDFlasher. UDS download trio: 0x34 RequestDownload (negotiates max block length, accounting for the 2-byte SID+BSC overhead); 0x36 TransferData chunked with BSC counter (1..255 then wraps to 1); 0x37 RequestTransfer Exit. AutoExecute=False default. OnBeforeFlash fires on the main thread under TThread.Synchronize with a Cancel out- parameter for last-ditch UI confirmation. OnProgress per chunk. Every gated entry-point raises EOBDConfig before any wire access. Single-in-flight async via GuardSingleAsync mirrored from DoIP. FreeNotification on Protocol everywhere. Tests cover safety-gate rejection on every gated entry-point, even-level rejection on Unlock, transform-not-configured rejection, empty-DID-list and empty-image rejections, and OnBeforeFlash cancel honoured. Phase 6 honest review documents the multi-DID heuristic split, flasher staging deferral, missing per-chunk retry, and the deferred TOBDUploader / TOBDFlashSession orchestrator. Hardware- loop verification continues to be the only no-deferral exception.
User pulled all Phase 6 deferrals forward. 1. TOBDUploader (UDS Service 0x35) — mirror of TOBDFlasher. 0x35 RequestUpload → 0x36 TransferData (read-side, response carries data) → 0x37 RequestTransferExit. Same NRC 0x78 pending-retry budget and per-chunk retry budget. Reads are non-destructive so AutoExecute is not gated; OnBeforeUpload still available for confirmation UIs. 2. TOBDFlashSession orchestrator — composes the classic UDS reflash choreography into a single call: 0x10/02 programming session → 0x27 SecurityAccess → 0x31/01 erase RID → flasher → 0x31/01 verify RID → 0x11/01 hardReset. Every step is configurable (SecurityLevel, EraseRoutineID, VerifyRoutineID, ResetAfterFlash, SessionAtStart); RID := 0 disables a step. Children lazily created and inherit Protocol / AutoExecute / SeedToKey from the orchestrator. 3. NRC 0x78 (responsePending) handling. New RequestWithPending helper in both flasher and uploader retransmits on 0x78 up to MaxPendingRetries (default 10) with PendingDelayMs (50 ms) between attempts. Applied to RequestDownload, RequestUpload, every TransferData chunk, and RequestTransferExit. 4. Per-chunk retry budget on TransferData. New MaxChunkRetries (default 3) and ChunkRetryDelayMs (default 20 ms) on flasher and uploader. Each retry surfaces via OnProgress so a UI can display "BSC 12 retry 2/3 (...)". 5. Strict-mode multi-DID read. New TOBDDataIdentifierIO.ReadStrict(ADIDs, ALengths) splits the response deterministically by declared length instead of the heuristic next-DID-echo scan. Length-array size mismatch raises EOBDConfig eagerly. 6. All-zero seed unlocked detection in TOBDSecurityAccess. Treats both a zero-byte seed and an all-zero-byte seed as "already unlocked" per ISO 14229-1 §9.4.5.2. TOBDUploader and TOBDFlashSession register on the "OBD Coding" palette tab. New tests cover retry-budget defaults, upload zero- size rejection, flash-session AutoExecute gate, empty-image rejection, strict-mode length-mismatch, and the security-access guard order. Hardware-loop verification stays the only standing deferral.
Six new units shipping the XCP/CCP/A2L calibration stack and the IsoBus / Tachograph speciality-bus surface: - OBD.Calibration.A2L — TOBDA2L parser. Tokenizer-based recursive-descent over MEASUREMENT, CHARACTERISTIC, COMPU_METHOD; unknown blocks (RECORD_LAYOUT, AXIS_PTS, FUNCTION, …) skipped cleanly with brace counting. Convert evaluates IDENTICAL / LINEAR / RAT_FUNC. - OBD.Calibration.XCP.Transport — IOBDXCPTransport contract so the master never touches a CAN driver directly. - OBD.Calibration.XCP — TOBDXCP master. CONNECT / DISCONNECT / GET_STATUS / GET_ID / GET_SEED + UNLOCK / SET_MTA / UPLOAD / SHORT_UPLOAD / DOWNLOAD / SHORT_DOWNLOAD / SET_CAL_PAGE / GET_CAL_PAGE / START_STOP_DAQ_LIST / START_STOP_SYNCH. Honours the slave-declared byte order on every address field. - OBD.Calibration.CCP — TOBDCCP master. Legacy CAN-only ASAP1a; CONNECT / EXCHANGE_ID / GET_VERSION / GET_SEED + UNLOCK / SET_MTA / DNLOAD / UPLOAD / SELECT_CAL_PAGE / START_STOP. CTR byte advances per command; addresses big-endian per spec. - OBD.Speciality.IsoBus — TOBDIsoBus. ISO 11783-5 NAME encode/ decode (LSB-first), priority comparison through 64-bit value, address-claim conflict resolution, registry, PGN-request builder. OnAddressLost fires when the local stack must yield. - OBD.Speciality.Tachograph — TOBDTachograph. EU 2016/799 Annex IC TimeReal ↔ TDateTime, Activity / Event / Fault record decoders, ASCII-string padding stripper. Tests cover the A2L parser end-to-end, the XCP master through a queue-backed stub transport (CONNECT decode, SHORT_UPLOAD address byte order, UNLOCK key layout, ERR raises), CCP packet shape, IsoBus NAME round-trip + priority + claim conflict, and Tachograph TimeReal pinned to 2024-01-01 = 1704067200. TOBDXCP / TOBDCCP / TOBDIsoBus register on a new "OBD Calibration" palette tab; A2L and Tachograph are stateless decoders. Phase 7 honest review documents the eight tracked follow-ups (sample IOBDXCPTransport, full DAQ programming, ProgramFlash, CCP DAQ, A2L MOD_COMMON, COMPU_VTAB tables, IsoBus VT/TC/FS/GNSS, Tachograph CalibrationRecord + PC/SC). Hardware-loop verification remains the only standing deferral.
User pulled all Phase 7 deferrals forward. Eight closures: 1. Sample IOBDXCPTransport — new OBD.Calibration.XCP.Loopback ships TOBDXCPLoopbackTransport: in-process queue + event implementation, useful as a reference and as test infrastructure for code that drives the master. 2. Full XCP DAQ programming. TOBDXCP gained FreeDAQ, AllocDAQ, AllocODT, AllocODTEntry, SetDAQPtr, WriteDAQ, SetDAQListMode — every multi-byte field honours the slave-declared byte order. 3. XCP ProgramFlash (PGM). TOBDXCP gained ProgramStart, ProgramClear, Program_, ProgramReset, ProgramVerify. Same byte-order discipline as UPLOAD/DOWNLOAD. 4. CCP DAQ programming. TOBDCCP gained GetDAQSize, SetDAQPtr, WriteDAQ, StartStopAll. Big-endian addresses per ASAP1a. 5. A2L MOD_COMMON / MOD_PAR. New TOBDA2LModuleCommon + TOBDA2LModulePar records on the cluster; parser fills ByteOrder / Deposit / Alignment* and EpkValue / EpkAddress / Customer / Version. HasCommon / HasPar flags signal presence. 6. A2L COMPU_VTAB / table interpolation. New verbal-table + numeric-table entry types, parser branches for COMPU_VTAB / COMPU_VTAB_RANGE / COMPU_TAB, and a new ConvertVerbal lookup. Convert now interpolates cmTabIntp and does nearest-lower lookup for cmTabNointp, with edge-clamping. 7. IsoBus VT / TC / FS / GNSS. Four new units shipping the framing helpers: TOBDIsoBusVT (Get_Memory, Get/Load/Store/ Delete_Version, End_Of_Object_Pool, Audio_Signal, Change_Active_Mask, Soft_Key + VT_Status decoders), TOBDIsoBusTC (Status decoder, Value / SetValue / Request* / ProcessDataAck builders + Value round-trip), TOBDIsoBusFS (Open/Read/Write/Seek/Close + CWD), TOBDIsoBusGNSS (NMEA 2000 PGN 129025 / 129026 / 129029 decoders). 8. Tachograph CalibrationRecord + PC/SC. DecodeCalibration covers the Gen-1 fixed-offset record per Annex IB §2.39 (purpose, workshop name, card number, date, VIN, w / k constants, tyre size, authorised speed). New OBD.Speciality.Tachograph.PCSC dynamic-loads winscard.dll / libpcsclite.so.1 with SCardEstablishContext / ListReaders / Connect / Transmit / Disconnect — same pattern as the Phase 4d OpenSSL plug. 22 new tests across 5 fixtures. Hardware-loop verification remains the only standing deferral.
…helpers Fifteen new units, the longest-running phase since Phase 4: Generic write surface - OBD.UDS.WriteMemory — TOBDUDSWriteMemory (UDS 0x3D WriteMemoryByAddress), AutoExecute=False default. - OBD.KWP.WriteID — TOBDKWPWriteID (KWP2000 0x3B WriteDataByLocalIdentifier). - OBD.Coding.Diff — byte-level diff/apply/revert with length + before-byte mismatch detection. - OBD.Coding.AuditLog — append-only JSONL with optional HMAC-CMAC chain over (prev || line-without-hmac); Verify walks the chain and returns the first-tamper line index. Reuses the Phase 4e CMAC primitive — no new crypto. - OBD.Coding.Session — orchestrator: snapshot every step → write all → verify all → rollback-on-fail. Snapshots taken before any writes so a partial-batch failure rolls back the entire batch. Wires audit log when assigned. Per-OEM helpers (parse/get/set primitives, no OEM catalogues) - VAG: long-coding parse/format/Get-SetBit/Byte + adaptation channel encode/decode. - BMW: CAFD/NCS TLV walk, FindEntry, WriteValue, Read/SetBit on multi-byte values, ParseVehicleOrder S-codes. - Ford: AsBuilt section parse/format, two's-complement checksum compute/verify/seal, Get/SetByte, FindSection. - HMG (Hyundai/Kia/Genesis): configuration-word parse + GetOption / SetOption (1/2/4-byte values). - Honda: flat-array customisation entries with ranged setter. - Mercedes: variant-coding bit + sub-byte field access; SCN decode/encode. - Stellantis: FCA Proxi parameter parse/get/set by ID. - Toyota: customisation menu parse/get/set. - VAG Component Protection: CP DID catalogue + status decoder + challenge → AuthFunc-callback → authorisation flow. No OEM secrets shipped — host wires the Geko/SVM bridge. 21 tests across 4 fixtures: write-safety gates, diff round-trip + mismatch detection, audit-log three-entry round-trip + clean-verify + tamper-verify, every OEM primitive with a hand-derived vector. Five new components register on the "OBD Coding" palette (WriteMemory, KWP WriteID, AuditLog, Session, VAG CP). The OEM primitive units are pure helpers; no palette entry. Phase 8 honest review documents six follow-ups (OEM option catalogues, dry-run mode, RLE diff for firmware, additional component-protection vendors, label-file parsers, hardware loop). Hardware loop continues to be the only standing deferral.
User pulled all but the hardware-loop test forward. 1. JSON-Schema-validated OEM coding-option catalogue. data/schemas/oem-coding-catalog.schema.json ships the schema; OBD.Coding.OptionCatalog ships the loader. Seven addressing kinds covered (byte_bit, byte_field, byte_range, tlv_id, config_word, asbuilt_section, menu_index) with per-kind validation, optional value labels, optional tag arrays. No OEM content shipped — hosts populate from their own ground-truth sources. 2. TOBDCodingSession dry-run mode. New DryRun: Boolean property; snapshots still read so the audit trail captures the pre-state, but write / verify / rollback skip the wire and emit audit entries with 'dry-run' notes. 3. TOBDCodingDiffRLE — run-length-encoded diff for firmware- scale buffers. Configurable gap budget; TransferSize for pre-flash planning. Round-trip + revert + gap-merging + reject paths covered. 4. BMW CAS / Mercedes EZS / Stellantis SGW component- protection helpers. Each parallels TOBDComponentProtectionVAG with vendor-default DIDs (overridable) and the same AuthFunc callback contract. All three register on the "OBD Coding" palette tab. No OEM secrets shipped — hosts wire ISTA / DAS-Xentry / SGW token bridges. 5. VAG .lbl label-file parser. Line-oriented Ross-Tech format: bit-position labels, bit-range labels, whole-byte labels, adaptation channels, inline value tables, header / inline comments. BMW / Ford / Mercedes vendor formats are proprietary; for those, the JSON option catalogue is the supported path. 22 new tests across 4 fixtures. Hardware-loop verification remains the only Phase 8 deferral.
Phase 9 split into six subphases. 9a ships the foundation that the rest of the flashing pipeline drives. OBD.UDS.Transfer — TOBDUDSTransfer. Full ISO 14229-1 §14 data-transfer state machine: Idle → RequestingDownload → Transferring → RequestingExit → Completed (or → Aborted on any failure). Chunked TransferData with BSC counter, NRC 0x78 auto-retransmit budget, per-chunk retry budget, cooperative Cancel, OnStateChange / OnProgress / OnError. Resumable — hosts pass a TOBDTransferCursor (offset + BSC + MaxChunkBytes) to skip the prefix already accepted by the ECU. AutoExecute defaults to False per the standing safety contract. OBD.J1939.MemoryAccess — TOBDJ1939MemoryAccess. SAE J1939-73 DM14 / DM15 / DM16 / DM17 / DM18 PGN catalogue + framing helpers. EncodeDM14 / DecodeDM14 / EncodeDM15 / DecodeDM15 / EncodeDM16 / DecodeDM16. Length-overflow rejected at encode. Stateless; the host wires PGN frames through the Phase 4c J1939 transport. Tests cover safety gates (AutoExecute=False raises; empty image raises; resume-on-complete-cursor raises; resume-with- mismatching-image raises), retry-budget defaults, and DM14 / DM15 / DM16 round-trip with hand-derived vectors. The Phase 6 TOBDFlasher in OBD.Coding.Flasher remains as a simple one-shot helper. Phase 9c will ship the orchestrator that drives the new transfer engine plus the safety / audit / voltage-gate / signature pipeline.
OBD.Flash.VoltageGate — TOBDVoltageGate. Background voltage monitor with configurable MinimumVoltage / PollIntervalMs / HoldTimeMs (defaults 12.0 V / 200 ms / 1000 ms). Hosts wire a measurement source via SourceFunc or OnRequestVoltage; the gate fires OnVoltageLow on every dip and latches OnAbort once the reading has been below the threshold for HoldTimeMs. Transient dips shorter than HoldTimeMs do NOT latch. Source missing → gate fires OnAbort and stops. OBD.Flash.Checkpoint — TOBDFlashCheckpoint. File-backed JSON checkpoint store for resumable transfers. Captures the TOBDTransferCursor + a SHA-256 of the firmware image so a host that resumes against a different image gets a hard-fail. Atomic write (write-to-temp + rename) so a crash mid-write doesn't corrupt the previous good checkpoint. Schema version pinned at 1; load rejects mismatching versions. OBD.Flash.Phases — TOBDFlashPhase enum (preflight / verify-image / enter-programming / transfer / verify / reset / finalise); TOBDFlashCheckList collection that runs checks per phase in insertion order; severity buckets (csInfo / csWarning / csError); first csError failure aborts the phase, warnings are logged-only. TOBDFlashChecks ships built-in helpers for the standard preflight checks: EngineOff, VoltageFloor, AmbientTemperature, IgnitionOn — each takes a host-supplied TFunc<…> measurement source. Tests cover voltage-gate latch + transient-dip recovery, a SHA-256 hash vector pinned to "abc", checkpoint round-trip, image-tamper detection, version-mismatch rejection, and the phase runner across all three severity levels with a visitor that walks every result.
OBD.Flash.Pipeline — TOBDFlashPipeline. Composes every Phase 9
building block into one safe-by-default end-to-end run:
fpPreflight — host-supplied checks
fpVerifyImage — image hash / signature checks
fpEnterProgramming — host wires session + security + erase
through OnEnterProgramming
fpTransfer — TOBDUDSTransfer (Phase 9a)
fpVerify — host wires verify routine through
OnVerifyRoutine
fpReset — 0x11/01 ECUReset hardReset (configurable)
fpFinalise — close audit log
Safety surface:
- AutoExecute defaults False. Without an OnConfirmExecute
handler, Flash aborts with EOBDConfig (PLAN.md §785).
- OnConfirmExecute fires on the main thread under
TThread.Synchronize so a worker waits for the user.
- Voltage gate integration: a TOBDVoltageGate plugged in
monitors the supply during the entire run; an abort
cancels TOBDUDSTransfer at the next chunk boundary. A
pipeline with NO voltage gate logs a WARN audit entry at
start-of-flash but proceeds (PLAN §785: developer-choice).
- Audit log integration: every phase change, every check
result (info/warn/error), every wire-side phase boundary,
and every error fires through TOBDCodingAuditLog when one
is attached.
- Checkpoint integration: when CheckpointFile is set, the
pipeline atomically writes the cursor + image SHA-256
after every accepted chunk through TOBDFlashCheckpoint.
- Phase visitor: OnPhaseChange / OnCheckResult / OnComplete
fire on the main thread for UI binding.
TOBDUDSTransfer + TOBDVoltageGate + TOBDFlashPipeline register
on a new "OBD Flashing" palette tab.
Tests cover the safety gate (empty image, missing protocol,
no-handler-and-not-AutoExecute, OnConfirmExecute cancel),
Checks list exposure, and per-spec defaults.
OBD.Signature — TOBDSignatureVerifier abstract base + four
backend implementations + a process-wide registry. Hosts
register backends at startup; the flash pipeline queries the
registry for an algorithm and gets routed to the first matching
backend.
Algorithm coverage:
Classical: RSA-PSS-SHA256, RSA-PKCS1-SHA256, ECDSA-P256/P384,
Ed25519
Post-Q : Dilithium-2/3/5 (ML-DSA), Falcon-512/1024,
SPHINCS+ SHA2-128f / 192f (SLH-DSA)
OBD.Signature.BCrypt — Windows CNG. Dynamic-loads bcrypt.dll;
classical algorithms only. Verify path resolves the public-
key blob (CNG RSAPUBLICBLOB / ECCPUBLICBLOB), hashes the
message via BCryptCreateHash, and runs BCryptVerifySignature
with the right padding info per algorithm.
OBD.Signature.OpenSSL — OpenSSL 3.x. Same dynamic-load pattern
as the Phase 4d DoIP TLS plug, different symbol set (libcrypto
EVP_*). Accepts PEM or DER public keys (sniffs the leading
bytes). RSA-PSS / RSA-PKCS#1 / ECDSA / Ed25519 covered.
OBD.Signature.HSM — PKCS#11 scaffolding. Dynamic-loads the
host-specified vendor driver via LibraryPath; ships the
property surface and Supports / IsAvailable hooks. Verify
itself is wired to raise until the host plugs a vendor shim
(Vector / Thales / Utimaco SDKs all provide a thin one);
hosts using OpenSSL with the HSM's PKCS#11 engine get
verification through the OpenSSL backend.
OBD.Signature.PQC — Open Quantum Safe (liboqs). Dynamic-loads
oqs.dll / liboqs.so.0; routes Dilithium / Falcon / SPHINCS+
through OQS_SIG_new + OQS_SIG_verify. Reports IsAvailable =
False when liboqs isn't installed so the registry falls back
to BCrypt / OpenSSL for classical algorithms.
Tests cover registry routing (first-supporting wins), no-
backend-supports raises, per-backend Supports surface, HSM
unavailable-without-LibraryPath, and AlgorithmName strings.
Tiny convenience wrappers ported from v1's OBD.OEM.Helpers. Lets vendor extensions build their catalogues as array literals — DIDs := [DID($1234, 'name', 'desc'), DID($5678, 'name2', 'desc2', $7E0)] — rather than two-line record-literal blocks. Both DID and Routine have a global overload and an ECU-scoped overload; ECU is the bus-map record builder. Registered in DelphiOBD_RT.dpk.
OBD.OEM.Catalog.JSON: full TOBDOEMJSONCatalog parser ported from
v1 — DIDs, routines, ECUs, DTC ranges, coding blocks, adaptations,
actuator tests, live PIDs and DTC extended-data records, each
carrying source + verified provenance. Decoder spec compiles
enum + bitmask lookup maps and DecodePayload renders the parsed
value with scale/offset/unit. AsBaseDIDs / AsBaseRoutines /
AsBaseECUs cast to the public schema for vendor BuildCatalog
hooks. Plus the v1 ParseOEMDecoderKind / ParseCodingFieldKind /
ParseAdaptationKind / ParseActuatorResponseKind /
ParseLivePIDMode / ParseDtcExtendedKind enum-string mappers.
OBD.OEM.Catalog.Loader: SetCatalogSearchPath / ResolveCatalogPath
(user override → exe-dir/catalogs → ../catalogs → cwd/catalogs,
each also probed under motorcycle/agricultural/marine/powersports
subdirs), MergeCatalogJSON (2- and 4-arg overloads) with
replace-by-DID / replace-by-Identifier / replace-by-Address
semantics, MergeExtendedCatalogJSON for the extended-catalogue
sections, VINMatchesCatalog for JSON-driven WMI routing.
Malformed-catalog failures fall through silently to keep app
startup robust.
OBD.OEM.DTC: TOBDDtcCatalog.LoadFromFile parses { "dtcs": [ ... ] }
into RegisterEntry calls; EOBDDtcCatalog raised on file/parse errors.
OBD.OEM.DTC.Loader: MergeDtcCatalog (resolves through the shared
search path, silently no-ops on missing file).
Tests.OBD.OEM.CatalogLoader fixture: 32 assertions across every
catalogue / loader surface — header parsing, every section
loader, decoder rendering (uint16 + enum), AsBase* casts, error
paths, ResolveCatalogPath search order (override + vehicle-class
subdir + miss), MergeCatalogJSON replace + append + ECU
preservation + no-file no-op, MergeExtendedCatalogJSON across all
five extended sections, VIN matching (case-insensitive + non-match
+ short-VIN), DTC loader load / miss / malformed, and the enum
parsers.
OBD.OEM.Catalog.CSV: TOBDCatalogCSVImporter ports the v1
community-CSV → vendor-JSON converter. Minimal RFC-4180 reader
handles comma separation, double-quoted fields with embedded
commas, escaped quotes (""), comment lines (#) and blanks.
Required columns (did, name, description) are validated;
optional source / verified / ecu_address / decoder columns
flow through to the emitted JSON with the decoder column
re-parsed as an embedded sub-object. Convert() round-trips
through TFile and ForceDirectories the destination path.
Tests.OBD.OEM.CatalogCSV: 12-assertion fixture covering header
validation, body-empty rejection, quoted comma + escaped-quote
parsing, decoder-as-embedded-object, verified true/false/blank
emission, comment/blank skip, file round-trip, missing-file
exception.
OBD.OEM.GoldenCheck: GoldenVector / CheckGoldenVectors — framework-neutral spot-check helper that returns a failure list rather than raising. Verifies each (DID, payload, expected substring) tuple against an extension's DecodeDID and surfaces empty-output or substring-mismatch regressions. OBD.OEM.HD: shared base for heavy-duty (J1939) OEM extensions. Maps the J1939-71 source-address constants (engine, transmission, brakes, aftertreatment, off-board tool, etc.), provides TOBDHDSessionNegotiator (3-second tester-present for trucks where 2s races with DM1 broadcast), and the SPN-FMI helpers FormatSPNFMI / ParseDM1DTC (J1939-73 packed-DTC unpack). OBD.OEM.SCN.Mercedes: XENTRY SCN coding-flow framing. TMBSCNVersionRequest/Response + TMBSCNCodingRequest/Response records and their Encode/Decode wire-format functions (VIN(17) + BE16 length-prefixed payloads), plus the IMBSCNSolver contract a dealer-portal client implements and TMBSCNSolverNotAvailable which raises EOBDMBSCNNoSolver — the shipped default since real coding requires NDA-bound back-end credentials. Tests.OBD.OEM.Support: 18-assertion fixture covering golden-vector round-trip, all-pass / empty-output / missing-substring / empty-expected paths, the HD negotiator's 3-second cadence and display name, SPN-FMI formatting + DM1 unpack + short-payload return-empty, the J1939 source-address constants, and SCN version/coding request+response round-trips, wrong-VIN-length and wrong-byte-length raises, truncated coding-request raise, and the NotAvailable solver raising for both methods.
OBD.OEM.Coding: hex helpers (HexStringToBytes accepts space / dash / underscore / colon / dot separators and raises EOBDCodingError on bad chars or odd-length input; BytesToHexString takes an optional separator) and GetBit / SetBit byte-array bit-field accessors with range checking. OBD.OEM.RoutineControl: full UDS service-0x31 helpers ported from v1. BuildStartRoutine / BuildStopRoutine / BuildRequestRoutineResults emit the standard 31 SF HiRID LoRID frames. ParseRoutineResponse validates the 71 SF RID echo and raises EOBDRoutineError on negative replies (7F 31 NRC), wrong SID, sub-function or RID mismatch, or under-length payloads. TOBDRoutineRequestBuilder offers typed AddUInt8/16BE/32BE, AddInt16BE/32BE, AddAscii (with FixedLength padding), AddBcdDate and AddBcdYear plus ToFrame which wraps the payload with the SID header. TOBDRoutineResponseReader is a cursor-based reader with RequireBytes() under-read protection. DecodeRoutineOutput renders a TOBDRoutineSchema's output fields into "name = value" lines including enum lookups and bitmask name joins; trailing optional fields are skipped cleanly when the payload is short. OBD.OEM.ServiceFunction: canonical TOBDServiceFunctionKind enum covering 19 dealer-style service functions (oil-life reset, EPB service, SAS calibration, battery registration, DPF regen, TPMS relearn, throttle / idle / transmission / crank relearn, brake bleed, air-suspension calibration, immo relearn, hybrid battery test, Haldex calibration, basic setting, reset adaptations, DEF quality, fuel-trim reset). TOBDServiceFunctionRegistry maps each kind to a token list and classifies routine names as case-insensitive substring matches. FindServiceFunction / ListServiceFunctions walk a vendor extension's Routines and emit TOBDServiceFunction records; BuildServiceFunctionFrame produces the StartRoutine frame; ServiceFunctionKindName renders a UI label. Registry shutdown is done through a class procedure so the unit-level finalization compiles against the strict-private class var. Tests.OBD.OEM.Routines: 27-assertion fixture covering hex round-trip + separators + odd-length / bad-char raises, bit set/get + out-of-range, routine frame builders, positive + negative + wrong-SID / wrong-RID / short response parses, builder typed fields + ASCII fixed-length pad + oversize raise + BCD date + to-frame, reader round-trip + under-read raise, schema decode of mixed uint16+ascii + short-payload graceful stop, service-fn name classification (known / unknown), Find / List against an extension, BuildServiceFunctionFrame uses 31 01, and KindName labels.
OBD.OEM.Session.Runner: TOBDSessionRunner executes a TOBDSessionPlan synchronously against a bound TOBDProtocol. sskATCommand steps go through Adapter.SendCommand and match ExpectedResponse as ASCII; sskUDSRequest steps split the UDS payload into SID + Data, call Protocol.Request, reassemble the on-wire response (ServiceID + Data) and match the ExpectedResponse byte prefix exactly the way v1 did. Each step records duration via TStopwatch and surfaces exceptions as ErrorMessage. Execute() truncates the audit at the first failure so the result reflects only the steps actually run. TOBDTesterPresentThread is the heartbeat thread. It splits the plan's TesterPresentRequest into SID + body and fires Protocol.Request at the configured interval; timeout exceptions are swallowed (sub-function 0x80 suppresses the positive response, so a timeout is the expected outcome) but other failures break out so a dropped adapter stops the heartbeat instead of spinning. StopGracefully() signals the thread + joins. Constructor raises EOBDSessionRunnerError when the protocol is nil or has no bound adapter — fail fast at wiring time rather than at the first plan step. Rebased onto the v2 transport (TOBDProtocol + TOBDAdapter) because v2 has no async-future surface; the equivalent v1 TOBDConnectionAsync.OBDAsync futures are replaced with synchronous Request calls.
Vendor extensions can now port verbatim. The bottleneck was that v1 vendor units register seed-key algorithms via the interface- based contract (TOBDSeedKeyKWP2000TwosComplement.Create, etc.) while v2's stub only exposed an anonymous-function registry. OBD.OEM.SeedKey: replaced the lambda-only stub with the v1 surface — IOBDSeedKeyAlgorithm interface, TOBDSeedKeyRegistry with per-level multi-algorithm LIFO lists, TOBDSeedKeyAlgorithmBase + four reference implementations (KWP2000 two's-complement, XOR mask, byte rotation, fixed-key) and the ISO 14229 §10 frame helpers (RequestSeedFrame / SendKeyFrame / ExtractSeed). Algorithms carry Description / Source / Verified provenance metadata so production callers can filter unverified algorithms out of flashing paths. Backward-compatibility shims for the existing v2 stub callers: TOBDSeedKeyAlgorithm lambda type (wrapped via internal TOBDLambdaSeedKeyAlgorithm adapter), RegisterAlgorithm lambda overload, ComputeKey(Level, Seed) convenience, level-only UnregisterAlgorithm overload, RegisteredLevels (sorted-ascending alias for Levels), EOBDSeedKey alias for EOBDSeedKeyError. The existing v2 lambda-based test fixture passes with one small change: the nil-argument test needs a typed cast now that RegisterAlgorithm is overloaded. OBD.OEM: thin compatibility umbrella that re-exports the v2 OBD.OEM.Types records and OBD.OEM.Extensions contract/base/registry under the names v1 vendor units expect (IOBDOEMExtension, TOBDOEMExtensionBase, TOBDOEMRegistry). v2 split the v1 OBD.OEM monolith into two units; this shim lets a vendor file keep its single 'uses ..., OBD.OEM, OBD.OEM.Session, OBD.OEM.SeedKey, OBD.OEM.DTC;' import line unchanged. The unit also documents the v1->v2 rename for the registry class (TOBDOEMRegistry was the vendor registry in v1; in v2 the same name lives in OBD.OEM.Registry as the runtime overlay resolver — the alias here points at the vendor registry, TOBDOEMExtensionRegistry).
Removed inline references to prior versions / phases / port provenance from headers, history notes and XMLDoc summaries across the OEM unit family and four affected test fixtures. The History block keeps dates and brief notes describing what the unit does; STYLE.md requires that version intent live only in those dedicated header sections, not in narrative throughout the source. Files touched: OBD.OEM, OBD.OEM.Types, OBD.OEM.Session, OBD.OEM.Session.Runner, OBD.OEM.Extensions, OBD.OEM.SeedKey, OBD.OEM.DTC, OBD.OEM.Helpers, OBD.OEM.RoutineControl, OBD.OEM.Coding, OBD.OEM.ServiceFunction, OBD.OEM.Catalog.JSON, OBD.OEM.Catalog.CSV, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader, OBD.OEM.GoldenCheck, OBD.OEM.HD, OBD.OEM.SCN.Mercedes, plus Tests.OBD.Catalog.Inventory, Tests.OBD.Tachograph, Tests.OBD.UI.LegacyPorts, Tests.OBD.Version. No code behaviour changed.
Audit finding: the DTC loader was reading three fields from each catalogue entry (code, description, notes) and silently dropping the other ten that catalogs/dtc-*.json ship — severity, possible_causes, verified, symptoms, repair_guidance, monitor_type, freeze_frame_relevant, related_dids, related_routines, oem_bulletin — plus the file-level default_source. The principle stands: catalogues are the reference and code adheres to them, so every catalogue field has to be available and usable. OBD.OEM.Types: enriched TOBDDtcCatalogEntry with the missing fields and added the supporting enums (TOBDDtcSeverity, TOBDDtcSystem, TOBDDtcMonitorType). Existing fields (Code, Description, Notes) keep their original positions so existing callers and the existing JSON fixture remain compatible. OBD.OEM.DTC: LoadFromFile now reads every schema field, applying the file-level default_source as the per-entry source fallback (matches OBD.OEM.Catalog.Loader's resolution policy for DID / Routine source). Added the J2012 helpers EncodeDtc / FormatDtc (byte and bytes overloads) / ParseDtcSystem / FormatDtcSystem / IsManufacturerDtc / ParseSeverity / FormatSeverity / ParseMonitorType / FormatMonitorType and the EOBDDtcError exception they raise. Helpers are pure functions exported at unit level.
Audit finding: catalogs/key-platforms-{ford,hmg,toyota}.json
ship with rich chassis applicability data (chassis_key,
display_name, access classification, notes) but no code path
reads them. Tools that need to gate destructive PATS / smart-key
routines on chassis access had no way to query "is this
gateway-locked?"; the only references in src/OEM/ were
documentation comments.
OBD.OEM.KeyAdaptation.Platforms (new): exposes
TFordPlatformAccess / TFordPlatformInfo + FindFordPlatform,
THMGPlatformAccess / THMGPlatformInfo + FindHMGPlatform,
TToyotaPlatformAccess / TToyotaPlatformInfo +
FindToyotaPlatform. Loads all three catalogues once at unit
initialisation through the standard ResolveCatalogPath
resolution. Unknown chassis return a fail-safe default
(gateway-locked / certificate-required) so hosts default to
refusing destructive operations. ReloadKeyPlatformCatalogues
re-reads for runtime refresh.
Catalogues are parsed inside a TProc<TJSONObject> visit
callback shared across the three loaders — malformed files
silently leave the registry untouched so application startup
is never blocked, matching the resolution policy of
OBD.OEM.Catalog.Loader and OBD.OEM.DTC.Loader.
OBD.RadioCode.Aftermarket: corrected a stale History note that
referenced a deprecated catalogue path; the Becker4/5 loaders
already read from catalogs/radio-code/.
Three audit follow-ups in one change.
1. Removed catalogs/radiocode-becker{4,5}.json at repo root.
md5-identical to catalogs/radio-code/becker{4,5}.json which
is what TOBDRadioCodeBecker4/5 actually loads; the root
copies were unreachable orphans.
2. Extracted the Ford V-series factory radio-code lookup table
to catalogs/radio-code/ford-v.json. 999,999 entries, 1-based
serial -> 4-digit code, sourced from the same dataset that
TOBDRadioCodeFordV.EnsureLoaded expects. Spot-checks against
the original embedded array confirm round-trip for serial 1,
2 and 999999. JSON header carries schema_version / format /
brand / description / count for tools that want metadata
without parsing the codes array.
3. OBD.KWP.Catalog (new): loads catalogs/kwp/common-ids.json
into two registries — ECU-Identification (Service 0x1A,
0x80..0x9B range) and Common-Identifiers (Service 0x22,
F180..F199). Exposes FindKwpIdentifier(family, id) +
KwpEcuIdentifications + KwpCommonIdentifiers (sorted
ascending). Handles the catalogue's mixed length field
(numeric or "variable") via LengthVariable flag.
Tolerant resolution: missing or malformed file leaves the
registries empty so application startup never blocks.
Vendor extensions receive their DIDs / routines / coding blocks / adaptations / actuator tests / live PIDs / DTC extended-data records via TOBDOEMJSONCatalog's AsBase* + the loader's Convert* helpers. The lean form previously dropped the schema's provenance fields (source, verified) and the live-PID / DTC-extended decoder's structural maps (size, enum values, bit names). The OEM JSON catalogue carries that data; tools and vendor code now see it. OBD.OEM.Types: added Source + Verified to TOBDOEMDataIdentifier, TOBDOEMRoutine, TOBDOEMCodingBlock, TOBDOEMAdaptation, TOBDOEMActuatorTest, TOBDOEMLivePID, TOBDDtcExtendedDataRecord. TOBDOEMDecoderSpec (new) packages the full decoder schema (kind, size, scale, offset, unit, enum values, bit names) as a value-type record; TOBDOEMDataIdentifier carries one directly, and TOBDOEMLivePID / TOBDDtcExtendedDataRecord expose it alongside the existing flat decoder fields (kept for back-compat, mirrored from the structured form). OBD.OEM.Catalog.JSON: TOBDLivePIDEntry / TOBDDtcExtendedDataEntry now also parse the decoder's "size", "values" and "bits" sub-objects. Added ParseBitNames helper. AsBaseDIDs + AsBaseRoutines populate the new public fields, including a ToOEMDecoderSpec converter that flattens the internal class-backed TOBDDecoderSpec into the value-type TOBDOEMDecoderSpec. OBD.OEM.Catalog.Loader: every Convert* function populates Source + Verified; ConvertLivePID and ConvertDtcExtended additionally populate the structured Decoder subrecord.
OBD.OEM.VW: Volkswagen Audi Group extension. Loads vw.json + uds-standard.json into the base catalogue and vw.json's extended slice into the coding / adaptation / actuator-test / live-PID / DTC-extended tables. SeedDefaultDtcCatalog overlays the ISO 15031-6 P0xxx baseline plus dtc-vw.json. The session negotiator (TOBDVWSessionNegotiator) prepends the AT SH + AT CRA handshake that VCDS / ODIS issue against the request + request-plus-8 CAN-IDs and uses a 2000 ms tester-present cadence matching ODIS service mode 2. Seed-key starter registers the textbook KWP2000 two's-complement at Level 1 for the pre-2008 KWP components; production callers replace it via RegisterAlgorithm. DecodeDID specialises battery_voltage (0xF405), vehicle_speed (0xF40D), and VIN (0xF190). OBD.OEM.Bentley: Bentley Motors (Crewe). VAG-group sister brand; loads bentley.json + uds-standard.json + the standard DTC overlay. ApplicableToVIN reads applicable_wmis from bentley.json. DecodeDID renders VIN (0xF190) and the Bentley metadata block 0xF1A0/A2/A4/A6/A8 as ASCII strings. Both extensions register themselves through TOBDOEMRegistry.RegisterExtension at unit init; the package includes both new units.
OBD.OEM.BMW: Bayerische Motoren Werke. Catalogue + DTC overlay (bmw.json + dtc-bmw.json + iso-15031 baseline). TOBDBMWSessionNegotiator declares SecurityAccess required for extended / programming / OEM-specific sessions and a 1500 ms tester-present cadence (the stock 2000 ms causes session drops on E-series DMEs). Seed-key starter is the documented community XOR-mask placeholder (community-pr, Verified=False); production callers replace it via RegisterAlgorithm. DecodeDID specialises battery_voltage (D051), mileage (D050), and VIN (F190). OBD.OEM.MINI: BMW Group sub-brand with its own WMIs (WMW Oxford, SAW BMW Brilliance China). Inherits the BMW E-Sys session lineage (same RequiresSecurityAccess + 1500 ms cadence). Catalogue mini.json + dtc-mini.json. Same seed-key starter as BMW. DecodeDID renders VIN and the MINI chassis-code metadata DID (F1A4). OBD.OEM.RollsRoyce: Rolls-Royce Motor Cars (Goodwood). BMW Group sub-brand sharing the E-Sys / ISTA stack. Catalogue rolls-royce.json + dtc-rolls-royce.json. Same negotiator shape; no separate seed-key override (uses the base default which production callers replace anyway). DecodeDID renders VIN plus the Rolls-Royce metadata DID block F1A0/A2/A4/A6. Package updated to include all three units.
OBD.OEM.Mercedes: Mercedes-Benz Group. Catalogue + DTC overlay (mercedes.json + dtc-mercedes.json) layered on the ISO 15031-6 baseline. TOBDMercedesSessionNegotiator extends the inherited plan with a 22 F198 workshop-code probe after non-default session entry — XENTRY ECUs gate later routines on a known last-writer ID. Tester-present cadence matches XENTRY (1500 ms). Seed-key starter is the textbook KWP2000 two's-complement at Level 1 for legacy HHTwin / Star Diagnosis. DecodeDID renders VIN (F190), BCD manufacturing-date (F18B), 24-bit mileage (0202), engine-running seconds (0203) and the programming- status enum (F19E). OBD.OEM.Smart: smart Automobile Co. — Mercedes-Geely JV. Catalogue smart.json + dtc-smart.json. Seed-key starter is the Mercedes lineage's KWP2000 two's-complement (modern SEA-platform algorithms are NDA-protected; production callers register the real one). DecodeDID renders VIN and the smart metadata trio (F1A0 model_code, F1A2 drivetrain, F1A4 battery_pack); rewrote v1's case-as-expression construct into a plain inner case statement so the unit compiles under the standard Delphi grammar.
OBD.OEM.SecurityAccess: TOBDSecurityAccessClient runs the full
ISO 14229-1 §10 service 0x27 dance against a bound TOBDProtocol.
Given a level it requests the seed (27 LL), resolves the
algorithm from a TOBDSeedKeyRegistry, computes the key, sends it
(27 LL+1 + key), and reports the outcome via a structured
TOBDSecurityAccessResult. Honours the documented negative-
response semantics: 0x35 invalidKey surfaces as failure, 0x36
exceededNumberOfAttempts surfaces as ECU lockout (the lockout
is the ECU's, not ours, so the client refuses further
attempts), 0x37 requiredTimeDelayNotExpired triggers a
caller-configurable backoff loop (RetryDelayMs, MaxDelayRetries).
A zero-length seed is treated as "already unlocked at this
level" per §10.5.2.
Algorithms stay user-supplied via RegisterAlgorithm so the
client itself never contains any OEM-specific crypto; production
deployments register their NDA-protected implementations at
startup and the client just sequences the round trips.
OBD.OEM.SeedKey: three new reference algorithms, all flagged
Verified=False, all sourced from public-domain examples in
ISO 14229-1 Annex A / SAE J2186 textbook material:
- TOBDSeedKeyTwosComplementLE: variant of the existing
two's-complement that propagates the +1 carry from byte 0
upwards rather than byte N downwards. Documented Bosch ME7 /
EDC15 KWP2000 family shape.
- TOBDSeedKeyNibbleSwapXor: key[i] = nibble_swap(Mask[i] XOR
seed[i]). Caller supplies the mask. Common public shape on
several legacy KWP2000 / KLine ECUs.
- TOBDSeedKeyCrc32XorMask: CRC-32 (reflected IEEE-802.3 / zlib
poly 0xEDB88320) of the seed bytes XOR'd with a caller-
supplied 4-byte mask. Common community bench-flash template.
OBD.OEM.Stellantis: Stellantis (FCA + PSA) extension. Covers Fiat / Chrysler / Jeep / Dodge / RAM / Alfa Romeo / Lancia / Maserati / Peugeot / Citroen / DS / Opel / Vauxhall. Negotiator appends an F198 workshop-code probe after non-default session entry — PSA DiagBox always reads it; the expected- response prefix is left empty so FCA's 7F 22 31 NACK does not fail the plan. Seed-key starter: KWP2000 two's-complement for the legacy PSA BSI / FCA Body Computer modules. DecodeDID specialises VIN (F190), BCD programming-date (F199), 24-bit mileage (1A02), fuel-level percent (1B01), engine-run-time seconds (1B02), and battery voltage (1B03). OBD.OEM.Bentley + OBD.OEM.RollsRoyce: backfilled SeedDefaultSeedKeyAlgorithms. Bentley inherits the VAG-lineage KWP2000 two's-complement at Level 1; Rolls-Royce inherits the BMW-lineage XOR-mask placeholder. Both starters are flagged community-pr / Verified=False so production callers replace them via RegisterAlgorithm — every ported vendor now ships a non-empty seed-key registry so TOBDSecurityAccessClient.Unlock produces a non-trivial round trip even with the defaults.
OBD.OEM.Ford: Ford Motor Company. Negotiator prepends AT ST 32 to extend the ELM327 OBD timeout for programming sessions (~3.2 s to match FDRS post-1002 stabilisation). Seed-key starter is the ForScan-documented byte-rotate placeholder for pre-2010 Visteon PCMs. DecodeDID handles VIN, mileage (DD00), fuel level (DE00), engine-run-time (DE01), battery voltage (DE02), and the calibration-id metadata trio (DF00/01/02); rewrote v1's case-as-expression construct into an inner case. OBD.OEM.GM: General Motors. Negotiator prepends AT SP 6 to lock the adapter onto ISO 15765-4 11-bit / 500 kbps (GMLAN) regardless of any prior auto-protocol state. Seed-key starter is the GMLAN Class B trial-mode constant key (four zero bytes). DecodeDID handles VIN, mileage (1981), engine-run-time (1982), battery voltage (1983), and the metadata pair (F1A0 broadcast_code / F1A4 engineering_part_number) — same case-as-expression rewrite. OBD.OEM.JLR: Jaguar Land Rover. JSON-driven catalogue; seed-key starter is the legacy DDW2000 / IDS KWP2000 two's-complement. DecodeDID handles VIN, jlr_model_code (F1A0), jlr_assembly_plant (F1A2). OBD.OEM.AstonMartin: Aston Martin Lagonda. Ford-lineage byte-rotate starter (pre-2018 Visteon PCM family); AMG-era AML ECUs use Mercedes XENTRY crypto which production callers register separately. DecodeDID handles VIN and the aml_meta DID block (F1A0/A2/A4/A6/A8).
All four units load their JSON catalogue + DTC overlay, register a SecurityAccess starter algorithm at Level 1, and decode the brand-specific F1Ax metadata DIDs into typed labels. - OBD.OEM.Tesla: starter is the textbook KWP2000 two's-complement placeholder; Tesla service-mode unlock is NDA-protected so production callers register the real implementation via RegisterAlgorithm. DecodeDID surfaces tesla_firmware_version (F1A0) and tesla_hardware_id (F1A2). - OBD.OEM.Lucid: same KWP2000 two's-complement starter; in-house Wunderbox toolchain ships the real algorithm. DecodeDID surfaces the full Lucid metadata quartet (model_code, drivetrain, battery_pack, software_release). - OBD.OEM.Rivian: same starter; in-house service-mode unlock. DecodeDID surfaces rivian_model_code + drivetrain. - OBD.OEM.Polestar: KWP2000 two's-complement matching the Volvo / SPA2 lineage; SEA-platform Polestars use proprietary crypto. DecodeDID surfaces polestar_model_code + drivetrain. Per the "every extension ships a non-empty seed-key registry" policy, all four register a Level 1 starter so TOBDSecurityAccessClient.Unlock has something to call against defaults; every starter is flagged Verified=False with community / placeholder provenance so production callers know to replace it.
OBD.OEM.HyundaiKia: Hyundai Motor Group (Hyundai / Kia / Genesis) on the hmg.json + dtc-hmg.json catalogues. Session negotiator picks a 1500 ms tester-present cadence — GDS / KDS keep the extended session alive at that rate. Seed-key starter is the community 'HMC' XOR-mask. DecodeDID surfaces VIN, hmg_rom_id (F193), hmg_calibration_id (F1A0), hmg_vehicle_option_code (F1B0). OBD.OEM.Toyota: Toyota Motor Corporation (Toyota + Lexus + Daihatsu — Subaru is its own extension). Seed-key starter is the textbook KWP2000 two's-complement accepted by pre-2010 2AZ-FE / 2GR-FE controllers. DecodeDID surfaces VIN and toyota_calibration_id (F1A0). OBD.OEM.Honda: Honda Motor Co. (incl. Acura). Seed-key starter is the community 'SHMO' XOR-mask used by pre-2010 PCMs. DecodeDID surfaces VIN, honda_chassis_code (F1A0), honda_factory_code (F1A2). OBD.OEM.Mazda: Mazda Motor Corporation. Pre-2014 PCMs (Ford Visteon lineage) accept the KWP2000 two's-complement; modern M-MDS uses NDA crypto. DecodeDID surfaces VIN, mazda_as_built_code (F1A0), mazda_market_code (F1B0). All four register a Level 1 seed-key starter per the every-extension policy so TOBDSecurityAccessClient.Unlock has something to call against the defaults; production callers register their NDA implementations via RegisterAlgorithm.
- OBD.OEM.Nissan: Nissan / Infiniti / Datsun. KWP2000 starter.
DecodeDID surfaces VIN, nissan_chassis_code (F1A1),
nissan_market_code (F1B0).
- OBD.OEM.Mitsubishi: Mitsubishi Motors Corp. KWP2000 starter.
DecodeDID surfaces VIN, mitsu_chassis_code (F1A0),
mitsu_market_code (F1B0).
- OBD.OEM.Subaru: Subaru Corporation. Seed-key starter is the
SSM-community byte-rotate ('SB' mask, rotate 4) accepted by
pre-2012 ECUs documented in OpenECU. DecodeDID surfaces VIN,
subaru_chassis_code (F1A0).
- OBD.OEM.Suzuki: Suzuki Motor Corp. (incl. Maruti Suzuki).
KWP2000 starter. DecodeDID surfaces VIN, suzuki_chassis_code
(F1A0).
- OBD.OEM.Isuzu: Isuzu Motors Ltd. KWP2000 starter (GM Powertrain
Diesel Engineering lineage on pre-2015 EFI-Live / Tech 2).
DecodeDID surfaces VIN, isuzu_model_code (F1A0),
isuzu_engine_code (F1A2).
All five register a Level 1 starter; production callers replace
via RegisterAlgorithm.
- OBD.OEM.Renault: Renault Group (Renault / Dacia / Alpine). Seed-key starter is the CLIP-community 'RNLT' XOR-mask for pre-2014 EMS modules. DecodeDID surfaces VIN, renault_calibration_id (F1A0), renault_market_code (F1A2). - OBD.OEM.Dacia: Automobile Dacia (Renault Group). Inherits the Renault-lineage 'RNLT' XOR-mask starter so the registry is non-empty by default. DecodeDID surfaces VIN + the dacia_meta block (F1A0/A2/A4/A6). - OBD.OEM.Volvo: Volvo Cars (Geely). Session negotiator picks a 5000 ms tester-present cadence (VIDA / DiCE keep-alive rate). Seed-key starter is the KWP2000 two's-complement for pre-SPA2 ECUs. DecodeDID surfaces VIN, volvo_struct_week (F1A0), volvo_factory_code (F1A2), volvo_pno_code (F1B0).
- OBD.OEM.Ferrari: Ferrari N.V. KWP2000 starter for pre-2015 SDC ECUs. DecodeDID surfaces VIN, ferrari_model_code (F1A0), ferrari_paint_code (F1A2), ferrari_options_block (F1A4), ferrari_assembly_data (F1A6), ferrari_warranty_block (F1B0). - OBD.OEM.McLaren: McLaren Automotive. KWP2000 starter; in-house MSO toolchain ships the real algorithm. DecodeDID surfaces VIN + mcl_meta block (F1A0/A2/A4/A6). - OBD.OEM.Porsche: Dr. Ing. h.c. F. Porsche AG. KWP2000 starter; PIWIS uses proprietary algorithms. DecodeDID surfaces VIN, porsche_model_code (F1A0), porsche_paint_code (F1A2), porsche_options_block (F1A4). All three register a Level 1 seed-key starter; production callers replace via RegisterAlgorithm.
All five register a Level 1 KWP2000 two's-complement starter so TOBDSecurityAccessClient.Unlock has something to call against defaults; production callers replace via RegisterAlgorithm. - OBD.OEM.BYD: BYD Auto Co. Ltd. DecodeDID surfaces VIN, byd_model_code (F1A0), byd_battery_pack_id (F1A2). - OBD.OEM.Geely: Geely Auto / Lynk & Co. DecodeDID surfaces VIN, geely_platform_code (F1A0), geely_market_code (F1A2). - OBD.OEM.GreatWall: Great Wall Motor (Haval / WEY / ORA / Tank / Poer). DecodeDID surfaces VIN, gwm_brand_code (F1A0), gwm_platform_code (F1A2). - OBD.OEM.NIO: NIO Inc. DecodeDID surfaces VIN, nio_model_code (F1A0), nio_battery_swap_id (F1A2). - OBD.OEM.Xpeng: Xpeng Motors. DecodeDID surfaces VIN, xpeng_model_code (F1A0), xpeng_xpilot_version (F1A2).
All three register a Level 1 KWP2000 two's-complement starter; production callers replace via RegisterAlgorithm. - OBD.OEM.Tata: Tata Motors Ltd. DecodeDID surfaces VIN, tata_model_code (F1A0), tata_variant_code (F1A2), tata_engine_code (F1A4). - OBD.OEM.Mahindra: Mahindra & Mahindra Ltd. DecodeDID surfaces VIN, mahindra_model_code (F1A0), mahindra_variant_code (F1A2), mahindra_engine_code (F1A4). - OBD.OEM.Lada: AvtoVAZ. Granta / Vesta / Niva Legend / Niva Travel / Largus on the Bosch ME17.9.7 / EDC17 lineage. DecodeDID surfaces VIN + the lada_meta block (F1A0/A2/A4/A6).
… Scania + VolvoTrucks) All seven extend TOBDOEMExtensionBase + import OBD.OEM.HD for the J1939 source-address constants. All register a Level 1 KWP2000 two's-complement starter; production callers replace via RegisterAlgorithm. - OBD.OEM.Cummins: engine-OEM (Ram HD, Iveco / IH commercials, marine / industrial gensets). DecodeDID: F1A0 engine_serial, F1A1 calibration_id. No VIN — engines are not whole vehicles. - OBD.OEM.DetroitDiesel: engine-OEM (Daimler Truck — Freightliner / Western Star, gensets). DecodeDID: F1A0 engine_serial, F1A1 calibration_id, F1A2 emissions_family. - OBD.OEM.Iveco: Iveco Group (Daily / Eurocargo / S-Way / Iveco Bus / Iveco Defence). DecodeDID: VIN, F1A0 model_code, F1A2 emissions_pkg. - OBD.OEM.MAN: MAN Truck & Bus (Traton). DecodeDID: VIN, F1A0 chassis_code, F1A2 engine_serial. - OBD.OEM.PACCAR: PACCAR Inc. (Peterbilt / Kenworth / DAF / Leyland). DecodeDID: VIN, F1A0 chassis_code, F1A2 factory_code. - OBD.OEM.Scania: Scania AB (Traton). DecodeDID: VIN, F1A0 chassis_number, F1A2 specification_code, F1A4 engine_serial. - OBD.OEM.VolvoTrucks: Volvo Group commercial (Volvo Trucks / Mack / Renault Trucks — distinct from Volvo Cars / Polestar passenger). DecodeDID: VIN, F1A0 chassis_code, F1A2 emissions_pkg.
…wersports) Each unit is a thin registration shell on top of an abstract base class. Brand subclasses only declare a JSON filename + the identity strings; the base wires the catalogue load, extended- catalogue merge, ISO 15031-6 + brand-specific DTC overlay, and a Level 1 KWP2000 two's-complement seed-key starter so TOBDSecurityAccessClient.Unlock has something to call against the defaults. Catalogue files live in catalogs/<class>/<oem>.json and are resolved through the standard ResolveCatalogPath subdirectory search. - OBD.OEM.Agricultural (8): JohnDeere, CNH, CaterpillarAgri, Komatsu, Kubota, AGCO, Claas, VolvoCE. - OBD.OEM.Marine (6): MercuryMarine, VolvoPenta, YanmarMarine, MTU, CumminsMarine, YamahaMarine. - OBD.OEM.Motorcycles (14): Ducati, HarleyDavidson, Triumph, BmwMotorrad, KTM, YamahaMoto, HondaMoto, Kawasaki, SuzukiMoto, IndianMotorcycle, RoyalEnfield, MVAgusta, Aprilia, HusqvarnaMoto. - OBD.OEM.Powersports (5): Polaris, CanAmBrp, ArcticCat, YamahaWaverunner, KawasakiJet. That closes the 38 car + 7 HD + 4 speciality vendor port plan.
Closes out the runtime tooling I had on hold during the vendor port. OBD.OEM.ServiceRoutines: workshop service-routine registry loaded from catalogs/service-routines.json. Exposes lookup by key / category / OEM applicability, plus BuildRoutineControlFrame to render the on-wire 31 SF RID-hi RID-lo OptionRecord bytes. OBD.UDS.NRC: ISO 14229-1 negative-response-code catalogue loaded from catalogs/uds-nrc.json. DescribeNRC returns a typed record (with category) and falls back to a synthetic entry for unknown codes; FormatNRC renders a log line; IsTransientNRC classifies the busy / repeat-request / response-pending / temporarily- unavailable codes as retry-friendly. OBD.OEM.SessionHelper: one-call wrapper around service-routine execution. Opens session, consults voltage gate when the routine demands it (srsBatteryMin12V5), sends RoutineControl, optionally reads result, always attempts to close. Voltage source is a TOBDVoltageSourceFunc matching OBD.Flash.VoltageGate so a host can reuse one function across both. OBD.OEM.DiagSession: stateful diagnostic-session wrapper around TOBDProtocol + IOBDOEMExtension. Owns the tester-present heartbeat thread, delegates SecurityAccess to the new TOBDSecurityAccessClient, and provides ReadDID (raw + decoded), StartRoutine / StopRoutine / RequestRoutineResults helpers. Last-error reporting via the LastError property; never raises from runtime methods. OBD.Async: lightweight futures + cancellation tokens (IOBDFuture<T> / IOBDPromise<T> / IOBDCancellationToken). The producer creates a promise, the consumer gets the future facet and Awaits / polls / attaches OnComplete. Shared tokens propagate a single Cancel call to every in-flight observer. OBD.OEM.UdsClient: high-level catalogue-driven UDS client. Given a TOBDOEMJSONCatalog + an IOBDDiagnosticTransport it exposes ReadDID(name) → typed value, WriteAdaptation with min/max clamp, ExecuteRoutine, ReadCodingBlock + WriteCodingBlock with bit-level pack/unpack and read-modify-write safety, RunActuatorTest with a safety-warning gate, ReadDtcs(statusMask) and StreamLivePIDs polling. OBD.OEM.UdsClient.Async: future-based facade over IOBDUdsClient. Single-worker serialisation per session; cancellation drops the result before observation rather than interrupting the underlying SendReceive (UDS has no out-of-band cancel). OBD.OEM.Captures: v2-native rewrite of the capture validator. Walks an .obdlog as TArray<TOBDLogEntry> (the recorder produces these directly), pairs Frame → next Response / NRC by ServiceID, and runs each 22 hi lo pair through the OEM extension's DecodeDID — reports which DIDs were catalogued, which decoded cleanly, which fell back to a hex dump.
Five focused fixtures covering the units shipped in the remaining-infra commit. Tests.OBD.OEM.ServiceRoutines: BuildRoutineControlFrame encodes Start / Stop / RequestResults sub-functions, appends option-record bytes after the RID, and rejects invalid sub-functions with EOBDServiceRoutine. Tests.OBD.UDS.NRC: DescribeNRC falls back to a synthetic record (Category=nrcReserved) for uncatalogued codes; FormatNRC renders the documented one-line form; IsTransientNRC classifies 0x21 / 0x22 / 0x78 / 0x94 as retry-friendly and rejects the common non-transient codes. Tests.OBD.OEM.SessionHelper: drives RunServiceRoutine via lambda callbacks. Covers the happy path, OpenSession refusal, StartRoutine refusal, voltage-gate failure when reading below threshold, voltage-gate pass at or above threshold, and the missing-required-callback raise. Tests.OBD.Async: SetResult + Await returns the value; SetError raises through Await; cancellation via shared token raises EOBDOperationCancelled; an unsettled future times out with EOBDFutureTimeout; OnComplete attached pre- and post-settlement both fire as documented. Tests.OBD.OEM.Captures: synthesises TArray<TOBDLogEntry> and verifies ExtractCapturePairs strips the SID + DID echo on a 0x22 reply, marks NRC entries IsNegative, flushes a hanging Frame without a response, and ValidateAgainstExtension reports catalogued DID names + skips decode for non-22 service IDs. Skipped: TOBDSecurityAccessClient + TOBDDiagSession need a mock TOBDProtocol with a working adapter (heavyweight); the underlying primitives (SeedKey algorithms, RoutineControl frame helpers, NRC catalogue, plan runner) are covered elsewhere. UdsClient tests require an OEM JSON catalogue fixture; the unit's transport-abstraction shape means hosts exercise it directly with their mock transport in production.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
This PR executes a major repository restructuring aligned with the Delphi-OBD v2 rewrite plan. The changes:
Remove legacy v3.x infrastructure: Deletes old Delphi package files (
Packages/RunTime.dproj,Packages/DesignTime.dproj, etc.), v3 changelog, and outdated documentation (ROADMAP, PROPOSALS, EXTENSION_PLAN_v3.*, etc.)Introduce catalog-first architecture: Adds comprehensive JSON catalogs under
catalogs/for OBD-II (DTCs, PIDs modes 01/06, NRCs), UDS (DIDs, NRCs), J1939 (PGNs, SPNs), and OEM-specific extensions (20+ manufacturers). These catalogs are the single source of truth for diagnostic identifiers.Establish new source layout: Creates clean
src/directory structure with.gitkeepplaceholders for:Core/— foundational types, errors, catalog loading, decodersAdapter/,Connection/,Protocol/,Coding/,Flashing/— subsystems (to be populated)DesignTime/— IDE registration stubsModernize documentation: Replaces scattered v3 docs with focused guides:
PLAN.md— locked design decisions and rewrite roadmapSTYLE.md— code style guidephase-reviews.md— honest end-of-phase reportsflashing-safety.md— safety warnings for flashing componentsSimplify examples: Removes 20+ legacy example projects; introduces minimal
samples/00-Hello/as the canonical starting point.Update CI/CD: Streamlines GitHub Actions workflows (removes nightly/docs jobs, focuses on core build).
License & governance: Adds MIT LICENSE, updates CONTRIBUTING.md with v2 contribution model.
How did you test it?
Roadmap & changelog
PLAN.mdCHANGELOG.mdwith v2 milestone marker and catalog additionsPLAN.mdis now the single source of truthReviewer notes
Key structural decisions locked in this PR:
Files to spot-check:
PLAN.md— confirms design decisions and phase sequencingcatalogs/— verify schema consistency across OBD2, UDS, J1939, OEM folderssrc/Core/stubs — confirm they're placeholders only (no implementation yet).gitignore— simplified to exclude build artifacts onlyhttps://claude.ai/code/session_01FM1RUQv4WiMqsVZQnH6w24