diff --git a/CHANGELOG.md b/CHANGELOG.md index cc083c4a..014de157 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3240 +1,34 @@ # Changelog -All notable changes to this project will be documented in this file. +All notable changes are documented per major version. The full history +was previously a single 308 KB file; it has been split for browsability. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## Per-version files -## [3.79.0] - 2026-05-08 — Async UDS + cross-platform DoIP + TLS + tooling +- **[CHANGELOG/v3.md](CHANGELOG/v3.md)** — v3.0.0 → v3.79.0 (current). + All FMX bindings, the OEM extension framework, JSON catalog format, + 79 OEM catalogs, async UDS, cross-platform DoIP, DoIP TLS, coverage + harness. +- **[CHANGELOG/v2.md](CHANGELOG/v2.md)** — v2.1.0 → v2.5.0. Test + + CI foundation, component library, async + logging, distribution + + docs, hardening + ECU flashing. -**Async UDS client** (`OBD.OEM.UdsClient.Async`): future-returning -facade over `IOBDUdsClient` so UI threads can fire-and-await every -diagnostic call without blocking. One serialised worker thread per -client matches the wire contract (UDS allows one outstanding -request per ECU). Cooperative cancellation through -`IOBDCancellationToken` — pre-cancelled tokens never reach the -wire; CloseSession drains pending futures as cancelled. -`Tests.OEM.UdsClient.Async` covers Await, OnComplete, -pre-cancellation, exception propagation, and serial in-order -completion. +## Latest release at a glance -**Capture/replay round-trip** (`Tests.OEM.UdsClient.Replay`): -new `TCaptureReplayTransport` parses recorded `.obdlog` pairs and -replays responses by request-byte match. First end-to-end test -that exercises ResolveCatalogPath + TOBDOEMJSONCatalog + -IOBDUdsClient + DID decoder against real wire data — VW VIN -(F190 → "WVWZZZ8N8Z1234567") and VAG part number (F187 → -"04L906056AA") decode bit-exactly from the captured fixture. +**v3.79.0** (2026-05-08) — Async UDS + cross-platform DoIP + TLS + +tooling. -**Cross-platform DoIP** (`OBD.Protocol.DoIP.Session.Cross`): -TCP-side ISO 13400-2 §8 implementation built on -`System.Net.Socket` (RTL, all platforms — Windows, macOS, Linux, -iOS, Android), no Indy or Synapse dependency. Same Connect / -ActivateRouting / SendReceive / Disconnect surface as the WinSock -variant; same 16-frame ACK/NACK consumption cap. Self-loop -integration test (`Tests.Protocol.DoIP.Cross`) spins a -`TFakeGateway` TThread on 127.0.0.1, walks the routing-activation -handshake, and asserts a diagnostic round-trip is bit-exact — -first end-to-end DoIP wire test in the suite. +- `OBD.OEM.UdsClient.Async` — future-returning UDS facade with + cooperative cancellation. +- `OBD.Protocol.DoIP.Session.Cross` — TCP DoIP on `System.Net.Socket` + for all platforms. +- `OBD.Protocol.DoIP.Session.TLS` — ISO 13400-3 §7 TLS 1.2+ via Indy + + OpenSSL with mutual TLS support. +- `TCaptureReplayTransport` — deterministic `.obdlog` round-trip. +- `examples/catalogbrowser` — VCL catalog explorer. +- `tools/coverage/` — `delphi-code-coverage` harness. -**DoIP TLS** (`OBD.Protocol.DoIP.Session.TLS`, ISO 13400-3 §7, -TCP/3496): Indy 10 + OpenSSL TLS-secured session. TLS 1.2 -mandatory minimum (1.3 allowed) per spec. Mutual TLS via -`TDoIPTLSCredentials` (root CA, client cert+key+passphrase, -peer-verification toggle, optional cipher-list override for OEM -policies). Self-loop integration test spins -`TIdTCPServer + TIdServerIOHandlerSSLOpenSSL` with a fixture -self-signed cert pair (`tests/fixtures/tls/`); falls back to -Assert.Pass with skip message if OpenSSL is unavailable on the -dev machine. - -**Catalog Browser VCL example** (`examples/catalogbrowser`): -programmatic single-form VCL app that walks every shipped OEM -catalog and lets the user drill into ECUs / DIDs / Routines / -Coding Blocks / Adaptations / Actuator Tests / Live PIDs / DTC -Extended Data side by side. Auto-loads from `catalogs/` by -walking up from the binary; skips schema, DTC catalogs, ISO/UDS -universal files and test fixtures. - -**Coverage harness** (`tools/coverage/`): wires -`delphi-code-coverage` against the DUnitX test runner. Emits -HTML + Cobertura XML + LCOV reports. `cov-include.txt` lists the -OEM / UDS / DoIP / async / capture units to instrument; CI -post-test upload step is documented for when the Delphi runner -is provisioned (see GAPS.md G4). - -GAPS.md G12 (DoIP TLS + self-loop integration test) marked -✅ FIXED. The remaining open gap is G4 (CI Delphi runner — process -constraint, not a code gap) — coverage tooling is now ready for -it. - -## [3.78.0] - 2026-05-08 — Production-quality gap pass + Phase B vehicle classes - -Phase D (DTC content): expanded `dtc_extended_data` across 47 OEM -catalogs (1,282 entries) with the v3.77 schema fields (symptoms, -repair_guidance, monitor_type, freeze_frame_relevant, related_dids, -related_routines, oem_bulletin). Phase E (DoIP transport): unit body -guarded with `{$IFDEF MSWINDOWS}`, `SendReceive` alive-check loop -bounded to 16 frames per call. Phase F.1-F.3 (UDS client): -`OBD.OEM.UdsClient` async-friendly facade — OpenSession / ReadDID / -WriteAdaptation / ExecuteRoutine / ReadCodingBlock / WriteCodingBlock -/ RunActuatorTest / ReadDtcs / StreamLivePIDs, with ASCII-empty- -payload guard and tightened bounds enforcement (no longer skipped -when min=max=0). - -Phase A (schema/JSON Schema): `catalogs/_schema/oem-catalog-v2.json` -shipped + new `Tests.OEM.SchemaShape` walks every catalog asserting -WMI regex, decoder/field/adaptation kind enums, DTC code formats -(SAE J2012 + J1939 SPN-FMI + 22 OEM prefixes), non-empty manufacturer -keys and version 1/2 bound. Phase C (catalog integrity): -`Tests.OEM.CatalogIntegrity` covers coding-block payload bounds, -cross-section ECU references, and duplicate primary keys — replaces -the deleted Python lint. - -Phase B (vehicle classes, 33 new OEM catalogs, ~50,000 entries): -- Motorcycles (14): Ducati, Harley-Davidson, Triumph, BMW Motorrad, - KTM, Yamaha-moto, Honda-moto, Kawasaki, Suzuki-moto, Indian - Motorcycle, Royal Enfield, MV Agusta, Aprilia, Husqvarna-moto. -- Agricultural (8): John Deere, CNH, Caterpillar-Agri, Komatsu, - Kubota, AGCO, Claas, Volvo CE. -- Marine (6): Mercury Marine, Volvo Penta, Yanmar Marine, MTU, - Cummins Marine, Yamaha Marine. -- Powersports (5): Polaris, Can-Am/BRP, Arctic Cat, Yamaha - WaveRunner, Kawasaki Jet Ski. - -Each backed by `OBD.OEM.{Motorcycles,Agricultural,Marine, -Powersports}.pas`, registered at unit init, wired into RunTime.dpk -+ RunTime.dproj. `ResolveCatalogPath` probes vehicle-class subdirs -after the top level. `AllOEMCatalogsLoadFromDirectory` recurses with -`TSearchOption.soAllDirectories`; threshold raised to ≥70 catalogs. - -Cleanup: removed all 8 Python lint scripts (this is a Delphi -repository); CI lint replaced with bash one-liner + the Delphi -`Tests.OEM.CatalogIntegrity` fixture. 36 catalogs re-deduped on -normalised integer ECU addresses; 9 coding-block payloads bumped; -59 implicit ECU references promoted to explicit `ecus[]` entries. - -Total: 79 OEM catalogs / 247,279 entries / 5 vehicle classes. - -## [3.76.0] - 2026-05-08 — Isuzu Motors ~18% ODIS, ~4,800 entries - -RZ4E 1.9 diesel + DDi 3.0 Blue Power + 4HK1 5.2 + 6HK1 7.8 + 6UZ1 -9.8 + 6WG1 15.7 + 4HK1 LNG + N-Series Electric + Giga Electric + -D-Max EV announced + Giga FCEV (Honda fuel cell partnership) + D-Max -Rough Terrain Mode + Aisin 6/8AT + AMT MZW6E + MIMAMORI + IDSS. - -## [3.75.0] - 2026-05-08 — Volvo Trucks Group ~22% ODIS, ~4,900 entries - -Volvo + Mack + Renault Trucks + UD Trucks. D8/D11/D13/D16 + D13TC -turbo-compound (I-Save) + D11K/D13K LNG + D13H hydrogen ICE + FL/FE/ -FH/FM/FMX Electric (FH Electric 490 kW dual e-axle) + B8R bus EV + -Mack MD Electric + Renault D Wide ZE + Volvo FH FCEV (Cellcentric JV) -+ I-Shift 12 AMT + I-Shift Dual Clutch + Powertronic 6AT + Tech Tool. - -## [3.74.0] - 2026-05-08 — PACCAR (Kenworth/Peterbilt/DAF) ~18% ODIS, ~4,800 entries - -MX-11/MX-13 + PX-7/PX-9 + MX-11 LNG + MX-13 hydrogen ICE + Kenworth -T680E EV + Peterbilt 579EV + DAF XB-e/XF Electric + Kenworth/Peterbilt -hydrogen fuel cell EV (Toyota partnership) + PACCAR AMT 12-speed + -Eaton Endurant + TruckTech+/SmartLINQ/DAF Connect. - -## [3.73.0] - 2026-05-08 — Scania ~22% ODIS, ~4,900 entries (Traton Group) - -DC09/DC13/DC16 V8 (660/770 hp) + Super 13L next-gen + OG13 LNG/CNG + -Super 13H hydrogen ICE + Scania BEV + PHEV DC09 + Opticruise G25/G33 -12-speed AMT + Active Prediction GPS-aware + Scania One TCU + SDP3. - -## [3.72.0] - 2026-05-08 — MAN Truck & Bus ~22% ODIS, ~4,900 entries (Traton Group) - -D08/D20/D26/D38/D15 + E3876 NG + D38H hydrogen ICE + eTruck eTGX/ -eTGS + eTGM/eTGE BEV + Lion's E City bus + EfficientCruise + -EfficientRoll + Predictive Powertrain Control + EBA 2 + Lane Return -+ Side Collision Avoidance + TipMatic 12-speed AMT + MAN-cats II. - -## [3.71.0] - 2026-05-08 — Iveco Group ~18% ODIS, ~4,800 entries - -Cursor 8/9/11/13/16 + F1C/F1A/NEF + Cursor NG (LNG/CNG) + FPT XC13 -hydrogen ICE + Hi-SCR (no EGR Iveco trademark) + Hi-Cruise predictive -+ eDaily/eMoover/S-eWay battery EV + ZF TraXon + HI-TRONIX 16AMT. - -## [3.70.0] - 2026-05-08 — Detroit Diesel ~20% ODIS, ~4,900 entries - -DD13/DD15/DD16/DD5/DD8 + DT8 legacy + DD5N natural gas + eCascadia -eAxle EV + DT12 AMT + Detroit Assurance Active Brake Assist 5 + IPM -+ Detroit Connect Virtual Technician. - -## [3.69.0] - 2026-05-08 — Cummins ~22% ODIS, ~4,900 entries (J1939 engine OEM) - -17 engine variants (ISB 6.7 / ISL 8.9 / ISX 15 / X15 / X12 / X10 / -B6.7 Ram HD / R2.8 Repower / QSB/QSL/QSX industrial / ISF2.8/3.8 + -X15N natural gas + X15H/B6.7H hydrogen ICE) + Accelera BTEV battery -EV + H Drive HEV/PHEV/BEV integration + Eaton Endurant 12-speed AMT -+ Allison 3000/4000 + ZF TraXon + DPF + SCR + DEF + DOC + ASC ammonia -slip cat + cooled EGR + 7th injector + INSITE. - -## [3.68.0] - 2026-05-08 — Tata Motors ~15% ODIS, ~4,800 entries - -Revotron 1.2T iCNG / 1.5 T-GDi + Kryotec 1.5/2.0 diesel + Nexon EV -LR + Punch EV ACTI.EV + Curvv EV + Tiago/Tigor EV + Harrier/Safari EV -+ Altroz EV + Sierra EV revival + Avinya/Atlas gen-3 + iRA Connected -+ ConnectNext + ACTI.EV OS. - -## [3.67.0] - 2026-05-08 — Mahindra ~15% ODIS, ~4,800 entries - -mHawk 2.2 diesel + mStallion 2.0/1.5/1.2 turbo petrol + Thar/Scorpio-N -4XPLOR 4WD low-range + diff lock + 6 terrain modes + XUV700 Z-Wheels -AMT + INGLO BE 6 / XEV 9e 800V + e-Verito legacy + Treo/Zoom 3-wheeler -EV + AdrenoX + Alexa Built-in. - -## [3.66.0] - 2026-05-08 — Lada (AvtoVAZ) ~15% ODIS, ~4,700 entries - -VAZ 1.5/1.6/1.8 8V/16V Evo + Renault H4Mk/H4Dt/K9K diesel + Niva -Travel/Legend 4WD low-range + Vesta NG/Sport/Cross/Aura + X-Ray -CMF-B-LS Renault platform + Largus e-EV + AvtoVAZ AMT robotised + -JATCO CVT/4AT + EnjoY Pro infotainment. - -## [3.65.0] - 2026-05-08 — GWM (Haval/Wey/Tank/ORA/Poer) ~18% ODIS, ~5,000 entries - -Lemon DHT 1.5/2.0 turbo PHEV + GW4N20/4N30 V6 + Tank 500/700 ladder- -frame + Tank 300 off-road + Tank turn (700) + 4WS crab-walk + 3 diff -locks + Wey Coffee PHEV + ORA Good/Lightning/Ballet Cat + Haval H6 -DHT-PHEV + Poer/Cannon pickups + Coffee OS. - -## [3.64.0] - 2026-05-08 — Geely Holding (9 brands) ~20% ODIS, ~5,000 entries - -Geely Auto + Lynk & Co + Zeekr + Lotus + Galaxy + Proton + Livan + -Geometry + Volvo (already done) sharing SEA/SEA-S/EMA platforms. -Zeekr 001/007/009/X/Mix 800V SiC + 5C Flash-Charge + Lotus Eletre/ -Emeya/Theory 1 (Eletre R 905hp) + Galaxy L7/E8 PHEV + Lynk & Co Z10 -+ Livan + Proton e.MAS 7 + E-DHT Hi-X PHEV. - -## [3.63.0] - 2026-05-08 — smart (Mercedes×Geely JV) ~22% ODIS, ~4,800 entries - -Legacy ForTwo/ForFour W453 + new #1/#3/#5 BEV3 SEA platform + Brabus -AWD 428 hp + smart Pilot + Beats Audio + Halo panoramic + smart AI -Cockpit Snapdragon. - -## [3.62.0] - 2026-05-08 — McLaren ~15% ODIS, ~4,900 entries - -M838T/M840T 3.8/4.0 V8 BiTurbo (720S/765LT/Senna/Speedtail KERS -1036hp/750S 740hp/W1 PHEV 1258hp/P1 903hp legacy) + M630 3.0 V6 -PHEV Artura 671hp + Artura Spider 700hp + Graziano 7-DCT + Artura -8-DCT no reverse (uses motor) + Proactive Chassis Control III + Race -Active Chassis Senna + Active DRS + nose lift + Variable Drift Control -+ Iris II 5G + future all-EV post-2030. - -## [3.61.0] - 2026-05-08 — Ferrari ~15% ODIS, ~5,000 entries - -F154 3.9 V8 BiTurbo (488/F8/Roma/Pista 711hp) + F154 4.0 V8 PHEV -SF90 1000hp + SF90 XX 1030hp + F163 3.0 V6 PHEV 296 GTB + F140 6.5 V12 -NA (812 Superfast/Competizione/Purosangue/12Cilindri 830hp/Daytona -SP3 840hp) + LaFerrari HY-KERS + Manettino 8-pos + E-Manettino PHEV + -SSC Side Slip Control + Active aero + F1-style DRS + first all-EV -2026 Elettrica. - -## [3.60.0] - 2026-05-08 — Aston Martin ~18% ODIS, ~4,800 entries - -AMG 4.0 V8 BiTurbo DBX707 697hp + Vantage 665hp + DB12 671hp + 5.2 -V12 BiTurbo DBS Superleggera + V12 Speedster + Vanquish 2024 835hp + -Valhalla PHEV + Valkyrie Cosworth 6.5 V12 NA 1000hp + ZF 8HP rear- -mount transaxle (DBS) + Mercedes Comand 8 + AML in-house infotainment -+ first all-EV 2026. - -## [3.59.0] - 2026-05-08 — Rolls-Royce ~32% ODIS, ~5,000 entries (BMW Group leakage) - -N74 V12 BiTurbo + Black Badge 600 hp + Spectre BMW i7-derived dual -motor + Black Badge 650 hp + Magic Carpet Ride + Planar Suspension + -Flagbearer cam + Satellite Aided Transmission + Starlight Headliner -1568 fibres + Shooting Stars + Starlight Doors (Phantom Tranquillity) -+ Spirit of Ecstasy retractable + 13-bit Bespoke (crystal door -handles, Droptail program, Bespoke Collective) + 18-channel Bespoke -Audio + Whispers app + iDrive 8 bespoke. - -## [3.58.0] - 2026-05-08 — Bentley ~32% ODIS, ~5,000 entries (VW Group leakage) - -W12 + V8 BiTurbo (incl. Continental GT Speed PHEV) + Bentley Dynamic -Ride 48V active anti-roll + Rotating Display + Breitling rotating -clock + 15-bit Mulliner bespoke (Naim for Bentley, Akrapovič, crystal -glass, Linley overmats, diamond knurling, Battue Pack) + Bentley -Smart Cabin Snapdragon + first all-EV PPE 2026. - -## [3.57.0] - 2026-05-08 — Dacia ~22% ODIS, ~4,800 entries - -Shared CLIP/Renolink with Renault. Y-light signature + StarklePack + -YouClip modular accessories + Extreme Pack 6-mode + Spring CN-platform -EV + E-TECH 140 hybrid Jogger/Bigster. - -## [3.56.0] - 2026-05-08 — Suzuki / Maruti pushed to ~22% ODIS, ~4,900 entries - -SDT / Suzuki Diagnostic Tool community. - -ALLGRIP 4-mode (Auto/Sport/Snow/Lock + Jimny 4L/4H) + SHVS Smart -Hybrid + Strong Hybrid + Jimny part-time 4WD low-range + 13 engine -variants (K14D Boosterjet + K14C SHVS + K10C 3-cyl + K15B/K15C SHVS + -Z14EET Strong + K12C Dualjet + DDiS diesel + eVX dual motor + e Vitara -27PL + **Across/Swace Toyota-badge hybrids + RAV4-based PHEV**). - -## [3.55.0] - 2026-05-08 — Mini (BMW Group) pushed to public-source ceiling (~30% ODIS, ~5,000 entries) - -BMW ISTA + Mini Connected community sources. Mini is a BMW sub-brand -sharing UKL2/FAAR/Spotlight platforms. - -### catalogs/mini.json -~5,000 entries. Built via shared library. - -#### Brand-specific captures -- **Go-Kart Mode** + 8 Mini Experiences (Green/Sport/Timeless Classic/ - Core/Vivid/Balance/Personal) -- **Mini Yours / Spotlight** 6-bit (Door LED projector + Union Jack - taillights + Piano Black + Chrome delete + Multitone Roof + LED - ambient patterns) -- 12 engine variants: B38 1.5T 3-cyl + B48 2.0T + JCW 306 hp + B58 3.0 - I6 JCW GP3 + B37/B47 diesel + Cooper SE legacy (BMW i3 driveline) + - **Cooper SE new (Spotlight CN platform) + Aceman dual + Countryman - E UKL2/FAAR PHEV+EV + Countryman SE ALL4 + JCW Electric** -- 7 transmissions incl. Aisin 8-speed Steptronic + ZF 8HP legacy + - 7-DCT + 6/7MT + ALL4 AWD coupling -- Mini Connected + **Mini OS 9 round OLED** - -Estimated ~30% ODIS — at BMW-shared/Mini-community ceiling. - -## [3.54.0] - 2026-05-08 — Renault / Alpine pushed to public-source ceiling (~25% ODIS, ~4,900 entries) - -CLIP / Renolink / Pyren / Ddt4all community sources. Covers Renault + -Alpine (now full sub-brand with A110/A290/A390 EV). - -### catalogs/renault.json -~4,900 entries (132 ECUs / 1,950 DIDs). Built via shared library. - -#### Brand-specific captures -- **MULTI-SENSE** 8-mode (Comfort/Sport/Eco/Perso/Neutral/Race - Megane RS/Snow/All-Road) -- **4CONTROL** 4-wheel steering coding (low-speed opposite phase + - high-speed same phase + sport aggressive — Megane RS/Espace/Rafale) -- **E-TECH** multi-mode clutchless hybrid 9-bit (PHEV + EV priority + - Hybrid auto + E-Save + Pure full EV + V2L on R5/R4 E-Tech) -- 19 engine variants: H5Ht 1.8 turbo Megane RS Trophy + H4Ht/H5Dt - Blue dCi + K9K + E-TECH 1.6/1.8 + **Renault 5 E-Tech AmpR Small + - Renault 4 E-Tech + Megane E-Tech CMF-EV + Scenic E-Tech + Alpine - A290 R5-based + Alpine A390 fastback SUV** + Zoe legacy + Kangoo - Z.E. + Master E-Tech -- 9 transmissions incl. EDC 7-DCT (Getrag) + EDC 6-DCT + E-TECH multi- - mode clutchless + 1/2-speed EV + X-Track 4WD -- HU gens: EASY LINK + **OpenR Link Android Automotive** + Alpine - telematics -- 5 routines incl. **4CONTROL calibrate + E-TECH multi-mode clutch - relearn + Alpine telemetry export** - -Estimated ~25% ODIS — at CLIP/Renolink-community ceiling. - -## [3.53.0] - 2026-05-08 — GM (Chevy/Buick/Cadillac/GMC) pushed to public-source ceiling (~30% ODIS, 5,254 entries) - -GDS2 / MDI / Tech2 community sources. Covers all 4 brands sharing GM -diagnostic topology: Chevrolet, Buick, Cadillac, GMC. - -### catalogs/gm.json — 28 → 5,254 entries -141 ECUs, 2,244 DIDs, 126 routines, 32 coding blocks (309 fields), 78 -adaptations, 134 actuator tests, 39 live PIDs, 2,460 DTC ext. - -#### Brand-specific captures -- **Super Cruise** 13-bit coding (hands-off lane change + hitched towing - + speed limit assist + driver attention camera + steering wheel - light bar + approved-route only + Super Cruise w/ trailer + max - speed) -- **Ultra Cruise** (Cadillac Celestiq) door-to-door -- **Hummer EV** 11-bit (CrabWalk diagonal + 4-wheel steering + Extract - Mode +6 inch lift + Watts to Freedom launch + Adaptive Air Ride + - Ultra Vision UFS underbody + off-road Super Cruise + Infinity Roof - sky panels + Power Pack outlets + V2H bidirectional + Terrain Mode) -- **C8 Corvette modes** 16-bit (Weather/Tour/Sport/Track/MyMode/Z-Mode - + 6-level PTM Performance Traction Management Wet→Race 2 + frunk - button + front-lift GPS memory + valet mode + Performance Data - Recorder) -- **DFM** Dynamic Fuel Management 17-cylinder modes -- **OnStar Connected Services** 19-bit (Automatic Crash Response + 5G - Wi-Fi + Smart Driver coaching + Google Built-in Maps/Assistant/Play - + Alexa + Phone-as-Key UWB + Trailering App + Connected Navigation - + my{Chevy/GMC/Cadillac/Buick} app + SiriusXM 360L) -- **Trailering** 12-bit (Max Trailering + Advanced Trailering System + - Transparent Trailer view + Trailer Camera 14 views + Hitch View + - Hitch Guidance + Jackknife Alert + Trailer Blind Zone + Trailer - TPMS + Super Cruise w/ trailer + max kg up to 11,500) -- **30 engine variants**: LS3 → LT2 mid-engine C8 Stingray + LT6 5.5 - flat-plane Z06 670 hp NA + LT7 5.5 twin-turbo ZR1 1064 hp + LT4 SC - Z06/ZL1 + L84/L87 DFM + LDD/LM2 3.0 Duramax I6 diesel + L5P 6.6 - Duramax V8 + L8T 6.6 V8 gas HD + Ecotec 2.0/2.7T + LF3 V6 BiTurbo - Blackwing + Ultium Lyriq/Hummer EV dual+tri/Silverado EV/Equinox EV/ - Blazer EV SS/Celestiq/Escalade IQ/BrightDrop Zevo -- **12 transmission variants**: 6L50/6L80/8L90/10L80/10L90 + - Tremec TR-9080 8-DCT (C8 Corvette) + Tremec 6/7MT manual + VT40 CVT - + Ultium 1-speed + Ultium 2-speed Hummer EV / Silverado EV + - ATC active transfer -- **HU gens**: Infotainment 3 Plus + VIP / Global B + VIP Ultium - Snapdragon + Ultifi software platform -- 19 routines incl. **Watts to Freedom launch test + Extract Mode + - CrabWalk + Corvette front-lift GPS save + PTM calibrate + DFM - cylinder mode relearn + MidGate calibrate Silverado EV + Performance - Data Recorder export + UFS underbody calibrate** -- 15 adaptations incl. **WTF default + Extract lift height + Ultium - max DC kW + V2H + V2L Power Pack** -- 11 actuator tests incl. **Watts to Freedom launch demo + CrabWalk + - Extract demo + Corvette front lift + Power Pack outlet test + - MidGate demo + Trailer 14-view demo** - -Estimated ~30% ODIS coverage — at the realistic GDS2/Tech2-community -ceiling. - -## [3.52.0] - 2026-05-08 — EV specialists (6 OEMs) pushed to public-source ceiling (~18-22%, 29,626 entries) - -Tesla, Rivian, Lucid, BYD, NIO, XPeng brought up to depth using -community sources (TeslaScan, Rivian Service Mode, Lucid community, -BYD e-Platform forums, NIO Banyan, XPILOT community). Built via -shared parameterized library. - -### catalogs/{tesla,rivian,lucid,byd,nio,xpeng}.json - -| OEM | Entries | ECUs | DIDs | Routines | Coding | Adapts | Acts | Live | DTC ext | -|---|---|---|---|---|---|---|---|---|---| -| Tesla | 4,985 | 135 | 2,005 | 114 | 30 (273 fields) | 70 | 132 | 39 | 2,460 | -| Rivian | 4,894 | 131 | 1,931 | 109 | 29 (252 fields) | 68 | 127 | 39 | 2,460 | -| Lucid | 4,902 | 134 | 1,941 | 107 | 28 (251 fields) | 67 | 126 | 39 | 2,460 | -| BYD | 4,995 | 131 | 2,030 | 111 | 29 (255 fields) | 68 | 127 | 39 | 2,460 | -| NIO | 4,934 | 132 | 1,967 | 112 | 29 (256 fields) | 68 | 127 | 39 | 2,460 | -| XPeng | 4,916 | 131 | 1,950 | 111 | 29 (253 fields) | 68 | 128 | 39 | 2,460 | - -#### Brand-specific captures - -**Tesla** — Autopilot 14-bit coding (Basic + Enhanced + FSD + FSD -Supervised + FSD Unsupervised + Navigate on Autopilot + Summon + -Smart Summon + Actually Smart Summon + Autopark + Traffic Light & -Stop Sign + Auto Lane Change + Highway Assist + Hands-off lane -change + max-speed-above-limit) + Acceleration Boost + Track Mode + -Drift Mode + Plaid Mode + Cheetah Stance + Premium Connectivity -11-bit (satellite maps + live traffic + streaming + Theater + -Caraoke + Arcade + Sentry Live View + in-car camera) + Sentry / Dog -Mode / Camp Mode / Bioweapon Defense / Hospital Mode (HEPA) + -Supercharger V3 350 kW handshake + Plaid tri-motor + Cybertruck -steer-by-wire + 8-camera autopilot calibrate + 15 engine variants -(Model S/3/X/Y/Cybertruck Cyberbeast + Roadster 2 + Semi + Robotaxi); -HU gens incl. AMD Ryzen + HW3/HW4 FSD computers + AI5 Cybercab. - -**Rivian** — Driver+ Enhanced Highway hands-off + R1 8-mode coding -(All-Purpose + Conserve + Sport + All-Terrain + Rock Crawl + Rally + -Drift + Soft Sand) + Camp Mode auto-level + V2L outlets + Gear -Tunnel coordinator + Tank Turn (legacy R1 hardware) + 12 engine -variants (R1T/R1S Quad Gen 1 Bosch / Gen 2 in-house + Tri Motor -Performance + R2/R3 + EDV); Quad-motor demo + Gear Tunnel test + -auto-level routines. - -**Lucid** — DreamDrive Pro 8-bit (Highway Assist + auto lane change -+ intelligent speed + Surround View + Smart Summon + Reverse Summon) -+ Wunderbox 8-bit (19.2 kW AC + 350 kW DC + V2L 9.6 kW + V2H + V2G + -ISO 15118 PnC + Plug & Charge + NACS adapter) + Sapphire tri-motor -1217 hp + Glasshouse canopy (Gravity) + 14-camera DreamDrive -calibrate + RacePak track telemetry + 9 engine variants (Air Pure/ -Touring/Grand Touring/Sapphire + Gravity Dual/Grand Touring/Sapphire -+ midsize Earth platform); 900V architecture metadata. - -**BYD** — Blade Battery LFP + Cell-to-Body integration + Super -e-Platform 1000V Flash Charge (10C / 1MW DC) + Yangwang quad-motor -e4 platform 9-bit (e4 quad + tank turn + floating mode + 3-wheel -drive limp home + crab-walk + jumping DiSus-A + DiSus-A/C/P -intelligent body control) + DiPilot City + 18 engine variants -(DM-i 1.5/2.0 PHEV + DM-o off-road PHEV Bao 5/8 + Atto 3/Seal/ -Dolphin/Han/Tang EV + Yangwang U7/U8/U9 quad-motor + Denza N7/D9 + -Song L + Seal 07). - -**NIO** — Power Swap 9-bit (BaaS subscription + 75 kWh LFP Power Up -Lite + 100 kWh ternary + 150 kWh semi-solid + Flexible swap any -size + Power Swap 4.0 station + V2G Charge & Discharge + lifetime -swap counter) + NAD with 33 sensors (Aquila + Adam) + NIO Pilot Plus -+ Navigate on Pilot Plus + City Pilot + NOMI in-car AI 5-bit (NOMI -Mate LLM + expressive face + voice only + English voice) + SkyRide -active suspension (ET9 900V) + Executive Class lounge seats + -Banyan 2.0 LLM-native OS + 11 engine variants (ES8/ES6/ES7/EC6/EC7/ -ET5/ET7/ET9 900V + ONVO L60 + Firefly + EVE). - -**XPeng** — XNGP / XPILOT 4.0 9-bit (XPILOT 2.5/3.0/4.0 tiers + -XNGP Highway/City/map-free + VPA Memory Park + ACC) + X9 4-wheel -steering with steer-by-wire 5-bit (crab-walk + U-turn + narrow park) -+ S5 Flash-Charge 480 kW 5C 6-bit (Robotic charging arm + 800V SiC + -silicon carbide inverter) + LeDar lidar + AeroHT eVTOL flying-car -module + Iron humanoid robot interface + XOS Tianji 5.0 + Orin XNPU -compute + 10 engine variants (P7/P5/G3i/G6/G9/X9 4WS/G7/MONA M03 + -AeroHT X2 flying car). - -Estimated ~18-22% ODIS coverage per OEM — proprietary CAN protocols -limit ceiling. NIO benefits from Power Swap public docs; BYD from -e-Platform 3.0 forums; Tesla from extensive community reverse -engineering. - -## [3.51.0] - 2026-05-08 — Tier 2 (5 OEMs) pushed to public-source ceiling (~22-25%, 24,921 entries) - -Volvo Cars, Polestar, Subaru, Mazda, Nissan/Infiniti, Mitsubishi -brought up to depth using community sources (VIDA/DiCE, SSM, M-MDS, -CONSULT III+, MUT-III). Built via shared parameterized library to -keep depth pattern consistent with the prior 10 OEMs. - -### catalogs/{volvo,polestar,subaru,mazda,nissan,mitsubishi}.json - -| OEM | Entries | ECUs | DIDs | Routines | Coding | Adapts | Acts | Live | DTC ext | -|---|---|---|---|---|---|---|---|---|---| -| Volvo | 5,034 | 127 | 2,078 | 108 | 28 (241 fields) | 68 | 126 | 39 | 2,460 | -| Polestar | 4,921 | 127 | 1,970 | 107 | 27 (242 fields) | 66 | 125 | 39 | 2,460 | -| Subaru | 4,925 | 131 | 1,961 | 111 | 29 (251 fields) | 68 | 126 | 39 | 2,460 | -| Mazda | 4,960 | 122 | 2,008 | 111 | 28 (245 fields) | 66 | 126 | 39 | 2,460 | -| Nissan | 5,015 | 125 | 2,058 | 109 | 29 (250 fields) | 68 | 127 | 39 | 2,460 | -| Mitsubishi | 4,987 | 127 | 2,030 | 109 | 28 (250 fields) | 68 | 126 | 39 | 2,460 | - -#### Brand-specific captures - -**Volvo** — Pilot Assist + Care Key max-speed limiter + Four-C -continuously controlled chassis + IntelliSafe + Connected Safety + -EX90 LiDAR + dual-chamber air suspension + integrated child boosters; -17 engine variants (B4204T turbo + Drive-E + B6304T V6 + B8444S V8 -Yamaha legacy + EV P2/P3/EX30/EX90/ES90); Pilot Assist 9-bit coding -(hands-off warn + emergency stop + oncoming lane mit + run-off road -mit), Care Key max-speed adaptation 50-210 km/h. - -**Polestar** — Performance Pack OTA unlock (+25 kW) + Öhlins DFV -manually adjustable damping + Akebono brakes + front Brembo + gold -seat belts + 50/50 dual-motor split + Track telemetry recorder; 11 -engine variants (Polestar 1 PHEV 3-motor + Polestar 2/3/4/5/6 incl. -BST 270/230 + 800V Polestar 5/6); Performance Pack 9-bit coding + -Öhlins calibrate routine. - -**Subaru** — EyeSight stereo camera (9-bit: ACC + Pre-Collision Brake -+ Lane Keep + Sway Warn + Lane Departure + Throttle Mgmt + Emergency -Lane Keep + DriverFocus) + DriverFocus distraction mitigation + DCCD -Driver Controlled Center Diff (Auto / Auto- / Auto+ / Manual modes) -+ X-MODE 4-mode coding (Snow/Dirt + Deep Snow/Mud + Hill Descent + -Normal) + Symmetrical AWD + STARLINK; 11 engine variants (EJ257 + -FA20DIT + FA24F WRX 2022 + FB20/25 + FA20 BRZ + e-Boxer + Solterra). - -**Mazda** — Skyactiv-X SPCCI Spark-Controlled Compression Ignition -calibrate + GVC Plus G-Vectoring Control + Kinematic Posture Control -+ i-Activsense (9-bit: Smart Brake Support + Distance Recognition + -Mazda Radar Cruise + Lane Dep + LKA + BSM + RCTA + DAA + Cruising -Traffic Support) + Wankel Range Extender (MX-30 R-EV); 15 engines -(Skyactiv-G/X/D + Inline-6 3.3 turbo/PHEV/diesel CX-60/70/90 + e- -Skyactiv R-EV + EZ-6 EV). - -**Nissan/Infiniti** — ProPILOT Assist 2.0 hands-off (9-bit) + Navi- -link + e-4ORCE 5-mode coding + e-Pedal Step / one-pedal drive + -Direct Adaptive Steering DAS + Intelligent Around-View; 15 engines -(VR30DDTT Q50 Red Sport + VR38DETT GT-R + VC-Turbo 2.0 variable -compression + VC-Turbo 1.5 3-cyl + e-POWER serial hybrid + Leaf Plus -+ Ariya e-4ORCE); VC-Turbo compression-ratio relearn routine. - -**Mitsubishi** — S-AWC Super All-Wheel Control 8-bit (Tarmac/Gravel/ -Snow/Mud + AYC Active Yaw Control + ASC + Sport) + Outlander PHEV -9-bit (EV priority + save + charge + V2H CHAdeMO + V2L 1500W + -twin-motor 4WD + Power Drive electric AWD + target save SOC) + Twin -Clutch SST 6-DCT (Evo X) + DCCD-style legacy; 14 engines (4B11T Evo -X + 4G63T Evo IX + 4B40T Eclipse Cross + Outlander PHEV motors + -i-MiEV legacy). - -Estimated ~22-25% ODIS coverage per OEM — at the realistic -SSM/M-MDS/CONSULT III+/MUT-III/VIDA-community ceiling. - -## [3.50.0] - 2026-05-08 — Stellantis (14 brands) pushed to public-source ceiling (~30% ODIS, 5754 entries) - -Same depth-pattern as the prior 9 OEMs, applied to Stellantis via -wiTech / Mopar / Multiecuscan + FCA-PSA forums. Covers all 14 brands -(Chrysler/Jeep/Dodge/Ram/Fiat/Alfa Romeo/Maserati/Peugeot/Citroën/ -Opel/Vauxhall/DS/Lancia + Ducati on commercial side) since they share -wiTech topology post-merger. - -### catalogs/stellantis.json — 28 → 5,754 entries - -| Section | v3.39 | v3.50 | -|---|---|---| -| ECUs | 8 | **166** | -| DIDs | 20 | **2,633** | -| Routines | 0 | **159** | -| Coding blocks | 0 | **34 (322 fields)** | -| Adaptations | 0 | **81** | -| Actuator tests | 0 | **168** | -| Live PIDs | 0 | **53** | -| DTC extended-data | 0 | **2,460** | - -#### ECUs (+158) -wiTech bus map covering ICE + 4xe PHEV + STLA Large/Medium/Frame BEV -+ Ramcharger REEV: powertrain (ECM + bank-2 V8 Hemi split + ZF 8HP/ -9HP TCM + transfer + HV battery + front+rear MCU + OBC + LDC + VCMS -STLA + front+rear motor + **SRT/Demon/Hellcat drive mode coordinator** -+ active exhaust + **Launch Control + Line Lock** + active engine -mount Quadrifoglio), chassis (ABS + SAS + EPS + EPB + TPMS + ORC + -occupancy + Active Park + **Bilstein Adaptive Damping + Active Roll -Control Quadrifoglio + rear-axle steering Maserati + Q4 torque -vectoring Alfa + Quadra-Lift air suspension Jeep/Ram + iBooster + -Trailer Sway Control**), **Jeep off-road suite** (Selec-Terrain + -Wade Sensing Wrangler/Gladiator + SelecSpeed Crawl + HDC + NV245 -transfer + **Tru-Lok rear+front diff lock Rubicon** + **electronic -sway bar disconnect Rubicon**), ADAS (master + Forward Facing Camera -+ Forward Facing Radar + rear radar L+R + 4 corner radars + Surround -View 4-cam + Driver Status Monitor + **Night Vision Wagoneer/Grand -Wagoneer** + sonar + traffic sign), body (CGW + BCM + Uconnect 5 -cluster + dual ATC + 4 doors + 4 seats Wagoneer Executive Class -24-way + 2 mirrors + steering column + 2 sliding doors Pacifica + -liftgate + pano + convertible Wrangler/Spider/124 + fuel/charge flap -+ **frunk BEV** + **power tonneau Ram/Gladiator**), **Pixel LED -headlights** + LED tails + welcome signature, **Uconnect 5 -Snapdragon** + passenger display Wagoneer + 2 rear displays + ** -McIntosh / Harman Kardon / Alpine / Sonus Faber Maserati** premium -amp + tuner + 5G TCU + eCall + SiriusXM Guardian, Passive Entry + -SKIM + alarm + tilt + glass-break, HV/EV thermal (heat pump + PTC + -HV scroll compressor + battery heater + chiller + valve block + 2× -aux pumps + **Range Extender Ramcharger / Jeep 4xe REEV**), heated -steering + 4× seat climate + HUD + ambient + Qi + 2× massage + auto- -wiper + **N95 cabin air filter Pacifica** + ionizer, trailer Tow -Package + power hitch + Defender aux battery + **center console -fridge** + **Power Running Boards Ram/Wagoneer** + **RamBox** -lockable + Trailer view camera + **multifunction tailgate Ram** + -bed lights, OTA + HSM + Ethernet + 5 domain controllers + Car2X + -**Uconnect / Mopar Owner app gateway** + DAB+/HD Radio + Face Connect -+ **UWB Phone-as-Key**. - -#### DIDs (+2,613) -- 26 generic UDS DIDs incl. Mopar part no, FCA calibration, wiTech ID, brand code (CHR/JEE/DOD/RAM/FIA/ALF/MAS/PEU/CIT/OPL/VAU/DS/LAN). -- 85 ECM engine DIDs (RPM/torque/MAP/MAF/lambda B1+B2, 2× turbo + **supercharger rpm Hellcat/Demon + supercharger boost**, 2× wastegate, **MDS state + count + active minutes Hemi 4-cyl deact**, VVT intake+exhaust B1+B2, idle, drive mode 9 modes, ISS, **Launch Control + Line Lock count Demon + Red Eye/Demon mode flag**, max-RPM/speed/oil-temp/G lifetime, **DPF soot + regen count + distance since regen + active regen**, **DEF/AdBlue + remaining km + NOx in/out**, **4xe PHEV charge mode + EV distance + Hybrid distance + eTorque 48V assist + state**, SRT chiller water temp). -- 64 per-cylinder (1-8) — Hemi V8 + V6 Quadrifoglio + 4xe V6. -- 256 engine variant DIDs — 32 Stellantis engines × 8 fields (Hemi 5.7 / 6.4 392 / 6.2 Hellcat / Hellcat Redeye / **Demon 170 V8** / Hurricane 3.0 SO+HO I6 / Pentastar 3.6 V6 + eTorque + 4xe / EcoDiesel V6 / Alfa 2.0/2.2/2.9 V6 Quadrifoglio / 1.3/1.4/2.4 MultiAir / **Maserati Nettuno V6 Twin Combustion** / V8 / PSA PureTech 1.0/1.2/1.6 + hybrid / BlueHDi 1.5/2.0 / Opel 1.4T / PHEV 1.6 PSA / **STLA Large single/dual/Banshee SRT** / **STLA Medium e-3008/e-Avenger** / **STLA Frame Wagoneer S** / STLA Smallcar e-208/Corsa-e). -- 240 transmission variants — 15 trans gens × 16 fields (ZF 8HP50/70/75/95 + 9HP48 + EAT8/EAT6 PSA + 6-DCT + 6MT + STLA front/rear reducer + **2-speed Charger Daytona Banshee** + NV245/Rock-Trac/Quadra-Drive II transfers). -- 64 Uconnect head-unit gens (Uconnect 4 + 5 + PSA NAC + Alfa/Maserati Connect) × 16 fields. -- 41 ABS + chassis DIDs (Adaptive Damping state + ARC state + rear steer angle + 4× ride height + **4× Quadra-Lift pressure + compressor + mode 5-position Aero/Normal/OR1/OR2/Park** + Q4 split + transfer split + low-range + **front+rear Tru-Lok state + sway bar disconnect state** + wade depth + max-safe wade + HDC + crawl + Selec-Terrain mode). -- 64 per-wheel ABS/TPMS (4 × 16) + sensor IDs + camber/toe. -- 33 HV battery DIDs incl. **800V architecture STLA Large** + chemistry + pyrofuse + lifetime charged/discharged. -- 192 per-cell V (STLA Large max). -- 256 per-module (32 × 8 fields). -- 38 motor/MCU DIDs (front + rear, 19 each, incl. **PowerShot active Charger Daytona + overboost remaining**). -- 31 OBC + VCMS DIDs incl. **V2L active/kW/lifetime + REEV runtime + REEV fuel burned + REEV target SOC** (Ramcharger). -- 35 ADAS (Adaptive Cruise + Stop & Go + Lane Keep + AEB + Highway Assist + **Hands-Free Active Drive Level 3 STLA AutoDrive** + Intersection Assist + DSM + Blind Spot + Rear Cross Path + Surround View + **Trail Camera Wrangler/Gladiator** + Night Vision + Active Park + remote parking + **Trailer Reverse Steering Control** + Swerve). -- 96 ADAS object stack (12 × 8). -- 38 cluster + **Performance Pages** (lap timer + best 0-60/0-100/QM + best 60-0 braking + max long/lat G + **pitch + roll + wheel articulation + altitude + max wade + off-road minutes + low-range minutes + diff-lock minutes + sway-disconnect minutes + Launch + Line Lock + PowerShot + Drift mode minutes + Track minutes**). -- 256 last-32-trip × 8. -- 80 driver coaching (20 × 4 windows incl. off-road + wade + PowerShot). -- 46 per-bulb hours (Pixel LED + welcome + race-track brake + aux off-road + bed lights). -- 32 ambient zones. -- 64 per-key (8 × 8) incl. Passive Entry + **UWB Phone-as-Key**. -- 36 per-camera (9 × 4) incl. Trail + Night Vision IR. -- 60 premium audio incl. **15-band parametric EQ × 3** (gain/freq/Q). -- 132 per-ECU programming (33 × 4). -- 52 Uconnect/SiriusXM Guardian/Mopar Owner subscription incl. **Free2move EV Route Planner + Free2move Charge + Mopar Owner app + Uconnect Market in-car commerce + SiriusXM with 360L**. -- 20 bus topology (C/B/Diag/Chassis CAN + LIN + CAN-FD + Ethernet + AVB + SOME/IP). -- 32 quad-zone HVAC + 96 user profiles + 104 service history (26 × 4 incl. ATF + Tru-Lok diff oil + supercharger oil + DPF + DEF) + 43 vehicle metadata (incl. **SRT + Quadrifoglio + Trail Rated + Rubicon + TRX + DT + Rebel + brand 14 codes + STLA platform 4 codes + 4xe PHEV + eTorque + V2L + Mopar pack**). - -#### Routines (+159) | Coding blocks (34 / 322 fields) | Adaptations (81) | Actuator tests (168) | Live PIDs (53) | DTC ext (2,460) -Engine adapt resets + **MDS relearn + DPF forced regen + DEF priming + Launch + Line Lock arm + Red Key/Demon unlock + supercharger test + eTorque init**, ZF + EAT8 + Quick Learn, transfer + Q4 adapts, per-wheel ABS + SAS + yaw zero + TPMS + EPB workshop, **Bilstein Adaptive Damping + ARC + rear-axle steer + Quadra-Lift + ride-height + iBooster calibrations**, **Jeep off-road**: Selec-Terrain init + Wade Sensing + crawl + HDC + center diff + **front+rear Tru-Lok + sway bar disconnect tests**, BCM + window + mirror + sunroof + convertible + liftgate + sliding doors L+R + frunk + tonneau + **Power Running Boards + RamBox + multifunction tailgate** calibrations, ATC basic + heat-pump self-test, headlight aim L+R + Pixel LED calibrate, front camera + radar align + 4 corner/rear radars + Surround View + Trail Camera + Night Vision + DSM + sonar, cluster + mileage align + **Performance Pages reset**, HV cell balance + capacity + isolation + contactor + pre-charge + pyrofuse, motor resolver zero + inverter self-test, OBC + LDC + VCMS + thermal + charge flap, **Range Extender self-test Ramcharger**, exhaust flap test, 23 module-replacement procedures incl. **SKIM**, key + **SKIM relearn + Phone-as-Key UWB pair**, OTA check/install/rollback, HSM provision/zeroize, 5 domain self-tests + Ethernet + Car2X, seat init + massage calibrate + Qi + HUD + **Uconnect/SiriusXM Guardian refresh + welcome animation load + Ramcharger REEV test**. - -Coding blocks: BCM + door + alarm + **Pixel LED features** (matrix HB EU + R/T runway projection Daytona + lane lighting + intersection lighting), tail signature (Daytona Charger illuminated strip), ADAS Lane (**Hands-Free Active Drive Level 3 STLA AutoDrive**), ACC (Stop & Go + predictive + curve speed), AEB (intersection + reverse + swerve), Blind Spot + Rear Cross Path, ATC (heat pump + ionizer + N95 cabin filter Pacifica), EV charge (CCS1/2 + NACS + V2L + ISO 15118 PnC + 11/22kW AC + 200/350kW DC + 800V STLA Large + Free2move EV Route Planner), **SRT/Demon/Hellcat features** (Track + Drag + Drift + Custom + Snow + Tow + Eco + Valet + Launch + **Line Lock + Torque Reserve + Trans Brake + Red Key + PowerShot Charger Daytona + Performance Pages + G-meter**), active exhaust (legal quiet mode), **Uconnect 5 features** (CarPlay + AA + Connected + McIntosh + Harman Kardon + Alpine + Sonus Faber Maserati + passenger + rear displays Wagoneer + Uconnect Market), cluster (HUD + **Performance Pages overlay + Off-Road Pages overlay Jeep + articulation + wade depth overlays**), Passive Entry + UWB Phone-as-Key, trailer + **Trailer Reverse Steering Control** + power hitch, **Jeep off-road features** (Selec-Terrain + auto + Wade Sensing + crawl + HDC + low-range + front+rear Tru-Lok + sway bar disconnect + Trail Camera + 7 modes), Quadra-Lift + chassis (rear-wheel steer Maserati + ARC Quadrifoglio + Adaptive Damping + Q4 + Dynamic Response + Predictive Terrain Response), seat climate dr (24-way memory), sliding doors Pacifica, convertible, panoramic sunroof, HUD, Qi, DSM (gaze tracking HFAD), OTA (staged rollout), Crypto/HSM, **Uconnect/SiriusXM Guardian/Mopar Owner** (Phone-as-Key + Free2move EV Route Planner + Free2move Charge + voice + Alexa + Uconnect Market + Mopar Owner app + region NA/EU/CN/SA), **Ram truck features** (RamBox + multifunction tailgate + bed lights + cargo + trailer cameras + Power Running Boards + auto air lift + TRX off-road), frunk BEV, **Ramcharger / Jeep 4xe REEV** (Auto + EV priority + Hybrid + battery charge + save modes), N95 cabin filter, aux battery (Defender/Wrangler dual-battery + auto disconnect on low SoC). - -Adaptations: Engine (incl. **MDS default + Launch max RPM + Red Key default + exhaust flap policy**) + ZF + ABS + Adaptive Damping + Quadra-Lift heights (off-road/aero/park) + rear steer + Q4 + **Selec-Terrain default + Wade warn depth + crawl + HDC default speeds** + Lane Keep + AEB + ACC + Highway Assist + Hands-Free Drive + Pixel LED HB + welcome animation + comfort + N95 default + EV (DC/AC targets + 350kW + ISO 15118 PnC + V2L + AVAS) + **Ramcharger REEV target SOC + default mode** + OTA + HSM + massage. Actuator tests incl. **CDC 4× dampers + ARC actuators + rear-steer + 4× Quadra-Lift valves + center diff + front+rear Tru-Lok + sway bar disconnect + frunk + tonneau + Power Running Boards + RamBox + tailgate step + Pixel LED anim + supercharger test + Line Lock demo + PowerShot demo + Range Extender start/stop**. Live PIDs incl. engine + DPF + DEF + MDS state + supercharger + eTorque + HV battery + front+rear motor + OBC + V2L + REEV runtime + ADAS + chassis (pitch/roll/articulation/wade depth/Selec-Terrain mode). DTC ext: broad P/B/U/C codes × 4-6 record types incl. environmental_data + freeze_frame_template. - -Estimated ~30% ODIS coverage — at the realistic wiTech/Mopar-community ceiling. Higher coverage requires wiTech 2 dealer license. - -## [3.49.0] - 2026-05-08 — JLR (Jaguar Land Rover) pushed to public-source ceiling (~30% ODIS, 5465 entries) - -Same depth-pattern as the prior 8 OEMs, applied to JLR via SDD / -Pathfinder community + JLRTechInfo. Covers Jaguar (XE/XF/F-Pace/E- -Pace/I-Pace/F-Type) + Land Rover (Defender/Discovery/Discovery -Sport/Evoque) + Range Rover (RR/Sport/Velar/Range Rover Electric). - -### catalogs/jlr.json — 28 → 5,465 entries - -| Section | v3.39 | v3.49 | -|---|---|---| -| ECUs | 8 | **154** | -| DIDs | 20 | **2,396** | -| Routines | 0 | **150** | -| Coding blocks | 0 | **32 (303 fields)** | -| Adaptations | 0 | **75** | -| Actuator tests | 0 | **149** | -| Live PIDs | 0 | **49** | -| DTC extended-data | 0 | **2,460** | - -#### ECUs (+146) -SDD bus map covering ICE + MHEV + PHEV + BEV (I-Pace, EMA, Range -Rover Electric): powertrain (ECM + bank-2 V8 split + ZF 8HP TCM + -transfer case + HV battery + front+rear MCU + OBC + LDC + **VCMS** -EMA + front+rear motor + Dynamic Response + active exhaust flap + -Launch Control + active engine mount), chassis (ABS + SAS + EPS + -EPB + TPMS + SRS + occupancy + Park Pilot + **Adaptive Dynamics CDC -+ Active Roll Control / Dynamic Response Pro + rear-wheel steer L460 -+ Active e-Diff + 4-corner cross-linked air suspension + ride height -+ Continental MK C1 iBooster + Trailer Stability**), **Land Rover -off-road** (Terrain Response 2 + Wade Sensing + ATPC + HDC + center -diff lock + rear diff lock + 2-speed low-range), ADAS (master + front -camera + radar + rear radar L+R + 4 corner radars + **ClearSight -ground-view 360 front + rear** + **ClearSight camera mirrors L+R -Range Rover** + driver attention + **ClearSight Interior Rear-View -Mirror** + sonar + traffic sign), body (CGW + BCM + 12.3" cluster + -dual FATC + 4 doors + 4 seats Executive Class 22-way + 2 mirrors + -steering column + powered tailgate + **inner tailgate Range Rover -split** + pano roof + **F-Type convertible** + fuel/charge flap), -**Pixel LED Digital headlights** + **animated taillights** + welcome -signature, **Pivi Pro** (Snapdragon) + passenger display + 2 rear -displays Range Rover + **Meridian Signature Sound 3D** + premium amp -+ tuner + 5G TCU Pivi Connect + eCall + Stolen Vehicle Locator, -**Activity Key** waterproof wristband + smart key + immobilizer + -alarm + tilt + glass-break, HV/EV thermal (heat pump + PTC + scroll -compressor + battery heater + chiller + valve block + 2× aux pumps), -heated steering + 4× seat climate + **Hot Stone massage 4 seats** + -HUD + **CAIL ambient** + Qi + auto-wiper + **Cabin Air Purification -Pro PM2.5** + ionizer, trailer (**Advanced Tow Assist**) + power -hitch + Defender aux battery + **center console fridge**, OTA + HSM -+ Ethernet + 5 domain controllers + Car2X + InControl/Pivi Connect -TCU + DAB+/HD Radio + **Activity Key inductive charger** + UWB -**Phone-as-Key**. - -#### DIDs (+2,376) -- 25 generic UDS DIDs incl. JLR part no, calibration, SDD ID, Solihull/Halewood/Castle Bromwich/Nitra factory codes. -- 75 ECM engine DIDs (RPM/torque/MAP/MAF/lambda B1+B2, 2× turbo + **supercharger rpm 5.0L SC**, 2× wastegate, VCT intake+exhaust B1+B2, idle, drive mode 4 modes, ISS, **Launch Control state+count**, max-RPM/speed/oil-temp lifetime, full-load + overboost seconds, **DPF soot + regen count + distance since regen + active regen flag**, **AdBlue level + remaining km + NOx in/out**, **MHEV 48V belt-starter assist torque + recuperation kW + state**, PHEV charge mode). -- 64 per-cylinder (1-8) — V8 5.0 SC + BMW N63 4.4 V8 + 3.0 I6. -- 176 engine variant DIDs — 22 JLR engines × 8 fields (Ingenium 2.0 P200/P250/P300 gas + D150/D180/D200 MHEV/D240 diesel + 3.0 I6 D300/D350 MHEV diesel + 3.0 I6 P360/P400 MHEV gas + 3.0 P510e/P550e PHEV, **AJ-V8 5.0L SC + 5.0L SVR**, **BMW N63 4.4L V8 BiTurbo Range Rover**, I-Pace dual + EMA single/dual + Range Rover Electric, legacy V6/V8 TDV6/SDV8). -- 176 transmission variants — 11 trans gens × 16 fields (ZF 8HP50/70/76/95, 9HP, 6MT, I-Pace front+rear reducer, EMA 2-speed, transfer 4WD + 2-speed low-range). -- 48 head-unit gens (InControl Touch Pro Duo + Pivi Pro + Pivi Connect 5G) × 16 fields. -- 41 ABS + chassis DIDs (Adaptive Dynamics state + ARC state + rear-wheel steer angle + 4× ride height + 4× air-susp pressure + compressor + **cross-link state 4-corner** + e-Diff split + e-Diff oil temp + transfer split + low-range state + center+rear diff lock + **wade depth + max-safe wade** + HDC + ATPC + Terrain Response mode). -- 64 per-wheel ABS/TPMS (4 × 16) + sensor IDs + camber/toe. -- 33 HV battery DIDs incl. **800V architecture (EMA / Range Rover EV)** + chemistry + pyrofuse + lifetime charged/discharged. -- 144 per-cell V (EMA max). -- 256 per-module (32 × 8 fields). -- 38 motor/MCU DIDs (front + rear, 19 each). -- 25 OBC + VCMS DIDs. -- 34 ADAS (Adaptive Cruise + Intelligent Cruise + Lane Keep + Highway Assist + Steering Assist + AEB + driver condition + Blind Spot + Rear Traffic + **ClearSight Ground View** + **ClearSight Interior Rear-View Mirror** + Park Pilot + remote parking + intersection + swerve + Trailer Reverse Assist). -- 96 ADAS object stack (12 × 8). -- 33 cluster + **off-road telemetry** (lap timer + best 0-60/0-100/QM + **pitch + roll + wheel articulation + altitude + max wade depth + off-road minutes + low-range minutes + diff-lock minutes + Terrain Response use distribution + ATPC distance + HDC distance**). -- 256 last-32-trip × 8. -- 80 driver coaching (20 × 4 windows incl. off-road + wade + ATPC distance). -- 44 per-bulb hours (Pixel LED + signature DRL + animated tail + welcome). -- 36 CAIL ambient zones (incl. **headliner pano + d-pillars + canopy + mood lighting**). -- 64 per-key (8 × 8) incl. **Activity Key + Phone-as-Key UWB**. -- 32 per-camera (8 × 4) incl. ClearSight mirrors + Interior Rear-View. -- 60 Meridian Signature Sound 3D incl. **15-band parametric EQ × 3** (gain/freq/Q). -- 132 per-ECU programming (33 × 4). -- 50 InControl/Pivi Connect subscription incl. **EV Route Planner + JLR Charging Service + Connected Navigation Pro + OTA feature unlock**. -- 20 bus topology (PT/Chassis/Body/Info CAN + LIN + CAN-FD + Ethernet + AVB + SOME/IP). -- 32 quad-zone HVAC + 96 user profiles + 104 service history (26 × 4 incl. ZF 8HP fluid + e-Diff + supercharger oil + DPF service + AdBlue refill) + 39 vehicle metadata (incl. **SVR + SVAutobiography + Dynamic Pack + First Edition + brand JAG/LR/RR + battery size + chemistry + DC max kW + motor count + EMA platform flag + V2X capable + Meridian Signature 3D fitted + Executive Class seats**). - -#### Routines (+150) -ECM adapt resets (idle, throttle, misfire, kat, lambda, VCT, oil-pump, -starter) + battery register + oil/inspection/brake fluid resets + -**DPF forced regen + replace reset + AdBlue priming + Launch Control -calibrate + supercharger test 5.0 SC + 48V belt-starter init**, ZF -basic + oil reset + Quick Learn, transfer + e-Diff adapt, per-wheel -ABS bleed + pump + SAS + yaw zero + TPMS relearn + EPB workshop, -**Adaptive Dynamics CDC + Active Roll Control + rear-wheel steer + -4-corner air-susp + ride-height calibrations**, iBooster, **Land -Rover off-road** (Terrain Response init + Wade Sensing calibrate + -ATPC + HDC calibrations + center diff + rear diff lock + low-range -tests), BCM + window + mirror + sunroof + **F-Type convertible top** -+ tailgate + **inner tailgate Range Rover split** calibrations, FATC -basic + heat-pump self-test, headlight aim L+R + Pixel LED Digital -calibrate + animated taillight init, front camera dynamic + static + -radar align + 4 corner/rear radars + **ClearSight 360 + camera mirrors -L+R + Interior Rear-View** calibrations + driver attention + sonar -front+rear, cluster + mileage align + **off-road telemetry reset**, -HV cell balance + capacity + isolation + contactor + pre-charge + -pyrofuse, motor resolver zero (front+rear) + inverter self-test, -OBC + LDC + VCMS + thermal + charge flap, exhaust flap test, 22 -module-replacement procedures, key + immobilizer relearn + **Activity -Key wristband pair + Phone-as-Key UWB pair**, OTA check/install/ -rollback, HSM provision/zeroize, 5 domain self-tests + Ethernet + -Car2X, seat init dr+pa + **Hot Stone calibrate** + Qi + HUD calibrate, -**InControl/Pivi Connect refresh + welcome animation load**. - -#### Coding blocks (32 / 303 fields) -BCM general (UWB approach + walk-away), door extended (**flush door -handles Range Rover + auto-extend at speed**), alarm zones, **Pixel -LED Digital features** (Digital LED HD + matrix HB + dynamic signature -DRL + lane lighting + intersection lighting + country-specific), -animated taillight, ADAS Lane (Highway + Emergency Assist), Adaptive -Cruise (Intelligent Cruise Stop&Go + predictive + curve speed + -**Connected Navigation ACC**), AEB (intersection + reverse + swerve), -Blind Spot + Rear Traffic + **ClearSight Interior Rear-View auto**, -FATC (heat pump + ionizer + **Cabin Air Purification Pro PM2.5**), -EV charge (CCS1/2 + NACS + V2G/V2L/V2H + ISO 15118 PnC + 22kW AC + -200/350kW DC + **800V EMA / Range Rover Electric** + EV Route -Planner), Sport/Dynamic mode (**Dynamic Pro SVR + Track mode F-Type -+ Predictive Speed Control**), active exhaust (legal quiet mode), -**Pivi Pro features** (CarPlay + AA + InControl + Meridian Signature -3D + Meridian Premium + passenger display + rear displays Range -Rover), cluster (HUD + sport layout F-Type + **off-road layout LR + -articulation overlay + wade depth overlay**), smart key + Activity -Key + UWB, trailer + **Advanced Tow Assist** (knob steering), -**off-road features Land Rover** (Terrain Response 2 + auto + Wade -Sensing + ATPC + HDC + low-range + center+rear diff lock + ClearSight -Ground + articulation + pitch & roll indicators + 7 modes incl. rock -crawl), air suspension + chassis (cross-linked 4-corner + rear-wheel -steer + ARC + Adaptive Dynamics + e-Diff + Dynamic Response Pro + -Predictive Terrain Response), driver seat climate + **Hot Stone -massage** (Relaxation mode), F-Type convertible, panoramic sunroof, -HUD (AR overlay), Qi, driver attention (gaze tracking), OTA (staged -rollout), Crypto/HSM (debug locked), **InControl / Pivi Connect** -(Phone-as-Key + EV Route Planner + JLR Charging Service + voice + -Alexa + region NA/EU/CN/UK), **ClearSight features** (Ground View + -Interior Rear-View + camera mirrors + 360 + trailer view + transparent -hood), tow hitch, **Cabin Air Purification Pro** (PM2.5 + ionizer + -CO2 management + auto-recirc on smog), fridge / cool box. - -#### Adaptations (75) + Actuator tests (149) + Live PIDs (49) + DTC ext (2,460) -Engine (incl. **Launch max RPM + active exhaust policy + ISS min -coolant**) + ZF + ABS + Adaptive Dynamics + air-susp (off-road/ -access/loading heights) + rear steer + e-Diff + **Terrain Response -default + Wade warn depth + ATPC default speed + HDC default speed** -+ Lane Keep + AEB + ACC + Highway Assist + Pixel LED HB + welcome -animation + comfort + Cabin Air Pro + EV (DC/AC targets + 350kW + -ISO 15118 PnC + AVAS) + OTA + HSM + Hot Stone + off-road camera auto; -full per-actuator tests incl. **CDC 4× dampers + ARC actuators + rear- -steer + 4× air-susp valves + center+rear diff lock + low-range engage -+ inner tailgate Range Rover split + Pixel LED anim L+R + Hot Stone -+ supercharger test + Activity Key charger + fridge**; live engine + -DPF + AdBlue + MHEV + supercharger + HV battery + front+rear motor + -OBC + ADAS + chassis (ride heights + **pitch + roll + articulation + -wade depth + Terrain Response mode**) PIDs; broad P/B/U/C codes × -4-6 record types incl. environmental_data + freeze_frame_template. - -Estimated ~30% ODIS coverage — at the realistic SDD/Pathfinder- -community ceiling. Higher coverage requires Pathfinder dealer license. - -## [3.48.0] - 2026-05-08 — HMG (Hyundai/Kia/Genesis) pushed to public-source ceiling (~28% ODIS, 5537 entries) - -Same depth-pattern as the prior 7 OEMs, applied to HMG via GDS / KDS -community + E-GMP forums. Covers all three brands (Hyundai, Kia, -Genesis) since they share GDS topology and most ECUs. - -### catalogs/hmg.json — 30 → 5,537 entries - -| Section | v3.39 | v3.48 | -|---|---|---| -| ECUs | 8 | **153** | -| DIDs | 22 | **2,481** | -| Routines | 0 | **145** | -| Coding blocks | 0 | **32 (295 fields)** | -| Adaptations | 0 | **76** | -| Actuator tests | 0 | **145** | -| Live PIDs | 0 | **45** | -| DTC extended-data | 0 | **2,460** | - -#### ECUs (+145) -GDS bus map covering ICE + HEV + PHEV + E-GMP EV + N Performance: -powertrain (EMS + TCM + HTRAC AWD + HCU + HV battery + front+rear -MCU + OBC + LDC + **VCMS** E-GMP 800V + front+rear motor + Sport -mode + N exhaust flap + N Launch + active engine mount), chassis -(ESC + SAS + MDPS + EPB + TPMS + SRS + occupancy + RSPA + ECS + ARC -Genesis + rear-axle steer G90/EV9 + e-LSD + air suspension G90 + ride -height + Mando iBooster), ADAS (HDA/HDA2 master + front camera + radar -+ rear radar L+R + 4 corner radars + 4-cam SVM + DAW + BVM L+R -+ front+rear sonar + traffic sign), body (CGW + IPM + LCD/OLED 12.3" -cluster + dual FATC + 4 doors + 4 seats Genesis + 2 mirrors + steering -column + 2 sliding doors Carnival + tailgate + pano sunroof + fuel/ -charge flap + **frunk** EV9/Ioniq), **HD Matrix LED** Genesis IMA + -**pixelated taillights** + welcome pixel, ccNC head unit + passenger -display + 2 rear displays + B&O/Lexicon/Meridian/Krell amp + -Bluelink/Kia Connect/GCS TCU + 5G TCU + eCall + SVR, SMK + immobilizer -+ alarm + tilt + glass-break, HV/EV (ITMS thermal + heat pump + PTC -+ HV scroll compressor + battery heater + chiller + valve block + -2× aux pumps + **motor disconnector**), aux comfort (heated steering -+ 4× seat climate + AR-HUD Genesis + 64-color ambient + Qi + 2× Ergo -Motion massage + auto-wiper + fragrance + ionizer/fine dust filter), -trailer + power tow hitch, **V2L converter + V2X bidirectional -inverter**, OTA + HSM + Ethernet + 5 domain controllers + Car2X + -**Bluelink TCU app gateway + DAB+/HD Radio + fingerprint + Face -Connect GV60 + Digital Key 2.0 UWB**. - -#### DIDs (+2,459) -- 26 generic UDS DIDs incl. HMG part no, calibration, GDS ID, KDM/USA/EUR/GEN region. -- 74 EMS engine DIDs (RPM/torque/MAP/MAF/lambda B1+B2, 2× turbo speed, 2× wastegate, **CVVT intake+exhaust B1+B2 + CVVD state Smartstream**, idle, drive mode 5 modes incl. Smart, **ISG (Idle Stop&Go) count**, **N Launch + NGS / N Power Shift counters**, max-RPM/speed/oil-temp lifetime, **DPF soot + regen count + distance since regen**, **SCR urea + remaining km + NOx in/out**). -- 64 per-cylinder (1-8) — Tau V8 G80/G90. -- 224 engine variant DIDs — 28 HMG engines × 8 fields (Kappa 1.0T, Gamma 1.2/1.4T/1.6T/1.6T-N, Nu 2.0/2.0T/2.0T-N, Theta3 2.5T, Smartstream 2.5T/2.5T-N, Smartstream R 2.2 + U3 1.6 diesel, Lambda V6 3.0/3.3T/3.5T-N/3.8 NA, Tau V8 4.6/5.0, HEV Smartstream 1.6/1.8 + 1.5 PHEV, **E-GMP RWD/AWD/N/Long Range/Standard Range**). -- 176 transmission variants — 11 trans gens × 16 fields (6/8/10AT, 7-DCT dry/wet, 8-DCT wet, IVT CVT, 6MT, **E-GMP 1-speed reducer + 2-speed Ioniq 5 N/EV6 GT**, HTRAC transfer). -- 64 head-unit gens (AVN 5/ccIC/**ccNC Snapdragon**/Genesis) × 16 fields. -- 29 ESC chassis DIDs (ECS state + ARC state + rear steer angle + 4× ride height + 4× air-susp pressure + e-LSD split + e-LSD oil temp + HTRAC torque split). -- 64 per-wheel ABS/TPMS (4 × 16) + sensor IDs + camber/toe. -- 33 HV battery DIDs incl. **800V architecture flag + chemistry NMC/LFP + pyrofuse + lifetime charged/discharged kWh**. -- 192 per-cell V (E-GMP max 192 cells). -- 256 per-module (32 × 8 fields). -- 38 motor/MCU DIDs (front + rear, 19 each, incl. **disconnector state + N Grin Boost active + overboost remaining**). -- 32 OBC + VCMS DIDs incl. **400V boost** (E-GMP) + **V2L active/kW/session/lifetime + V2G active**. -- 36 ADAS + HDA2 (HDA / HDA2 hands-on lane change / curve speed + ISLA + DAW drowsy warnings + BCA + RCCA + RSPA + Safe Exit + Junction Turning Assist + Navi-based ACC + highway auto lane change). -- 96 ADAS object stack (12 × 8). -- 32 cluster + **N-mode telemetry** (lap timer, best 0-60/0-100/QM, total launches, **N Grin Boost + Drift Optimizer counters + N e-shift + N Active Sound+ mode**). -- 256 last-32-trip × 8. -- 80 driver coaching (20 × 4 windows incl. grin boost + drift optimizer). -- 43 per-bulb hours (matrix + parametric + pixel tail + welcome pattern). -- 32 ambient zones (incl. **sound-mood lighting**). -- 64 per-key (8 × 8) incl. SMK + **Digital Key 2.0 UWB**. -- 36 per-camera shading (9 × 4) incl. BVM L+R. -- 60 premium audio (B&O / Lexicon / Meridian / Krell) incl. **15-band parametric EQ × 3** (gain/freq/Q). -- 132 per-ECU programming (33 × 4). -- 48 Connect (Bluelink + Kia Connect + GCS) subscription incl. **EV Route Planner + Charge myHyundai/Kia Charge + Genesis Lounge**. -- 20 bus topology (B/C/P/M-CAN + LIN + **CAN-FD** + Ethernet + AVB + SOME/IP). -- 32 quad-zone HVAC + 96 user profiles (6 × 16) + 96 service history (24 × 4 incl. DCT clutch + e-LSD + alignment) + 38 vehicle metadata (incl. **N Performance + N Line + Genesis Designs + Genesis Lounge member + battery size + chemistry + DC max kW + motor count + E-GMP platform flag + V2L/V2G capable + brand HYU/KIA/GEN**). - -#### Routines (+145) -EMS adapt resets (idle, throttle, misfire, kat, lambda, **CVVT, CVVD -Smartstream**) + oil/inspection/brake fluid resets + **DPF forced regen -+ DPF replace reset + SCR/DEF priming** + **N Launch calibrate + N -track init**, TCM + DCT clutch + kiss-point, HTRAC + e-LSD adapt, per- -wheel ABS bleed + pump + SAS + yaw zero + TPMS relearn + EPB workshop, -**ECS + ARC + rear-axle steer + air-suspension calibrations**, IPM + -window/mirror init + sunroof + sliding doors L+R + tailgate + frunk -calibrate, FATC basic + heat-pump self-test, headlight aim L+R + HD -Matrix LED + pixelated taillight init, front camera dynamic + static -+ radar align + 4 corner/rear radars + SVM + BVM L+R + DAW + sonar -front+rear, cluster + mileage align + N telemetry reset, HV cell -balance + capacity + isolation + contactor + pre-charge + pyrofuse, -motor resolver zero (front+rear) + inverter self-test (front+rear), -OBC + LDC + **VCMS** + thermal loop + charge flap, **disconnector + -V2L + V2X self-tests**, N exhaust flap test, 21 module-replacement -procedures, key + immobilizer relearn + **Digital Key 2.0 pair + -fingerprint enroll + Face Connect enroll**, OTA check/install/rollback, -HSM provision/zeroize, 5 domain self-tests + Ethernet + Car2X, seat -init dr+pa + Ergo Motion calibrate + Qi + HUD calibrate, **Bluelink/ -Kia Connect refresh + welcome pixel animation load**. - -#### Coding blocks (32 / 295 fields) -IPM general (UWB approach unlock + walk-away lock), door extended -(flush handles Genesis), alarm zones, **HD Matrix LED features** -(IFS + IMA HD matrix + parametric DRL pixels + lane lighting + -intersection lighting + country-specific patterns), pixelated -taillight (parametric pixels Ioniq 5), ADAS Lane (LFA + LKA + LDW + -HDA + HDA2 hands-on), SCC + Navi ACC (predictive + curve speed + ISLA), -FCA (pedestrian + cyclist + JTS intersection + reverse PCA-R + evasive -steering), BCA + RCCA + BVM + Safe Exit Assist, FATC features (heat -pump + ionizer + fine dust filter + fragrance), EV charge features -(CCS1/2 + NACS + V2G/V2L/V2H + ISO 15118 PnC + 800V E-GMP + 22kW AC + -240/350kW DC + E-Route Planner), **N Performance features** (Grin -Boost + e-shift + Drift Optimizer + Active Sound+ + N Pedal + Track -SENSE Shift + Battery pre-condition + Launch Control + Power Shift + -lap timer + G-meter + track data export), N exhaust (legal quiet -mode), **ccNC head unit features** (CarPlay + AA + Bluelink/Kia -Connect + Bang & Olufsen + Lexicon Genesis + Meridian Kia + Krell + -passenger display + rear displays G90/EV9), cluster (HUD + AR-HUD -Genesis + OLED Genesis + N track mode + G-meter), SMK + UWB Digital -Key 2.0 (relay attack protection), trailer (power hitch), HTRAC + -chassis (HTRAC AWD + e-LSD + ECS + ARC + rear-axle steer + air susp -G90 + sport/snow/mud/sand/terrain modes), Ergo Motion (relaxation -mode Genesis), sliding doors (Carnival), pano sunroof, HUD (AR -Genesis), Qi, DAW (gaze tracking HDA2), OTA (staged rollout), Crypto/ -HSM, **Bluelink/Kia Connect/GCS** (EV Route Planner + Charge myHyundai/ -Kia Charge + voice assistant + region NA/EU/CN/KDM), frunk (Ioniq/ -EV9), tow hitch, fragrance Genesis, **fingerprint + Face Connect** -(GV60), **V2L/V2G features** (interior outlet + charge port adapter -+ V2H home backup + auto start on load detect). - -#### Adaptations (76) + Actuator tests (145) + Live PIDs (45) + DTC ext (2,460) -Engine (incl. **N Launch max RPM + N exhaust flap policy + ISG min -coolant**) + TCM + ESC + ECS + air-susp + rear steer + e-LSD + HTRAC -+ HDA2 + SCC + ISLA + matrix HB + pixel tail + comfort + EV (DC/AC -targets + 350kW DC + ISO 15118 PnC + V2L default) + OTA + HSM + -**N drive mode default + N torque distribution + N Active Sound + -fingerprint default + Face Connect default + Ergo Motion default + -fragrance + ionizer auto**; full per-actuator tests incl. **ECS 4× -dampers + ARC actuators + rear-steer + 4× air-susp valves + N Launch -demo + V2L outlet test + fingerprint + Face Connect tests + DPF -forced regen 60s**; live engine + DPF + SCR + HV battery + front+rear -motor + OBC + V2L + ADAS + chassis (ride heights, G long+lat) PIDs; -broad P/B/U/C codes × 4-6 record types incl. environmental_data + -freeze_frame_template. - -Estimated ~28% ODIS coverage — at the realistic GDS/KDS-community -ceiling. Higher coverage requires GDS Mobile or KDS Tester license. - -## [3.47.0] - 2026-05-08 — Porsche pushed to public-source ceiling (~38% ODIS, 5446 entries) - -Same depth-pattern as the prior 6 OEMs, applied to Porsche via PIWIS- -community + 911uk + Rennlist + shared VW Group ODX (Porsche shares -PPE/J1/MEB platforms with Audi/VW). Higher ceiling than Toyota/Honda -because Porsche shares platforms with VW Group (which we already -mapped at ~50% — Group ODX leakage benefits Porsche). - -### catalogs/porsche.json — 41 → 5,446 entries - -| Section | v3.39 | v3.47 | -|---|---|---| -| ECUs | 8 | **138** | -| DIDs | 33 | **2,423** | -| Routines | 0 | **141** | -| Coding blocks | 0 | **31 (278 fields)** | -| Adaptations | 0 | **74** | -| Actuator tests | 0 | **139** | -| Live PIDs | 0 | **40** | -| DTC extended-data | 0 | **2,460** | - -#### ECUs (+130) -PIWIS topology spanning ICE + hybrid + EV variants: powertrain -(DME + bank-2 split for V8, PDK/Tiptronic, AWD PTU, HV battery, -front+rear PCU, OBC, DC-DC, 800V DC charger, front+rear e-motor, -Sport Chrono, Sport exhaust flap, Launch Control coordinator, active -engine mount), chassis (ABS+PSM, SAS, Servotronic EPS, EPB, TPMS, -SRS, occupancy, ParkAssist, PASM, PDCC, rear-axle steer, PTV+, air -suspension, ride height, PCCB ceramic brake, iBooster), ADAS -(coordinator + InnoDrive, front camera + radar, rear radar L+R, -4× corner radar, 360-camera × 4, driver monitor, Night Vision Assist, -front+rear sonar, traffic sign), body (CGW + BCM + cluster + dual -HVAC + 4 doors + 4 seats Panamera + 2 mirrors + steering column + -convertible top + active rear spoiler + active front lip + tailgate -+ pano roof + fuel/charge flaps L+R), Matrix LED HD headlights L+R + -OLED taillights L+R, IVI (PCM + passenger display + 2 rear displays -+ Burmester 3D amp + premium amp + tuner + 5G TCU + eCall + Porsche -Vehicle Tracking), Kessy + immobilizer + alarm + interior motion + -tilt + glass-break, HV/EV (thermal + heat pump + PTC + HV refrigerant -compressor + battery heater + chiller + valve block + 2× aux pumps), -aux comfort (heated steering + seat climate dr+pa + HUD + ambient + -Qi + 2× massage + auto-wiper + fragrance + ionizer), trailer + power -hitch, zonal (OTA + HSM + Ethernet switch + 5 domain controllers + -Car2X + My Porsche TCU app gateway). - -#### DIDs (+2,390) -- 27 generic UDS DIDs incl. Porsche part no, calibration, PIWIS ID, factory. -- 72 DME engine DIDs (RPM/torque/MAP/MAF/lambda B1+B2, 2× turbo speed, 2× wastegate, VVT intake+exhaust B1+B2, idle, drive mode 4 modes, Launch Control state+count, **6 overrev band counters** (911 trademark), max-RPM/speed/oil-temp lifetime, full-load + overboost seconds). -- 64 per-cylinder (1-8) — V8 Cayenne/Panamera Turbo S. -- 192 engine variant DIDs — 24 Porsche engines × 8 fields (MA1/MA2 flat-6, 9A1/9A2 NA GT3 flat-6, MA2 flat-4 718, V6 BiTurbo, V6 turbo Cayenne/Macan, V8 BiTurbo Panamera/Cayenne, V6+V8 TDI legacy, PHEV V6+V8, T-Hybrid GTS, Taycan dual-motor, Taycan Turbo GT S Plate, Macan EV PPE, Macan EV Turbo). -- 160 transmission variants — 10 trans gens × 16 fields (7+8 PDK, Tiptronic, 6+7MT, 2-speed Taycan rear, 1-speed Taycan front, 2-speed Macan EV, transfer 4S, PTV+). -- 48 PCM head-unit gens (PCM 5/6/7) × 16 fields. -- 29 chassis DIDs (ABS+PSM + PASM state + PDCC state + rear steer angle + 4× ride height + 4× air suspension pressure + compressor + PTV+ split). -- 64 per-wheel ABS/TPMS (4 × 16 incl. PCCB disc temp + caliper temp + lifetime + camber + toe). -- 30 HV battery DIDs incl. 800V architecture flag + chemistry + pyrofuse + coolant in/out. -- 216 per-cell (108 × 2) for Taycan/Macan EV. -- 264 per-module (33 × 8 fields). -- 32 motor/PCU DIDs (front + rear, 16 each). -- 30 OBC DIDs incl. 800V booster + max session kW + charge curve. -- 35 ADAS + InnoDrive (predictive cruise + Night Vision + Emergency Assist + Lane Change + Side Assist L+R + intersection + swerve). -- 96 ADAS object stack (12 × 8). -- 28 cluster + Sport Chrono (lap timer, best 0-60/0-100/QM, total laps, total track minutes, launches). -- 256 last-32-trip × 8. -- 80 driver coaching (20 × 4 windows). -- 40 per-bulb hours (matrix + OLED + ambient). -- 31 ambient zones. -- 64 per-key (8 × 8) incl. UWB + Digital Key. -- 32 per-camera (8 × 4) incl. Night Vision IR. -- 60 Burmester 3D audio incl. **15-band parametric EQ × 3 (gain/freq/Q)**. -- 120 per-ECU programming (30 × 4). -- 48 Connect Plus subscription incl. **Function on Demand** (rear steer, PASM, PDCC, InnoDrive, matrix HB). -- 20 bus topology (Comfort/PT/Chassis/Info CAN + LIN + FlexRay + Ethernet + AVB + SOME/IP). -- 32 quad-zone HVAC + 96 user profiles (6 × 16) + 96 service history (24 × 4 incl. PDK clutch + timing chain + alignment) + 39 vehicle metadata (incl. **Weissach + lightweight + aero kit + carbon roof + PTS Exclusive Manufaktur paint** + battery size + chemistry + max DC kW + motor count + overboost). - -#### Routines (+141) -DME idle/throttle/misfire/kat/lambda/VVT/oil-pump/starter adapts + -**overrev counter clear** + **Track Mode init** + **Launch Control -calibrate**, PDK basic + clutch adapt + kiss-point, AWD + PTV+ adapt, -per-wheel ABS bleed + pump test + SAS + yaw zero + TPMS relearn + -EPB workshop, **PASM + PDCC + rear-axle steer + air-suspension + -ride-height calibrations**, PCCB warm-up + iBooster, BCM + window + -mirror + sunroof + convertible top + tailgate + active rear spoiler + -active front lip calibrations, A/C basic + heat-pump self-test, matrix -LED HD calibrate + OLED taillight init, front camera dynamic + static -+ radar align + 4 corner/rear radars + 360-camera + driver monitor + -Night Vision calibrations, cluster + mileage align + Sport Chrono -reset, HV cell balance + capacity + isolation + contactor + pre-charge -+ pyrofuse, motor resolver zero (front+rear) + inverter self-test -(front+rear), OBC + DC-DC + 800V DC charger + thermal loop + charge -flaps L+R, exhaust flap test, 21 module-replacement procedures, key -pairing + immobilizer relearn, OTA check/install/rollback, HSM -provision/zeroize, 5 domain self-tests + Ethernet + Car2X, seat init -dr+pa + massage calibrate + Qi + HUD calibrate, **My Porsche refresh -+ Function on Demand activate/deactivate**. - -#### Coding blocks (31 / 278 fields) -BCM general (UWB approach unlock + walk-away lock), door extended, -alarm zones (transport + garage modes), Matrix LED HD features -(**HD matrix 84k px + lane lighting + intersection lighting + pothole -warning projection**), OLED taillight, ADAS Lane (Emergency Assist), -ACC + InnoDrive (predictive ACC + speed limit assist), AEB -(intersection + reverse + swerve), Side Assist (Lane Change + -exit warning), A/C (heat pump + ionizer + fragrance), EV charge -(CCS1/2 + NACS + V2G/V2L/V2H + ISO 15118 PnC + 800V architecture + -22kW AC + 270/350kW DC), **Sport Chrono** (steering dial + Track -Precision app + Launch Control + PSM Sport + lap timer + video -overlay), Sport exhaust (legal quiet mode), PCM (CarPlay + AA + -Connect Plus + Burmester 3D + passenger display + rear displays), -cluster (HUD + Sport Chrono overlay + G-meter overlay + Track mode), -Kessy (UWB + Digital Key + relay attack protection), trailer (power -hitch), AWD/PASM/PDCC, driver seat climate (heat + vent + massage + -memory), convertible, panoramic roof, HUD (AR overlay), Qi, driver -monitor, OTA (staged rollout), Crypto/HSM (debug locked), **My -Porsche / Connect Plus + Function on Demand** (rear steer, PASM, -PDCC, InnoDrive, matrix HB unlocks + region NA/EU/CN), aero/spoilers -(active rear + front lip + diffuser), tow hitch, fragrance, massage. - -#### Adaptations (74) + Actuator tests (139) + Live PIDs (40) + DTC ext (2,460) -Engine (incl. **overrev clear + Launch Control max RPM + Track Mode -default + exhaust flap policy**) + PDK + ABS + PASM modes + air-susp -heights + rear steer angle + PTV+ + InnoDrive + ACC + matrix HB + -OLED + comfort + EV (DC/AC targets + 270kW DC + ISO 15118 PnC + -overboost + AVAS) + OTA + HSM + Sport Chrono + Launch + massage + -fragrance defaults; full per-actuator tests incl. **PASM 4× dampers -+ PDCC actuators + rear-steer + 4× air-susp valves + PCCB warm-up + -Launch Control demo**; live engine + HV battery + front+rear motor + -OBC + ADAS + chassis (ride heights, G long+lat) PIDs; broad -P/B/U/C codes × 4-6 record types incl. environmental_data + -freeze_frame_template. - -Estimated ~38% ODIS coverage — at the realistic PIWIS-community -ceiling. Higher coverage requires a PIWIS Tester license (Porsche -proprietary). - -## [3.46.0] - 2026-05-08 — Honda pushed to public-source ceiling (~26% ODIS, 4108 entries) - -Same depth-pattern as VW/BMW/Ford/Mercedes/Toyota, applied to Honda -via HDS-public + Honda-Tech community sources. Public ceiling is -moderate because HDS is closed but the community has decent coverage -of Honda SENSING + i-MMD hybrid + IMA legacy + Acura platforms. - -### catalogs/honda.json — 36 → 4,108 entries - -| Section | v3.39 | v3.46 | -|---|---|---| -| ECUs | 8 | **98** | -| DIDs | 26 | **1,782** | -| Routines | 10 | **110** | -| Coding blocks | 0 | **28 (207 fields)** | -| Adaptations | 0 | **64** | -| Actuator tests | 0 | **93** | -| Live PIDs | 0 | **33** | -| DTC extended-data | 0 | **1,900** | - -#### ECUs (+90) -HDS bus map: powertrain (ECM, TCM, IMA/IPU, MOT i-MMD, Battery ECU, -PCU, OBC, DC-DC), chassis (ABS+VSA, SAS, EPS, EPB, TPMS, SRS, OPDS -occupant), Honda SENSING (CMBS coordinator, FCW camera, FCW radar, -BLIS L+R, CTA, LKAS/parking, Multi-View Camera, driver attention), -body (BCM, CGW, multiplex, IC, A/C dual + aux, 2 seats + memory, -4 doors, 2 mirrors, tilt+telescope, 2 sliding doors Odyssey, power -tailgate, sunroof + pan roof), lighting (adaptive headlights L+R + -aim), IVI (Display Audio + nav + HondaLink TCU + amp + ELS Studio -Acura + TV + sat radio), Smart Entry + immobilizer + alarm, hybrid/EV -(brake booster HEV + regen brake + thermal + heat pump + DC/AC charge -inlets + LV battery), aux (auto wiper, mirror, HomeLink, Qi, heated -steering, seat climate dr+pa, HUD, gesture Acura), AWD (Real-Time / -SH-AWD), trailer + tow hitch + running boards, zonal (OTA, HSM, -Ethernet switch, 5 domain controllers, Car2X). - -#### DIDs (+1,756) -- 21 generic UDS DIDs incl. Honda part number, calibration, HDS ID. -- 50 ECM engine DIDs (RPM, torque, MAP/MAF/lambda, fuel rail, VTEC state, VTC intake+exhaust B1+B2, knock retard, EGR, wastegate, intercooler, cat efficiency B1+B2, idle, ECON mode, brake booster, drive mode 3 modes). -- 48 per-cylinder (cyl 1-6 × 8) — V6 J35. -- 144 engine variant DIDs — 18 Honda engines × 8 fields (K20C1 Type R, L15B7/BZ turbo, K24W/Z, J35Y/Z/A, J32A, K20A2/Z, LFA1, LFB1, L13B Fit hybrid, K20A9 Si, 3.0L V6 i-MMD, K20C5, EV motor Honda 0/Prologue). -- 160 transmission variants — 10 trans × 16 fields (5AT/6AT/9AT/10AT, CVT X-1/L4, i-MMD eCVT, 9DCT NSX/TLX, 6MT Type R/Si, transfer SH-AWD). -- 64 head-unit gens — Display Audio 8/9, Honda Connect, Acura Premium × 16 fields. -- 13 ABS+VSA DIDs + 32 per-wheel ABS/TPMS. -- 25 HV battery DIDs + 192 per-cell (96 × 2) + 72 per-module (12 × 6). -- 14 Motor/PCU (i-MMD) DIDs. -- 21 OBC charging DIDs incl. ISO 15118 + Plug & Charge. -- 24 Honda SENSING ADAS (camera + radar + ACC + LKAS + CMBS + RDM + AHB + Traffic Jam + Low-Speed Follow). -- 64 ADAS object stack (8 × 8). -- 15 cluster + iMMD power-flow + 256 last-32-trip + 64 driver-coaching. -- 31 per-bulb hours-on. -- 24 ambient zones. -- 64 per-key data (8 × 8) incl. Smart Entry + Digital Key. -- 24 per-camera lens shading (6 × 4). -- 19 ELS Studio audio + 7-band EQ. -- 96 per-ECU programming (24 ECUs × 4). -- 30 HondaLink subscription metadata (15 × 2). -- 11 bus topology (B-CAN + F-CAN + chassis CAN + LIN + Ethernet). -- 24 per-zone HVAC (3 × 8) + 64 user profiles + 64 service history (incl. valve adjust + timing chain) + 26 vehicle metadata (incl. Si/Type R + Acura A-Spec/Advance). - -#### Routines (+100) -ECM adapt resets (idle, throttle, misfire, kat aging, lambda trim, VTC), -oil pump + starter + battery register, oil/inspection/brake fluid/air -filter resets, alternator + compression + valve adjust + timing chain -inspection, TCM basic + Quick Learn, AWD adapt, per-wheel ABS bleed + -pump test + SAS + yaw zero, TPMS relearn, EPB workshop mode, BCM init, -window/mirror init, sunroof, power tailgate + sliding doors L+R, A/C -basic + compressor + heat-pump tests, headlight aim L+R + AFS, Sensing -camera dynamic + static + radar alignment, BLIS L+R + Multi-View + -driver camera + park assist calibrations, IC service reset + mileage -align, HV cell balance + capacity + isolation + contactor + pre-charge, -Motor resolver zero + inverter self-test, OBC + DC-DC + thermal loop + -charge door, hybrid brake booster, 13 module-replacement procedures, -key pairing/deletion + immobilizer relearn, OTA check/install/rollback, -HSM provision/zeroize, 5 domain self-tests + Ethernet + Car2X tests, -seat init dr+pa, wireless charging + HUD calibrate, HondaLink refresh. - -#### Coding blocks (28 / 207 fields) -BCM general, door extended, alarm zones, lighting features (Full LED + -adaptive + welcome/leaving choreography), Honda SENSING Lane (LKAS + -LDW + RDM + Traffic Jam Assist), ACC (Low-Speed Follow + curve speed), -CMBS (pedestrian + cyclist + intersection + reverse), A/C features -(plasmacluster + heat pump + auto demist), EV charge features (CCS1/2 -+ NACS + V2G/V2L/V2H + ISO 15118), Hybrid features (i-MMD: EV/ECON/ -Normal/Sport + regen paddle + predictive EV), head-unit (CarPlay + -AA + HondaLink + Alexa Built-in + ELS Studio), cluster features (IMA -power-flow + driver coaching), Smart Entry (Digital Key + walk-away), -trailer module, AWD (Real-Time + SH-AWD + Intelligent Traction -Management), driver seat climate, sliding doors (Odyssey), panoramic -roof, HUD, wireless charging, BLIS, driver attention, OTA, Crypto/HSM, -HondaLink (Security + Remote + Driver Score + Honda Driver + AcuraLink -Premium), power tailgate, tow hitch, power running boards. - -#### Adaptations (64) + Actuator tests (93) + Live PIDs (33) + DTC ext (1,900) -Engine + trans + ABS+VSA + TPMS + Honda SENSING + lighting + climate + -EV/hybrid + comfort + OTA + HSM + HondaLink defaults; full per-actuator -tests; live engine + HV battery + motor + OBC + Sensing PIDs; broad -P/B/U/C codes × 4-6 record types incl. environmental_data + -freeze_frame_template. - -Estimated ~26% ODIS coverage — at the realistic HDS-community ceiling -for Honda. Higher coverage requires commercial HDS / J2534 ODX-D -licenses. - -## [3.45.0] - 2026-05-08 — Toyota pushed to public-source ceiling (~30% ODIS, 5157 entries) - -Same depth-pattern as VW/BMW/Ford/Mercedes, applied to Toyota via -Techstream-public + HSD community sources. Public ceiling is moderate -because Techstream is closed but the HSD (Hybrid Synergy Drive) + -TSS (Toyota Safety Sense) communities have decent coverage. - -### catalogs/toyota.json — 21 → 5,157 entries - -| Section | v3.39 | v3.45 | -|---|---|---| -| ECUs | 8 | **117** | -| DIDs | 16 | **1,860** | -| Routines | 5 | **124** | -| Coding blocks | 0 | **31 (254 fields)** | -| Adaptations | 0 | **79** | -| Actuator tests | 0 | **104** | -| Live PIDs | 0 | **38** | -| DTC extended-data | 0 | **2,804** | - -#### ECUs (+109) -Full Techstream bus map: powertrain (ECM, TCM, HV ECU, MG ECU, -Battery ECU, Inverter ECU, OBC, DC-DC), chassis (ABS+VSC, SAS, EPS, -EPB, TPMS, SRS, occupant, pre-collision/TSS coordinator), TSS sensors -(front camera, front radar, BSM L+R, RCTA, ICS Park Assist, Panoramic -View Monitor, driver attention), body (BCM, CGW, multiplex, IC, -A/C amp + aux, 2 power seats + memory, 4 doors, mirrors, tilt+telescope, -2 sliding doors for Sienna/Alphard, power back door, sunroof + pan -roof), lighting (AFS L+R, headlight aim), IVI (Display Audio + nav -+ DCM telematics + amp JBL/Mark Levinson + TV + sat radio + AR navi -Lexus), Smart Key + immobilizer + keyless entry + alarm, hybrid/EV -(Power Management + EV charging + plug-in + brake booster HEV + regen -brake + thermal + heat pump + DC/AC charge inlets + charge door + LV -battery + wallbox iface), aux/convenience (auto wiper, mirror compass, -auto-dim, HomeLink, Qi wireless, heated steering, seat climate dr+pa, -massage Lexus, HUD, AR HUD, gesture, executive rear, kinetic seat), -4WD/off-road (AWD, A-TRAC/Crawl Control, KDSS, AHC, AHCS, trailer, -tow hitch, running boards, cargo, power back window), zonal (OTA, -HSM, Ethernet switch, 5 domain controllers, Car2X, V2X bZ4X). - -#### DIDs (+1,844) -- 21 generic UDS DIDs incl. Toyota part number, calibration, Techstream ID. -- 60 ECM engine DIDs (RPM, torque, coolant/oil, MAP/MAF/lambda, fuel rail, VVT-i intake+exhaust B1+B2, knock retard, EGR, wastegate, DPF, SCR NOx + DEF/AdBlue, turbo, intercooler, cat efficiency B1+B2, idle, immobilizer auth, brake booster active, eco score, drive mode 5 modes, e-boost). -- 64 per-cylinder (cyl 1-8 × 8) — V8. -- 184 engine variant DIDs — 23 Toyota engine families × 8 fields (2GR-FE/FXS/FKS/8GR-FXS, 2AR-FE/FXE, 8AR-FTS, A25A-FKS/FXS, M20A-FKS/FXS, 1NR-FKE, 1NZ-FXE, 3ZR-FAE, 1UR-FE, 3UR-FE, 1VD-FTV, 1GD-FTV, 2GD-FTV, V35A-FTS, T24A-FTS, 1ZR-FE, e-TNGA EV). -- 128 transmission variants — 8 trans × 16 fields (ECT 8AT, UC60E/AA80F/UC70 10AT, AA10F 10AT Tundra, eCVT THSII, eCVT DLSII, transfer case). -- 96 head-unit gens — Entune/Entune2/Entune3/Toyota Audio Multimedia/Lexus Interface/Display Audio × 16 fields. -- 14 ABS DIDs + 32 per-wheel ABS/TPMS. -- 30 HV battery DIDs + 192 per-cell (96 × 2) + 72 per-module (12 × 6). -- 22 motor/inverter DIDs (MG1 + MG2). -- 22 OBC charging DIDs incl. ISO 15118 + CHAdeMO + Plug & Charge. -- 23 TSS ADAS DIDs (camera + radar + DRCC + LTA + PCS + AHB + RSA + Toyota Teammate). -- 64 ADAS object stack (8 × 8). -- 16 cluster + ASSYST + 256 last-32-trip + 64 driver-coaching. -- 31 per-bulb hours-on (BiBeam segments). -- 24 per-zone ambient lighting. -- 64 per-key data (8 × 8) incl. Smart Key UWB + Digital Key. -- 24 per-camera lens shading (6 × 4). -- 19 premium audio (JBL/Mark Levinson) + 7-band EQ. -- 96 per-ECU programming history (24 ECUs × 4). -- 30 Toyota Connected subscription metadata (15 × 2). -- 16 bus topology (V-bus + body + powertrain + chassis CAN + LIN + AVC-LAN + MOST + Ethernet). -- 24 per-zone HVAC (3 zones × 8). -- 64 user profiles + 64 service history + 26 vehicle metadata (incl. TRD + Lexus F SPORT). - -#### Routines (+119) -ECM adapt resets, DPF + DEF + glow plug, TCM basic + Quick Learn, AWD -adapt, per-wheel ABS bleed + pump test + sensor zeros, TPMS relearn + -sensor replacement, EPB workshop mode, BCM init + window/mirror init + -sunroof/pan roof/power back door/sliding doors L+R/tow hitch/running -boards calibrations, A/C basic + compressor + heat-pump tests, headlight -aim L+R + AFS + BiBeam pixel test, TSS camera dynamic + static + radar -alignment, BSM L+R + park assist + Panoramic View + driver camera -calibrations, IC service reset all + mileage align, HV cell balance + -capacity remeasure + isolation + pyro + contactor + pre-charge, MG1+MG2 -resolver zero + offset + inverter self-test, OBC + DC-DC + thermal loop -bleed + EV charge door, hybrid brake booster + regen brake calibrate, -13 module-replacement procedures, CIG coding program, key pairing + -immobilizer + Smart Key relearn, OTA check/install/rollback, HSM -provision/zeroize, 5 domain self-tests + Ethernet switch + Car2X tests, -massage calibrate + seat init dr+pa, wireless charging + HUD calibrate, -Toyota Connected subscription refresh. - -#### Coding blocks (31 / 254 fields) -BCM general (CIG customisation), door extended (Smart Door auto-open), -alarm zones, lighting features (BiBeam LED + Triple Beam + BladeScan -AHS Lexus), TSS Lane Tracing (LTA + LDA + Emergency Steering + curve -speed reduction), DRCC extended (Stop & Go FSR + curve speed adapt + -lane change), Pre-Collision (pedestrian day+night + cyclist + intersection -+ reverse + Emergency Steering), A/C zone (S-Flow + nanoe X + heat pump), -EV charge features (CCS1/2/CHAdeMO/NACS/V2G/V2L/V2H/ISO15118), Hybrid -features (5 modes + regen paddle B-mode + predictive EV), head-unit -(Hey Toyota + Cloud Nav + Intelligent Assistant + Alexa), cluster (HSI -Hybrid System Indicator + driver coaching), Smart Key (UWB + Digital -Key + foot-open + walk-away), trailer module, Crawl Control + Multi- -Terrain Select (snow/dirt/sand/rock/mud/auto), driver seat climate -(kinetic seat Lexus), panoramic roof, OTA features, Crypto/HSM, Toyota -Connected (Safety Connect + Service Connect + Destination Assist + -Cloud Nav + Intelligent Assistant + Digital Key + Driver Score + Teen -Driver Tech), Toyota Teammate L2 (highway hands-free + max 60 urban), -HUD, wireless charging, Blind Spot Monitor, driver attention, Advanced -Park (memory + remote + trailer backup), power back door, AWD features -(Active Torque + E-Four hybrid AWD + DAC + HAC), power sliding doors -(Sienna/Alphard), tow hitch, power running boards. - -#### Adaptations (79) + Actuator tests (104) + Live PIDs (38) + DTC ext (2,804) -Engine + trans + ABS+VSC + TPMS + TSS + lighting + climate + EV/hybrid -+ Smart Key + OTA + HSM defaults; full per-actuator tests; live engine -+ HV battery + MG1/MG2 + OBC + TSS + cluster PIDs; broad P/B/U/C codes -× 4-6 record types incl. environmental_data + freeze_frame_template. - -Estimated ~30% ODIS coverage — at the realistic Techstream/HSD community -ceiling for Toyota. Higher coverage requires commercial Techstream -ODX-D licenses. - -## [3.44.0] - 2026-05-08 — Mercedes-Benz pushed to public-source ceiling (~35% ODIS, 6134 entries) - -Same depth-pattern proven on VW + BMW + Ford, applied to Mercedes-Benz. -Public ceiling is lower than VW/BMW/Ford because XENTRY/DAS is more -closed than VCDS/E-Sys/FORScan, but Vediamo/Carly community sources + -SCN coding documentation give a solid ~30-35% baseline. - -### catalogs/mercedes.json — 60 → 6,134 entries - -| Section | v3.39 | v3.44 | -|---|---|---| -| ECUs | 16 | **148** | -| DIDs | 32 | **2,101** | -| Routines | 10 | **164** | -| Coding blocks | 0 | **40 (327 fields)** | -| Adaptations | 0 | **94** | -| Actuator tests | 0 | **130** | -| Live PIDs | 0 | **41** | -| DTC extended-data | 0 | **3,416** | - -#### ECUs (+132) -Full XENTRY/DAS bus map: powertrain (ME engine, ETS 722.x trans, -secondary ME for V12, EMM front+rear motor for EQS/EQE/EQA/EQB/EQC, -VRM HV battery, OBC, DC-DC), chassis (ESP, SBC, ABC, AIRMATIC, AMG -Ride Control+, EHPS, LWS, PARKTRONIC, EPB, TPM, ETD, ABA Active -Brake Assist, ATC, Dynamic Select, rear-axle steering, sport diff), -body (SAM-F + SAM-R, CGW, IC, EZS, DAS, KG, OFV, BUA, AHW, AHE, -KLIMA, IHKU rear climate, 4 doors, dr+pa+rear seats + memory, -mirrors, FFL/Multibeam/DIGITAL LIGHT L+R, RFL, ILS), audio/IVI -(COMAND, MBUX, Burmester, TV, sat radio, TCU, GPS, MHI, RSE, -2 rear screens, MBUX Hyperscreen passenger), ADAS (camera main + -4 surround, driver attention, gesture control, front radar, -4 corner radars, ultrasonic F+R, Night View Plus, Speed Limit -Assist, Traffic Sign Assist, Cross-Traffic Alert, PRE-SAFE), EV -(SOBDMC rear, EMM rear, thermal, battery junction, charge inlets, -heat-pump compressor, wallbox iface, charge door, LV battery -monitor + management, charging planner, V2X, Car2X), zonal (OTA, -HSM, Ethernet switch TSN, 5 domain controllers), comfort premium -(wireless charging, HUD, AR HUD, 4 massage modules, Air-Balance -perfume, PURIFY ionizer, panoramic roof, Magic Sky electrochromic, -convertible top, AIRCAP, AIRSCARF, Maybach Executive Rear, exhaust -flap, sound actuator, trailer, electric tow hitch, power running -boards, cargo management, power trunk, power glovebox, chilled -cup-holder, fragrance pump). - -#### DIDs (+2,069) -- 23 generic UDS DIDs incl. Mercedes-specific SCN coding fields, EZS state, DAS lock state. -- 64 ME engine DIDs (RPM, torque, coolant/oil/MAP/MAF/lambda, HP+LP fuel rail, camshaft B1+B2, knock retard, EGR, wastegate, DPF deep, SCR NOx + AdBlue, turbo, intercooler, cat efficiency B1+B2, alternator, idle, drive authorisation, DAS, eco factor, e-boost). -- 96 per-cylinder DIDs (cyl 1-12 × 8) — supports M275/M279/M285 V12. -- 200 engine variant DIDs — 25 Mercedes engine families × 8 fields (M139/M177/M178/M256/M254/M260/M264/M266/M270/M271/M272/M273/M274/M275/M276/M277/M278/M279/M285, OM642/OM651/OM654/OM656/OM629/OM606). -- 128 transmission variants — 8 trans × 16 fields (722.6/722.9/725.0 9G-Tronic/724.0 DCT/AMG MCT/AMG DCT/EV single-speed/4MATIC transfer). -- 96 head-unit gens — NTG 4/5/5.5/6/7 + Hyperscreen × 16 fields. -- 14 ESP DIDs + 32 per-wheel + 32 per-corner suspension (AIRMATIC + ABC + AMG Ride). -- 28 VRM DIDs + 216 per-cell (108 cells × 2: V + T) + 72 per-module (12 × 6) — EQS/EQE pack depth. -- 21 EMM front motor + 9 EMM rear motor. -- 22 OBC DIDs incl. ISO 15118 + Plug & Charge. -- 24 ADAS Distronic/Active Distance + Active Steering + Emergency Stop + PRE-SAFE + Night View + Speed Limit + Traffic Sign + Active Brake. -- 64 ADAS object stack (8 × 8). -- 15 IC + ASSYST PLUS index + 256 last-32-trip + 64 driver-coaching. -- 32 per-bulb hours-on incl. multibeam segments + AIRSCARF. -- 30 per-zone Active Ambient (64-color premium) + trim strips. -- 64 per-key extended (8 × 8) incl. Keyless Go UWB + digital key. -- 24 per-camera lens shading (6 × 4). -- 40 per-radar waveform (5 × 8). -- 22 Burmester audio fine-grained + 4D resonator + 7-band EQ. -- 96 per-ECU programming + signature (24 ECUs × 4). -- 36 Mercedes me Connect subscription metadata (18 features × 2). -- 16 bus topology (CAN-B/C/D/E + LIN + FlexRay + MOST + Ethernet). -- 64 per-bank engine deep (2 banks × 32). -- 32 per-zone HVAC (4 zones × 8). -- 64 user profiles (4 × 16). -- 64 service history per-item (16 items × 4). -- 27 vehicle metadata (FA-style + AMG package + designo + Maybach). - -#### Routines (+154) -Engine adaptation resets, DPF + SCR + AdBlue + grid heater + secondary -air, ETS basic setting + clutch adapt + Quick Learn (9G-Tronic) + -oil filling, 4MATIC transfer adapt, per-wheel ESP bleed + pump test + -basic setting + sensor zeros, AIRMATIC height calibration + lift/lower -sets, ABC + AMG Ride calibration, EPB workshop mode, rear-axle steering -calibration, SAM init + window/mirror init + sunroof/panoramic/Magic Sky -calibrations, KLIMA basic + compressor + heat-pump + aux heater + -fragrance pump priming + perfume + ionizer tests, multibeam pixel calib -L+R, DIGITAL LIGHT calib L+R, ILS/AFS, ADAS dynamic + static camera -calib, FRR alignment zero, side radar L+R calib, surround camera + -driver attention + gesture + Night View Plus + AR HUD calib, IC service -reset all + ASSYST PLUS relearn, VRM cell balance + capacity + isolation -+ pyro + contactor + pre-charge tests, EMM resolver zero + offset + -inverter self-test, OBC + DC-DC + heat-pump self-tests, EV charge door -calibrate, 16 module-replacement procedures, SCN coding program + -offline, key pairing + DAS/EZS relearn + Keyless Go relearn, OTA -check/install/rollback, HSM provision/zeroize/log export, 5 domain -self-tests, ethernet switch + Car2X self-tests, wireless charging -calibrate, me Connect subscription refresh, AMG (Track Pace reset, -Drift Mode setup, sport diff calibrate, exhaust flap calibrate, launch -relearn), comfort (massage calibrate dr+pa, seat init, AIRSCARF + AIRCAP -+ tow hitch + running boards + glovebox + trunk tests, chilled cup-holder), -network topology rediscover. - -#### Coding blocks (40 / 327 fields) -SAM general, door extended, alarm zones, Multibeam/DIGITAL LIGHT -features, camera Lane Keep extended, Distronic Plus extended, Active -Brake Assist extended, KLIMA zones (4-zone + ionizer + perfume + AIRSCARF -+ heat pump + auto demist + solar comp), EV charge features (CCS1/CCS2/ -NACS/CHAdeMO/V2G/V2L/V2H/ISO15118/smart grid/scheduled/solar/Acceleration -Increase), EV thermal strategy, MBUX features (Hyperscreen + AR Nav + Hey -Mercedes + CarPlay/AA wired+wireless + 5GHz + video + zero layer + rear -screens + passenger screen), IC features, AMG Drive (8 modes + drift), -sport diff, exhaust flap, driver massage (vitalisation/relaxation/warming -+ kinetic seat + hot stone), driver seat climate (multicontour + dynamic -bolsters), panoramic roof + Magic Sky, tow hitch, Keyless Go (UWB + -digital key + kick-to-open + walk-away + approach), OTA, Crypto/HSM, me -Connect features bitmap, AIRMATIC (E-Active Body Control + lift + -loading + kneel), trailer module, HUD, Maybach Executive Rear (recline + -footrest + table + displays + audio + massage), convertible top -(AIRCAP + AIRSCARF), wireless charging, gesture control, driver attention, -blind spot (Active + exit warning + trailer extended), running boards, -power glovebox, power trunk, AR HUD, premium comfort bundle, AIRSCARF, -AIRCAP, Drive Pilot L3 (HW + activated + country + highway + max 60). - -#### Adaptations (94) + Actuator tests (130) + Live PIDs (41) + DTC ext (3,416) -Engine + trans + ESP + AIRMATIC/ABC + TPMS + ADAS + lighting + KLIMA + -EV + comfort + OTA + HSM defaults; full per-zone + per-actuator tests; -broad P/B/U/C codes × 4-6 record types incl. environmental_data + -freeze_frame_template. - -Estimated ~35% ODIS coverage — at the realistic Vediamo/Carly community -ceiling for Mercedes-Benz. Higher coverage requires commercial XENTRY -ODX-D licenses. - -## [3.43.0] - 2026-05-08 — Ford pushed to public-source ceiling (~40% ODIS, 7397 entries) - -Two combined passes lift Ford from 47 entries (post-v3.39 baseline) to -7,397 — same depth-pattern proven on VW + BMW. - -### v3.42 first pass (47 → 4,388 entries) - -| Section | v3.39 | v3.42 | -|---|---|---| -| ECUs | 18 | 115 | -| DIDs | 35 | 1,733 | -| Routines | 12 | 132 | -| Coding blocks | 0 | 37 (318 fields) | -| Adaptations | 0 | 88 | -| Actuator tests | 0 | 111 | -| Live PIDs | 0 | 40 | -| DTC extended-data | 0 | 2,132 | - -#### ECUs (+97) -Full Ford FDRS bus map: powertrain (PCM, TCM, secondary PCM, SOBDMC -electric powertrain front+rear, BECM HV battery, OBC, DC-DC), ABS + -abs pump + EPB, RCM (restraints), IPC + cluster secondary, BCM, GWM -+ NGWM next-gen gateway, CGEA gateway legacy, APIM (SYNC HU), FCIM, -FDIM, 4 door modules (DDM/PDM/RDM/RPDM), SCCM + PSCM steering, ACM + -amplifier B&O, TBM + GPSM + CMR telematics, OCS occupant, DSM driver -status monitor, DASCM driver assist, HCM L+R headlamps, AHM aux -heater, FEPS+REPS parking sensors, SOBD-APS active park steering, -TPMS, OFCM object fusion, SRM side radar L+R, FDM front radar, OFM -object fusion master, IPSM image processing surround, SODL+SODR side -object detection, ODLM diagnostic lighting, FCLM/RCLM/CLMU climate -loop, HTM heated tailgate, HTW heated trailer wiring, aerodynamic -shutter, EV charger door, APCM accessory power, CDCM convertible, -SGSM smart glass, R-DRLM rear DRL, DRCM door receiver UWB, RSEM rear -seat entertainment, headrest motor, massage modules dr+pa, seat -climate dr+pa, sunroof + panoramic roof, cargo management, power -running boards, Tow Tech package, Pro Power Onboard inverter, frunk, -tailgate step, mega console, Co-Pilot360, BlueCruise hands-free, BLIS, -CTA, PLC powerline charger, V2G Intelligent Backup Power, OTA -controller, cybersecurity HSM, Ethernet switch, 5 domain controllers, -wireless charging, HUD. - -#### DIDs (+1,698) -- 23 generic UDS DIDs (F1xx) — ECU serial, part #, HW/SW/boot version, calibration ID + CVN, supplier, prod date+plant, name, strategy + calibration parts, tear tag, OASIS + Ford diagnostic IDs, reset count, operating hours, supply voltage, internal temp, CPU load, RAM/Flash free. -- 60 PCM (engine) — Ford-specific telemetry incl. RPM/torque/coolant/oil/MAP/MAF/lambda, HP+LP fuel rail, VCT intake+exhaust B1+B2, knock retard, EGR position, wastegate duty, DPF deep, SCR NOx + DEF, turbo speed/inlet/outlet, intercooler, cat efficiency B1+B2, alternator load, IMRC, eco score, drive mode (10 Ford modes incl. Tow/Slippery/Sand/Mud/Trail/RockCrawl/Baja). -- 80 per-cylinder (cyl 1-10 × 8 fields) — supports V8/V10 (Godzilla 7.3, PowerStroke 6.7). -- 112 engine variant DIDs — 14 Ford engine families × 8 fields (1.5/2.0/2.3/2.7/3.0/3.5 EcoBoost + 3.5 H.O., Coyote 5.0L, Predator 5.2L, Godzilla 7.3L, PowerStroke 3.0L+6.7L, hybrids). -- 128 transmission variants — 8 trans × 16 fields (10R80, 10R140 diesel, 8F35, 8F57, 6F35, eCVT hybrid, 6DCT250, transfer case). -- 64 SYNC head-unit gens — SYNC 2/3/4/5 × 16 fields each (HW/SW, map, SSD, RAM, SoC temp, OTA, voice/nav/media engine versions). -- 14 ABS DIDs + 32 per-wheel ABS/TPMS (4 wheels × 8: speed, pad wear, disc thickness, pad temp, tire pressure+temp+target, offset). -- 29 BECM (HV battery) — pack V/A, SOC/SOH, max/min/avg cell V, delta, max/min/avg cell temp, isolation, capacity, charge cycle counts, thermal events, pyro+contactors, module + cells per module count. -- 192 per-cell battery (96 cells × 2: voltage + temperature). -- 72 per-module battery (12 modules × 6: V, A, max+min temp, SOC, SOH). -- 21 SOBDMC electric powertrain (front + rear motor: torque target/actual, rpm, stator/rotor temp, inverter temp, input V/A, phase current, efficiency, resolver offset). -- 22 OBC (charging) DIDs incl. ISO 15118 state + Plug & Charge. -- 9 Pro Power Onboard inverter (V2L for Lightning) DIDs. -- 24 Co-Pilot360 + BlueCruise ADAS. -- 64 ADAS object stack (8 objects × 8). -- 14 IPC + 256 last-32-trip extended history. -- 64 driver-coaching (16 metrics × 4 windows = lifetime / 30d / 7d / last_trip). -- 32 per-bulb hours-on counters. -- 24 per-zone ambient lighting RGB. -- 64 per-key data (8 keys × 8 fields incl. MyKey active). -- 24 per-camera lens shading (6 cameras × 4). -- 40 per-radar waveform (5 radars × 8). -- 21 premium audio (B&O) per-channel + 7-band EQ. -- 96 per-ECU programming history + signature (24 ECUs × 4). -- 13 bus topology (HS/MS/FD-CAN + LIN + Ethernet + load + errors). -- 16 per-corner suspension extended (4 corners × 4 fields). -- 64 per-bank engine deep (2 banks × 32 fields). -- 24 per-zone HVAC (3 zones × 8 fields). - -#### Routines (+120) -KAM reset, throttle/misfire/lambda/cam VCT/IMRC adapts, DPF force regen, DEF dosing, oil pump, starter, battery registration, oil/inspection/brake fluid/fuel filter/air filter resets, grid heater + secondary air tests, alternator + compression tests, TCM basic setting + clutch adapt + Quick Learn (10R80) + xDrive transfer-case adapt, per-wheel ABS bleed, ABS pump test, SAS + yaw + brake pressure zeros, TPMS relearn, EPB workshop mode, BCM init + window/mirror init, sunroof/panoramic/tailgate/frunk/tailgate step/running boards calibrations, HVAC basic + compressor + aux heater + heat-pump self-tests, headlight aim L+R, AFS, matrix pixel L+R, IPMA dynamic + static, FDM radar zero, IPSM surround, DSM, BLIS L+R, IPC service reset + mileage align + MyKey setup, BECM cell balance + capacity remeasure + isolation + pyro + contactor + pre-charge tests, SOBDMC resolver zero + offset + inverter self-test, OBC + DC-DC + Pro Power self-tests, EV thermal loop bleed, V2G self-test, 14 module-replacement procedures, As-Built/CCC programming, key pairing/deletion, PATS immobilizer relearn, MyKey admin, OTA check/install/rollback, HSM provision/zeroize/log export, 5 domain self-tests, ethernet switch self-test, BlueCruise + Co-Pilot360 calibrations. - -#### Coding blocks (37 / 318 fields) -BCM general, door extended, alarm zones, lighting extended, Co-Pilot Lane, FRR ACC, AEB, BlueCruise, IPMA camera, TDM trailer, EV charge features (CCS1/CCS2/NACS/CHAdeMO/V2G/V2L/V2H/Pro Power/ISO15118/smart grid/home integration/Intelligent Backup), MyKey, SYNC features, IPC features, massage dr, seat climate dr, panoramic roof, frunk, tailgate, running boards, BLIS, driver status, HVAC zones, OTA, crypto/HSM, Pro Power, FoD bitmap (12 features), APIM audio (AM/FM/HD/DAB+/SiriusXM/BT/USB/podcast/Spotify/Amazon/Apple Music/RSA/ANC), Co-Pilot360, Tow Tech, frunk features, mega console, AHM aux heater, aerodynamic shutter, HUD, wireless charging, trailer brake. - -#### Adaptations (88) + Actuator tests (111) + Live PIDs (40) + DTC ext (2,132) -Engine + trans + ABS + TPMS + ADAS + lighting + climate + EV + body + MyKey + trailer + OTA + HSM defaults; full per-zone + per-actuator tests; live engine + battery + motor + charge + ADAS + cluster PIDs; broad P/B/U/C codes × 4 record types. - -### v3.43 second pass (4,388 → 7,397 entries) - -| Section | v3.42 | v3.43 | -|---|---|---| -| DIDs | 1,733 | 1,945 | -| Routines | 132 | 187 | -| Adaptations | 88 | 142 | -| DTC extended-data | 2,132 | 4,820 | - -- 4 user profiles × 16 fields = 64 driver-stats DIDs. -- 16 per-corner air-spring chamber pressures + 12 onboard scales DIDs. -- 15 Tow Tech / Smart Hitch detail DIDs. -- 16 power running boards / tailgate step / frunk / tailgate position + lifetime cycles. -- 64 service-history per-item DIDs (16 items × 4: last miles + epoch + workshop + due-in). -- 25 vehicle metadata DIDs (FA-style: model year, plant, paint, interior, market, country, trim, options, kerb/GVW/payload, warranty start + first registered + production, engine + trans serials, axle ratio, tire + wheel size, color, trim). -- 55 routines incl. ZF deep + PowerStroke deep (glow plug, water separator drain, DEF tank drain, DPF burn-off, EGR clean), pinch-relearn (window FL/FR + sunroof + pan), seat init, SYNC factory reset + voice recog calibrate, ADAS deep recals, trailer pair + calibrate + brake burnish, Smart Hitch zero, BECM module-specific balance, V2G self-test, gateway routing reset, profile create/delete/export/import, aerodynamic shutter calibrate. -- 54 adaptations (cruise buffers, lighting, park assist, driver attention, off-road incl. Trail Control + Hill Descent + Crawl Control, EV deep incl. route-aware unlocks + curves + AVAS, FordPass remote services, massage/seat-climate defaults, comfort, power running boards, tailgate). -- 2,688 DTC ext-data records — long-tail P-codes round 2 + round 3 with 6 record types (incl. environmental_data + freeze_frame_template). - -Estimated ~40% ODIS coverage — at the realistic public-source ceiling for Ford (FORScan community is among the most open of any OEM). - -## [3.41.0] - 2026-05-08 — BMW final pre-commercial-ceiling pass (~42% ODIS, 7932 entries) - -Final BMW push toward public-source ceiling (~40-45% non-commercial). -Adds 2,337 entries focused on the coding-blocks gap and final long-tail. - -| Section | v3.40 | v3.41 | Change | -|---|---|---|---| -| ECUs | 138 | **138** | — | -| DIDs | 2,070 | **2,070** | — | -| Routines | 203 | **203** | — | -| Coding blocks | 8 (77 fields) | **43 (414 fields)** | +35 / +337 | -| Adaptations | 156 | **198** | +42 | -| Actuator tests | 151 | **151** | — | -| Live PIDs | 69 | **69** | — | -| DTC extended-data | 2,800 | **5,060** | +2,260 | - -### Coding blocks (+35 / +337 fields) -BDC general (auto-lock/unlock policy, comfort open/close, dome -behaviour), FEM window extended (4-door one-touch + anti-pinch + -rain close + travel/kid lock + comfort speed), FEM central lock + -alarm (full alarm zone bitmap + dynamic blink + transport mode), -FRM lighting extended (welcome/leaving choreography + dynamic turn -+ matrix high-beam + glare-free + adaptive cornering + highway/city/ -weather/intersection light + travel-mode swap + laser + OLED rear + -dynamic DRL), KAFAS Lane Assist extended (haptic/audio/visual warning -+ active steering + Emergency Assist + trained parking + highway -assist + min/max speed), FRR ACC extended (Stop&Go + curve + speed- -limit + predictive + lane-change + default distance + speed), KAFAS -AEB extended (pedestrian + cyclist + intersection + reverse + evasive -+ warn lead), IHKA zones extended (4 zones + sync + auto-recirc + -residual heat + aux heater + heat pump + ionizer + perfume + solar + -auto-demist + blower max), EV charge features extended (ISO 15118 + -DIN 70121 + CCS Combo 1+2 + CHAdeMO + NACS + V2G/V2L/V2H + smart -grid + solar + max DC), EV thermal strategy (preheat per route + DC -charge preheat + aggressive cooling + battery-friendly + heat-pump -priority + max charge temp), cluster features (HUD + AR HUD + digital -+ curved + OLED + track-mode + driver-coaching + speed-limit/eco/ -charge/regen/nav/phone/media overlays + brightness), iDrive features -(CarPlay + AA wired/wireless + 5GHz hotspot + voice wake-word + -local DNN + cloud + profile sync + app store + video + browser + -games + rear-pax apps + split screen + widget layout), sport diff -(torque vectoring + track-mode aggressive + dyn traction + default -lockup), active steering (velocity-dependent + track-mode direct + -default ratio), exhaust flap (track always open + sport open + manual -button + open threshold rpm), launch control (count limit + max rpm), -surround view (top-down + 3D + transparent hood + side obstacle + -rear cross traffic + collision warn + default view), driver attention -(phone-use + drowsiness + hands-off + gaze tracking + seatbelt check -+ warn threshold), gesture control (volume + call + nav zoom + 3 -custom gestures), AR HUD (nav/speed/ACC/lane/warning overlays + -brightness), tow hitch (electric + recognition + brake assist + sway -+ park assist + max trailer kg), driver massage (vitalisation + -relaxation + warming + Active Well-Being + default intensity), driver -seat climate (heat + vent + chill + auto-with-climate + zones + -default level), panoramic roof (Sky Lounge LED + electrochromic + -anti-pinch + auto rain + comfort + comfort speed), alarm extended -(full per-door switch bitmap + transport + garage + convertible mode + -duration), Comfort Access keyless (UWB + digital key + kick-to-open + -walk-away + approach + driver-only + walk-away distance), EHC air -suspension (self-levelling + aero drop + lift + loading + kneel + aero -drop speed), AR glasses (nav/call/media overlay), V2X / Car2X (V2V/ -V2I/V2P/V2G/V2L/V2H + DSRC/C-V2X radio + warn distance), OTA features -(auto-install + Wi-Fi only + metered + signed-only + rollback + min -battery), HSM features (secure boot + sec log + intrusion + anti-theft -+ seedkey + flash protection + level), FoD bitmap (15 features), -matrix headlight pixels, IHKU aux rear climate (zones + blower + -displays integration), Executive Lounge (rear recline + footrest + -table + console + displays + audio + default recline %). - -### Adaptations (+42) -Cruise overspeed/underspeed buffers + speed-limiter default, auto -high-beam threshold + min speed, cornering max speed + min angle, -welcome/leaving light proximity, park-assist (max speed + warn dist -+ volume + freq), reverse audio attenuation, trailer (brake gain + -sway sensitivity + max kg + tongue kg), fuel + EV range/charge -warnings, auto-wipe + auto-light sensitivity, memory parking slots -+ trained parking max, blind-spot lead + haptic, night-vision -threshold + max speed, driver-attention warn threshold + max -session min, Car2X warn distance, massage program/intensity defaults -(driver + passenger), seat climate defaults, perfume intensity + -pulse interval, ionizer default, chilled cup-holder target. - -### DTC ext-data (+2,260) — third long-tail sweep -P-codes round 3 with 6 record types (occurrence + aging + miles_since_cleared + oem_status_byte + environmental_data + freeze_frame_template), B+C codes round 2. - -## [3.40.0] - 2026-05-08 — BMW second-pass deep push (~37% ODIS, 5595 entries) - -Continuing the BMW catalog push toward ~40-45% ceiling. Adds 2,744 entries: - -| Section | v3.39 | v3.40 | Change | -|---|---|---|---| -| ECUs | 78 | **138** | +60 | -| DIDs | 1,254 | **2,070** | +816 | -| Routines | 107 | **203** | +96 | -| Coding blocks | 8 | **8** | — | -| Adaptations | 79 | **156** | +77 | -| Actuator tests | 87 | **151** | +64 | -| Live PIDs | 46 | **69** | +23 | -| DTC extended-data | 1,192 | **2,800** | +1,608 | - -### New ECUs (+60) -KAS (Comfort Access), RICOM, AMK (Active Anti-Roll), EHPS, FZD, -gestik (gesture), ICTM, gestik-3D (iX), 9 M-specific ECUs (sport -diff, ARS active steer, exhaust flap, launch coord, drift analyzer, -laptimer, data recorder, M3/M4/M5/M8 specific), top-tier amplifiers -(B&W Diamond, HK Logic7), zonal architecture (5 domain controllers, -ethernet switch TSN, OTA master, crypto/HSM, Car2X radio, V2X -controller), wireless charging (Qi), AR HUD, AR glasses interface, -GPU module, passenger + rear screens, massage modules, seat-climate -modules, sky lounge, executive lounge, perfume dispenser, ionizer, -power glovebox, power trunk, tow hitch, sliding doors L+R, chilled -cup-holder, curved-screen controller. - -### Engine variant DIDs (+112) -14 engine families × 8 fields each: B38, B47, B48, B58, B57, N20, -N55, S55, S58, S63, S68, N63, N74, B68 — displacement, max power + -torque, redline, compression ratio, bore, stroke, weight. - -### Transmission variants (+128) -8 variants × 16 fields: ZF 8HP45/50/70/75/76/90, M DCT 7-spd, -xDrive transfer case — oil type code, capacity, supplier, HW -revision, SW train, oil age + temp extremes, lifetime shifts + -clutch engagements + TCC lockup + manual-mode distance + kickdown -+ launch + over-torque + limp-home counts. - -### iDrive head-unit generations (+128) -8 generations × 16 fields: CIC, NBT, NBT EVO, ID4-8 — HW part no, -SW train, map version + region, SSD total + free, RAM, SoC temp, -uptime, boot count, OTA status + progress, voice/nav/media/ -connectivity-box engine versions. - -### Per-corner suspension extended (+32) -4 corners × 8 fields: velocity, compressor branch runtime, damper -actuator temp, air-spring chamber 1-4 pressures, levelling offset. - -### Per-bank engine deep (+64) -2 banks × 32 fields: turbo speed/inlet/outlet temp, intercooler -in/out, MAP, boost, wastegate duty + pos, VGT, EGR, throttle, -intake runner, HP+LP fuel rail, fuel pump duty, injector duration, -ignition advance, knock corr, O2 short+long trim, cat efficiency, -DPF soot+ash+pressure-drop+temp in/out, SCR NOx in/out + efficiency, -EGR cooler temp, oxidation cat temp. - -### Bank-2 O2 sensors (+16) — V8/V12 - -### IHKA deep (+14) -Solar intensity L+R + position, evap pressure, compressor -displacement + clutch state, aux heater glow plug + fuel -consumption + lifetime runtime, pollen filter age, air quality -CO + NOx, cabin pressure delta. - -### KAFAS lane stack (+32) -4 lanes × 8 fields: detection quality, curvature, offset, width, -marking type, color, age, confidence. - -### Traffic-sign recognition (+32) -8 detected signs × 4 fields: class, value, distance, confidence. - -### Per-camera lens shading (+24) -6 cameras × 4 fields: shading correction, white balance R|G|B, -lens temp, blockage. - -### Per-radar waveform (+40) -5 radars × 8 fields: chirp bandwidth, chirp duration, TX power, -blockage, h-align, v-align, temp, supply. - -### Premium audio (+21) -Per-channel gains (FL/FR/RL/RR/center/sub/surround/height L+R), -amp temp/supply/total power, DSP load, 7-band EQ. - -### Per-key UWB extended (+32) -8 keys × 4 fields: UWB ranging distance, AoA, RSSI, pairing count. - -### Per-ECU programming + signature (+96) -24 ECUs × 4 fields: programming attempts, SW checksum SHA1, -signature status, last successful programming epoch. - -### BMW Function-on-Demand (+30) -15 features × 2 DIDs: adaptive lights, CarPlay, AA wireless, -hotspot, TSR, Driving Assistant Pro, Parking Plus, remote engine -start, real-time traffic, nav premium, ConnectedDrive app, -teleservices, premium audio unlock, heated seats unlock, heated -steering wheel unlock — active flag + expiry epoch. - -### Bus topology (+15) -PT-CAN/K-CAN/D-CAN/LIN/FlexRay/MOST/Ethernet node bitmaps + bus -load + bus-off errors + Ethernet link speed + packet drops. - -### Routines (+96) -Per-wheel ABS bleed, ZF deep (5 clutches + TC + basic + oil-fill), -xDrive transfer-case clutch adapt, sensor static calibrations -(SAS, yaw, long+lat G, brake pressure), ride-height calib + low/high, -matrix pixel calib L+R, laser alignment L+R, OLED tail calib, -welcome/leaving choreography setup, KAFAS extended dyn+static, -surround camera + driver camera + gesture + AR HUD + blind-spot -calibrations, ultrasonic + night-vision, KESSY UWB recalibrate + -digital-key phone pair + valet + track-mode PIN reset, aux heater -burn-off + fuel-quality recal, heat-pump efficiency, evap drain, -EV deep (module-specific balance, capacity relearn, pyro continuity, -ISO 15118 + DIN 70121 handshake test, three-phase + resolver), -network/programming (FA, I-Stufe, SGBM, CAFD, FDL diff apply, OTA -check/install/rollback, HSM provision/zeroize, secure log export), -domain self-tests (PT, chassis, body, ADAS, infotainment), ethernet -switch self-test, Car2X self-test, wireless charging calibrate, FoD -subscription refresh, M-specific (Drift Analyser reset, Laptimer -reset, data recorder export, sport-diff calibrate, exhaust flap -calibrate, launch-control relearn), body/comfort (panoramic roof, -convertible top open/close, glovebox, power trunk, tow hitch, -sliding doors L+R, massage demos L+R, seat-climate tests, perfume, -ionizer). - -### Adaptations (+77) -Engine deep (torque curve, throttle response, dynamic overboost, -overrun fuel-cut delay, cold-start strategy, warmup target, lambda -target full load, e-boost target, alternator idle load, A/C -compressor max load), transmission (sport aggression, DPF avoidance, -manual hold, paddle priority), DSC (traction threshold, dynamic -traction unlock), EHC (self-levelling, lift speed, aero drop), ARS -default ratio, sport-diff lockup, exhaust flap rpm, launch max rpm, -EHPS (assist curve, return force, velocity-dependent), KAFAS -extended (lane-centring offset, speed-limit assist, construction -zone, no-overtake), FRR extended (hands-off lead, Emergency Assist, -TJA, Extended TJA Level 2, Highway Assistant, lane-change), matrix -anti-glare, OLED rear default, welcome+leaving anim, ambient color + -speed-dependent + dynamic mode, IHKA (blower curve, solar, humidity, -recirc CO+NOx thresholds, aux heater enable temp), EV charging -(pre-condition unlocks, max DC+AC limits, charge curves), recuperation -defaults, B-mode, EV creep, comfort (CKM profiles, per-key, auto -recalls), OTA (check interval, min battery, install windows, metered, -signed-only, rollback), HSM (secure boot, intrusion detect, seed-key -level). - -### Actuator tests (+64) -M sport (exhaust flap open/close, sport diff engage, active-steer, -launch-control), body/comfort (pan roof, conv top, glovebox, trunk, -tow hitch, sliding doors L+R, massage demos, seat-climate demos, -perfume, ionizer, chilled cup-holder), lighting deep (matrix sweep, -laser pulse, OLED choreography, welcome+leaving demos, ambient -red/green/blue/chase), ADAS deep (KAFAS dyn, FRR blockage, side -radar L+R, surround sweep, driver camera, gesture, AR HUD test, -blind-spot indicators, ultrasonic sweep), domain controllers + zonal -(5 domain tests, ethernet switch loopback, Car2X test, wireless -charging, AR glasses, AR HUD full calib, passenger+rear screens), -OTA (install force, rollback force), HSM self-test. - -### DTC ext-data (+1,608) — long-tail -P-codes broad sweep across P05xx-P0Fxx + P18xx-P2Dxx, B-codes round 2 (1400-2800), U-codes round 2 (0300-3200) — each × 4 record types. - -## [3.39.0] - 2026-05-08 — JSON-only architecture for all 45 OEMs + BMW deep-push (~28% ODIS) - -Two coordinated changes: - -### 1. JSON-only architecture across all OEM extensions -The VW JSON-only pattern (introduced in v3.31) is now applied to -every other OEM. Refactored 43 OEM Pascal files to remove their -hardcoded `ECU($xx, ...)` / `DID($xx, ...)` / `Routine($xx, ...)` -arrays. Every entry that was previously hardcoded has been merged -into the corresponding catalog JSON. Each OEM extension now: - -- `ApplicableToVIN` returns `VINMatchesCatalog('.json', VIN)` - (no hardcoded WMI lists in Pascal). -- `BuildCatalog` does only `MergeCatalogJSON('.json', ...)` + - `MergeCatalogJSON('uds-standard.json', ...)`. -- `BuildExtendedCatalog` (newly added) does - `MergeExtendedCatalogJSON('.json', ...)` for coding blocks, - adaptations, actuator tests, live PIDs and DTC extended-data. - -This means catalog updates ship as JSON edits (no recompile) and -porting to other languages only needs a JSON parser — same as VW. - -OEMs refactored: Aston Martin, BMW, BYD, Bentley, Cummins, Dacia, -Detroit Diesel, Ferrari, Ford, GM, Geely, Great Wall, Honda, -Hyundai/Kia (HMG), Isuzu, Iveco, JLR, Lada, Lucid, MAN, MINI, -Mahindra, Mazda, McLaren, Mercedes-Benz, Mitsubishi, NIO, Nissan, -PACCAR, Polestar, Porsche, Renault, Rivian, Rolls-Royce, Scania, -Smart, Stellantis, Subaru, Suzuki, Tata, Tesla, Toyota, Volvo, -Volvo Trucks, Xpeng (45 total — VW + 44 others). - -### 2. BMW catalog pushed to ~28% ODIS -First tier-1 OEM lift. catalogs/bmw.json grew from 43 to 2,851 -entries — applying the same depth-pattern proven on VW. - -| Section | Before | After | -|---|---|---| -| ECUs | 7 | **78** | -| DIDs | 34 | **1,254** | -| Routines | 9 | **107** | -| Coding blocks | 0 | **8 (77 fields)** | -| Adaptations | 0 | **79** | -| Actuator tests | 0 | **87** | -| Live PIDs | 0 | **46** | -| DTC extended-data | 0 | **1,192** | - -Coverage includes: -- Full F-series + G-series ECU map (DME, EGS, DSC, FEM, BDC, FRM, - KOMBI, KAFAS, ICM, IHKA, lighting modules, doors, audio, telematics, - HUD), plus i-series EV (SME, EMC front+rear, KLE, DC-DC, OBC, - chiller, HV heater, ISO 15118 wallbox interface), plus G-series - UDS-style addresses (0x6E0-0x6F9 + 0x600-0x618). -- DME deep telemetry (engine RPM/torque/coolant/oil/MAP/MAF/lambda, - HP+LP fuel rail, VANOS intake+exhaust, Valvetronic, knock retard, - DPF soot+ash+pressure-drop+temp+regen, SCR NOx in/out + efficiency, - AdBlue, turbo speed/inlet/outlet temp, intercooler, cat efficiency - bank 1+2, immobilizer/EWS state). -- Per-cylinder DIDs (cyl 1-12 × 8 fields = 96 DIDs) — supports V8/V12. -- EGS DIDs (oil temp/age/pressure, gear, TCC lockup, input/output rpm, - shift counts, clutch temp DCT, torque in/out, adapt status). -- DSC DIDs + per-wheel pad wear/disc thickness/temp/tire pressure+temp+target. -- EHC + VDC per-corner suspension actuators + ARS torque per corner. -- SME (HV battery) — pack V/A, SOC/SOH, max/min/avg cell V, cell delta, - isolation, capacity remaining + total, charge cycle counts, thermal - events, pyro fuse + contactors, module count + cells/module. -- 96 per-cell voltages + 96 per-cell temperatures. -- 12 modules × 6 fields (V, A, max/min temp, SOC, SOH). -- EMC front + rear motor (torque target/actual, rpm, stator/rotor - temp, inverter temp + input V/A, phase current, efficiency, - resolver offset). -- KLE charging (DC + AC voltage/current/power, phases active, - efficiency, ISO 15118 state, Plug & Charge, charge port - temp + lock state, lifetime DC + AC kWh, session counts). -- KAFAS + FRR + ACC ADAS (camera blockage, lane offset+curvature, - speed limit detect + confidence, dynamic-calibration state, radar - target distance + relative speed + class, blockage, alignment H+V, - chirp bandwidth, TX power, AEB lifetime interventions). -- ADAS tracked-object stack (8 simultaneous objects × 8 fields). -- IHKA per-zone (4 zones × 8 fields) + heat pump + aux heater. -- Cluster KOMBI (speed, odometer, trip A/B, range, fuel, service - intervals, drive cycle count) + last-32-trip extended history - (32 × 8 = 256 DIDs). -- Driver-coaching (16 metrics × 4 windows = 64 DIDs). -- Per-bulb hours-on (32 lighting circuits). -- Per-zone ambient lighting (24 zones). -- Per-key data (8 keys × 8 fields). -- 8 coding blocks (77 fields total): FEM door extended, - FRM lighting, matrix headlight features, ACC extended, AEB, - IHKA zones, alarm zones, EV charge features. -- 79 adaptations covering DME idle/torque/start-stop envelope/cyl-deact - /DPF/EGR/SCR/grid-heater, EGS shift speed + kickdown + creep, DSC - default mode + traction + auto-hold + trailer brake, TPMS per-axle - summer/winter/loaded, KAFAS Lane Assist + AEB sensitivity, ACC - default distance + speed + Stop&Go, lighting (auto high-beam, welcome, - ambient), IHKA defaults, EV charge limits + regen + AVAS, comfort - (auto-lock speed, mirror dip, window remote, auto-wipe). -- 87 actuator tests (DME throttle/EGR/wastegate/intake-runner/fuel-pumps - /secondary-air/DPF/SCR/starter/alternator/grid-heater/exhaust-flap/ - oil+coolant pumps, DSC pump + per-wheel inlet/outlet valves, EPB - motors L+R, EHC compressor + relief valve, VDC dampers, FEM - windows/mirrors/sunroof/tailgate/central-lock, lighting per-bulb - + matrix sweep + laser + OLED, IHKA compressor + heat pump + aux - heater, audio per-channel sweeps, ADAS tests, EV contactors + - pre-charge + pyro continuity + motor demos + chiller + radiator fan - + battery heater). -- 1,192 DTC ext-data records (P + B + U + C codes × 4 record types). - -## [3.38.0] - 2026-05-08 — VW final pre-commercial-ceiling pass (~50% ODIS) - -Final non-commercial pass — adds 993 entries across the last -practical gaps before public/community sources are exhausted. -Adds 36 new ECUs (rear-axle steer, EV thermal secondary, HV -junction box, charge inlet electronics, heat-pump compressor, -combustion + electric aux heaters, LV battery management, charging -planner, V2X controller, Car2X radio, OTA master, crypto/HSM, -ethernet switch + 5 domain controllers in the new zonal -architecture, AR headlight, digital OLED + matrix headlights, -ultrasonic clusters, blind-spot modules, night-vision, driver- -attention monitoring, EV rear motor + disconnect clutch), -per-bank engine deep telemetry (32 fields × 2 banks), per-zone -HVAC fine-grained, brake-fluid + iBooster + pedal-feel, ADAS -tracked-object stack (8 objects × 8 fields), premium audio -fine-grained (24 DIDs), HV battery per-module (12 modules × 6 -fields), 8 new coding blocks (lane-assist, ACC, AEB, V2X, OTA, -HSM, trailer, sound synthesis), 76 module-replacement and zonal -self-test routines, 42 actuator tests for the new domain, 500 -more long-tail VAG P-codes. - -### catalogs/vw.json — 7,498 → 8,491 entries - -| Section | v3.37 | v3.38 | Change | -|---|---|---|---| -| ECUs | 75 | **111** | +36 | -| DIDs | 2,774 | **3,042** | +268 | -| Routines | 460 | **536** | +76 | -| Coding blocks | 133 (915 fields) | **141 (973 fields)** | +8 / +58 | -| Adaptations | 565 | **604** | +39 | -| Actuator tests | 298 | **340** | +42 | -| Live PIDs | 421 | **445** | +24 | -| DTC extended-data | 2,772 | **3,272** | +500 | - -### New ECUs (36) — covers zonal-architecture + niche subsystems -Domain controllers (powertrain, chassis, body, ADAS, infotainment), -secondary central gateway, ethernet switch (TSN), crypto/HSM, -OTA master, central computer (zonal), Car2X DSRC/C-V2X radio, -V2X (V2G/V2L/V2H) controller, charging planner, AC + DC charge -inlet electronics, HV battery junction box, EV thermal secondary, -heat-pump compressor, combustion + electric aux heaters, LV aux -battery monitor + 12V starter battery sensor, panoramic roof, -convertible top, exhaust flap L+R, soundaktor L+R, rear-axle steer, -wireless charging (Qi), HUD projector, interior camera, gesture -control, massage modules dr+pa, passenger screen, rear screens L+R, -premium amplifier, DSP processor, AR headlight, digital OLED -taillights L+R, digital matrix headlights L+R, side radars FL+FR, -ultrasonic clusters front+rear, blind-spot modules L+R, night-vision, -driver-attention, EV rear motor inverter, EV rear-axle disconnect -clutch, secondary AWD coupling, trailer module. - -### Per-bank engine deep (64 DIDs) -2 banks × 32 fields each: turbo speed + inlet/outlet temp, -intercooler in/out temp, manifold absolute + boost pressure, -wastegate duty + position, VGT vane, EGR position, throttle position, -intake runner position, HP + LP fuel rail pressure, fuel pump duty, -injector duration, ignition advance, knock correction, O2 short + -long trim, cat efficiency, DPF soot + ash + pressure-drop + temp -in/out, SCR NOx in/out + efficiency, EGR cooler temp, oxidation cat -temp. - -### Per-zone HVAC (32 DIDs) -4 zones × 8 fields each: setpoint, actual, blower duty, temp flap, -defrost flap, face flap, foot flap, ambient sensor. - -### Brake-fluid + iBooster + pedal-feel (12 DIDs) -Reservoir level, fluid temp, age, water content, master cylinder -pressure, iBooster motor current + position + temperature, pedal -force + travel, park-brake L+R motor currents. - -### ADAS tracked-object stack (64 DIDs) -8 simultaneous tracked objects × 8 fields each: object ID, class -(car/truck/motorcycle/bicycle/pedestrian/animal/unknown), distance, -lateral offset, relative speed, confidence, track age, sensor-fusion -source bitmap. - -### Premium audio fine-grained (24 DIDs) -Per-channel gains (FL/FR/RL/RR/center/sub/surround L+R), amp temp + -supply + total power, DSP load, 7-band EQ, ANC active + attenuation, -road-noise level, speaker short + open bitmaps. - -### HV battery per-module (72 DIDs) -12 modules × 6 fields each: voltage, current, max temp, min temp, -SOC, SOH. - -### Coding blocks (+8 / +58 fields) -Lane Assist extended, ACC extended, AEB extended, V2X / Car2X, -OTA features, HSM / security, trailer, AVAS + soundaktor synthesis. - -### Routines (+76) -14 module-replacement procedures (engine, trans, ABS, steering, -airbag, cluster, BCM, gateway, radar, camera, EVCC, HV battery, EV -motor, OBC, DC-DC), KESSY + immobilizer + SSP relearns, key pairing -+ deletion, mileage + odometer + speedo calibration, central crash- -data clear, fuel-level + EV-range relearn, gateway component -protection, OTA install/rollback/verify, HSM key provision/zeroize/ -log export, 5 domain self-tests + ethernet switch + Car2X self-tests, -trailer module pair + calibrate, panoramic roof + convertible top -calibrations, exhaust flap + soundaktor calibrations, heat-pump + -aux heaters self-tests, LV aux battery test, wireless charging -calibrate, HUD test, interior camera + gesture calibrations, massage -+ passenger/rear screen tests, premium amp + DSP self-tests, AR -headlight + OLED tail + digital matrix calibrations, ultrasonic + -blind-spot + night-vision + driver-attention calibrations, EV rear -motor + disconnect clutch + battery junction + charge inlet tests. - -### DTC ext-data (+500) — long-tail P-codes (round 2) -Sparse second-pass through P14xx-P23xx + P2Cxx-P2Dxx with -additional offsets × 3 records (occurrence + miles_since_cleared -+ oem_status_byte). - -## [3.37.0] - 2026-05-08 — VW platform/MY splits + deep service routines (~45% ODIS) - -Fourth pass — adds 1,319 entries focused on the platform/MY axis -plus the service-routine and deep actuator-test gaps. Covers MQB / -MEB / MLB-evo / PPE / NSF / Modular CE platform metadata, MIB1-4 -head-unit generation specifics, DSG generation specifics -(DQ200/250/381/500/501), per-corner suspension actuator currents + -ride heights + air-spring pressures, V6/V8/V10/W12 per-cylinder -telemetry (cyl 5-12), bank-2 O2-sensor stack, broad door/alarm/ -TPMS/seat-memory/climate-zones coding blocks, 77 deep service -routines (ABS bleed per-wheel, DSG basic-setting, EPB pad change, -camshaft / timing chain / SCR / AdBlue / oil pump / coolant pump -/ headlight aim / matrix pixel / camera dynamic / radar zero / EV -inverter self-test / EV motor resolver zero / HV contactor + -pyro-fuse + IMD self-tests), 50 deep actuator tests, 775 long-tail -VAG P-codes. - -### catalogs/vw.json — 6,179 → 7,498 entries - -| Section | v3.36 | v3.37 | Change | -|---|---|---|---| -| ECUs | 75 | **75** | — | -| DIDs | 2,438 | **2,774** | +336 | -| Routines | 383 | **460** | +77 | -| Coding blocks | 128 (857 fields) | **133 (915 fields)** | +5 / +58 | -| Adaptations | 517 | **565** | +48 | -| Actuator tests | 248 | **298** | +50 | -| Live PIDs | 393 | **421** | +28 | -| DTC extended-data | 1,997 | **2,772** | +775 | - -### Platform / MY splits (272 DIDs) -- **Platforms (8 × 8 DIDs = 64):** MQB, MQB-evo, MEB, MEB+, MLB-evo, PPE, NSF, Modular-CE — variant ID, body style, wheelbase, front+rear track, kerb / GVW / payload weights. -- **MIB head-unit (5 × 16 DIDs = 80):** MIB1, MIB2, MIB2-High, MIB3, MIB4 — HW part no, SW train, map version + region, SSD total + free, RAM, SoC die temp, uptime, boot count, OTA status + progress, voice / nav / media / connectivity-box engine versions. -- **DSG (5 × 16 DIDs = 80):** DQ200, DQ250, DQ381, DQ500, DQ501 — K1+K2 clutch temp + wear + engagement count, oil temp + pressure + age + quality, mechatronic temp + supply V, lifetime shift counts (total, kickdown, manual, launch). - -### Per-corner suspension (32 DIDs) -4 corners × 8 fields: DCC actuator current, DCC setpoint + actual response, air-suspension ride height, air pressure, valve current, strut temp, compressor branch runtime. - -### V6/V8/V10/W12 per-cyl + bank-2 (80 DIDs) -- Cylinders 5-12 × 8 fields each (64 DIDs): drive-cycle + lifetime misfire counts, knock retard, injection correction, relative compression, EGT, individual lambda, coil resistance. -- Bank-2 O2 sensors 1-4 × 4 fields (16 DIDs): voltage, current, temp, heater current. - -### Coding blocks (+5 / +58 fields) -Door FL extended (windows + mirrors + door unlock strategy), -alarm sensor zones, TPMS per-axle thresholds (summer/winter/loaded), -driver-seat memory extended (3 slots × 4 axes), climate zones. - -### Deep service routines (+77) -Per-wheel ABS bleed, DSG K1/K2 clutch adapt-reset + basic setting + -oil-fill, Haldex priming + clutch adapt, EPB workshop mode + -basic setting, steering / yaw / ESP zeros, throttle / idle / -misfire / catalyst / AFM / MAP / fuel-trim adapt-resets, intake -runner + camshaft + timing chain learns, SCR + AdBlue routines, -oil / coolant pump + thermostat + fan-clutch tests, wiper park -position, sunroof / convertible / tailgate / trunk / window / -mirror calibrations, seat init, headlight aim + AFS + matrix + -laser + OLED calibrations, radar + camera dynamic calibrations, -sensor calibrations (rain, humidity, interior+outside temp), ESP -self-test, airbag crash-data clear, seatbelt pretensioner replace, -battery replacement (BEM relearn) + capacity test, alternator load -test, starter test, EV inverter self-test + motor resolver zero + -charge-door calib + thermal loop bleed + battery isolation + -pyro-fuse + contactor + IMD self-tests. - -### Deep actuator tests (+50) -ABS pump + per-wheel inlet+outlet valves, EPB motor extend/retract -L+R, air-suspension compressor + relief valve, steering assist / -column-lock, wiper / washer (front+rear+headlight), horn low/high, -starter test, alternator field, fuel pumps (LP+HP), throttle motor, -intake runner, wastegate, VGT, EGR + EGR cooler bypass, SCR + -AdBlue pump, exhaust flap, AVAS, EV front+rear motor demos, EV A/C -compressor, HV chiller valve, EV radiator fan, HV battery heater, -EV charge port lock + indicator LED, HV pyro-fuse continuity, -HV positive + negative + pre-charge contactors. - -### DTC ext-data (+775) — long-tail VAG P-codes -Sparse sweep across P14xx-P17xx, P18xx-P19xx, P20xx-P23xx, P2Cxx-P30xx ranges × 4 record types. - -## [3.36.0] - 2026-05-08 — VW catalog third deeper-niche pass (~40% ODIS) - -Continuing the same parity pass — adds 1,435 more entries focused -on driver-coaching telemetry, last-32-trip computer history, broad -per-component supplier/HW-rev/plant/manufacturing-date sweep, -CO2 + emission strategy adaptations (start-stop thresholds, cyl -deactivation envelope, coast/sailing parameters, SCR/DPF/GPF/EGR, -cold-start, EV regen/thermal envelopes), broad B/C/U-code DTC -ext-data sweep, per-key extended (8 keys × 8 fields), live -coaching telemetry. - -### catalogs/vw.json — 4,744 → 6,179 entries - -| Section | v3.35 | v3.36 | Change | -|---|---|---|---| -| ECUs | 75 | **75** | — | -| DIDs | 1,910 | **2,438** | +528 | -| Routines | 368 | **383** | +15 | -| Coding blocks | 128 | **128** | — | -| Adaptations | 484 | **517** | +33 | -| Actuator tests | 238 | **248** | +10 | -| Live PIDs | 377 | **393** | +16 | -| DTC extended-data | 1,164 | **1,997** | +833 | - -### Driver-coaching telemetry (80 DIDs) -20 metrics × 4 windows (lifetime / 30d / 7d / last-trip): harsh -brake/accel/corner counts, over-speed minor/major, idle time, eco -+ anticipation + smoothness + attention scores, phone-use events, -hands-off/drowsiness/lane-departure warnings, AEB interventions, -ACC manual disengagements, regen efficiency, coast/eco/sport -distance. - -### Trip-computer extended (256 DIDs — last 32 trips × 8 fields) -Per-trip: distance, avg/max speed, avg consumption, duration, -start epoch, regen kWh, idle seconds. - -### Per-component supplier sweep (128 DIDs across 32 ECUs) -Each ECU: supplier code (4-byte BCD), HW revision (4-char ASCII), -manufacturing plant (3-char ASCII), manufacturing date (YYWWD BCD). - -### Per-key extended (64 DIDs across 8 keys) -Per stored key: unique ID, battery mV, lifetime button-press count, -last-used epoch, profile index, UWB-ranging-active flag, digital-key -phone-paired flag, valet-mode flag. - -### CO2 / emission strategy adaptations (33 channels) -Start-stop envelope (min coolant temp, min battery V, min SOC, max -+ min ambient), cyl-deact min/max speed + load envelope, coast -disengage speed + decel, predictive-efficiency look-ahead, SCR -dosing factor, DPF/GPF regen distance + max temp, EGR steady + -transient max-open %, cold-start idle target rpm + duration, grid -heater max current, cat light-off target, secondary-air duration, -fuel-cut min rpm, EV regen aggressiveness D/B-mode, EV one-pedal -creep, EV motor temp target, EV battery thermal pre-charge + DC -charge envelopes. - -### DTC ext-data (+833) — broad B/C/U-code sweep -B-codes 96 codes × 4 records = 384 (BCM/airbag/lighting/seat -motor/window-motor/door/sunroof/tailgate/heater zone niche faults), -U-codes 70 codes × 4 records = 280 (lost-comm + invalid-data on -network nodes covering all major buses), C-codes 45 codes × 4 -records = 180 (per-wheel ABS + EPS + EPB + air suspension + iBooster -+ ADAS chassis faults). - -### Live PIDs (+16) -Live eco/smoothness/attention/anticipation scores, live load factor, -throttle change rate, brake pressure, steering rate, yaw rate, -long+lat accel, ACC target distance + relative speed, lane offset, -phone-detected flag, driver drowsiness level. - -### Routines (+15) -Driver-coaching reset, trip-history clear, supplier-code re-learn -from neighbours, CO2 strategy re-learn, SCR dosing recal, force GPF -regen, grid-heater test, secondary-air test, digital-key phone -re-pair, UWB key recalibrate, valet/teen-driver PIN reset, -predictive efficiency re-learn, EV regen + one-pedal pedal-feel -recalibrate. - -### Actuator tests (+10) -Driver-coaching audio cue test, steering-wheel haptic cue test, -cluster + HUD coaching-overlay tests, predictive-efficiency demo, -grid heater + secondary air pulse tests, start-stop force-disable, -cylinder-deactivation demo, sport-diff lock-up. - -## [3.35.0] - 2026-05-08 — VW catalog deeper-niche pass (~37% ODIS) - -Continuing the VW push to set the parity bar before applying the -template to other OEMs. Adds 969 more entries focused on niche -subsystems that previous passes only sampled: per-cell EV battery -telemetry, per-bulb hours-on counters, per-zone ambient lighting -RGB, Audi Function-on-Demand subscription metadata, per-ECU -programming history + signature/checksum, bus topology / network -discovery, per-camera lens shading + per-radar waveform parameters, -matrix-headlight per-pixel state, niche adaptations (seat massage -zones, Webasto fuel calibration deep, HUD/CarPlay/AA fine-grained, -ambient lighting calibration), and 13 new VAG-specific P-code ext -ranges (1Axx-1Fxx + 25xx-2Bxx). - -### catalogs/vw.json — 3,775 → 4,744 entries - -| Section | v3.34 | v3.35 | Change | -|---|---|---|---| -| ECUs | 75 | **75** | — | -| DIDs | 1,417 | **1,910** | +493 | -| Routines | 348 | **368** | +20 | -| Coding blocks | 124 (782 fields) | **128 (857 fields)** | +4 / +75 fields | -| Adaptations | 412 | **484** | +72 | -| Actuator tests | 206 | **238** | +32 | -| Live PIDs | 341 | **377** | +36 | -| DTC extended-data | 852 | **1,164** | +312 | - -### Per-cell EV battery telemetry (192 new DIDs) -0x3C00-0x3C5F: per-cell voltage in mV (96 cells); 0x3C60-0x3CBF: -per-cell temperature (96 sensors). Covers MEB 8-pack / 12-pack + -e-tron 36-cell modules with cell-level granularity. - -### Per-bulb hours-on counters (32 DIDs) -0x3400-0x341F: lifetime hours-on for every individual lighting -circuit — low/high beam L+R, DRL L+R, all turn signals (incl. -mirrors), brake L+R+CHMSL, reverse L+R, fog F+R, license, interior -(dome/map L+R/trunk/glovebox), puddle (4 corners), position lamps. - -### Per-zone ambient lighting (62 DIDs + 62 adaptations) -31 ambient zones (dash, doors, footwell, console, headliner, -cup holders, speaker rings, A/B/C-pillar strips, dash strip, -door strips) — each with RGB+brightness DID for live read-back -plus calibration adaptation for static color/intensity. - -### Audi Function-on-Demand metadata (34 DIDs) -17 FoD features × 2 DIDs each (active flag + subscription expiry -epoch): Matrix high-beam, DAB+, navigation premium, smartphone -interface, wireless CarPlay/AA, TSR, ACC upgrade, Park Assist Plus, -Remote Park Pilot, voice premium, connected nav, live traffic, -hotspot, Audi connect remote, Car2X, predictive efficiency. - -### Per-ECU programming history (72 DIDs across 18 ECUs) -For 18 high-traffic ECUs: programming attempt count, last -successful programming epoch, software checksum (truncated SHA1), -software signature verification status (not_signed/valid/invalid). - -### Bus topology / network discovery (16 DIDs) -Per-bus node bitmaps (CAN powertrain/extended/infotainment, LIN1+2, -FlexRay A+B, MOST150, Ethernet), bus load %, bus-off error counts, -Ethernet link speed + packet drop count. - -### Per-camera lens shading (21 DIDs across 7 cameras) -Front main + wide, rear, mirrors L+R, front grille (top-down), -interior driver-attention — each with shading correction matrix, -white-balance gain (R|G|B), and lens temperature. - -### Per-radar waveform parameters (30 DIDs across 5 radars) -Front + 4 corners — chirp bandwidth, chirp duration, TX power, -antenna blockage estimate, horizontal + vertical alignment. - -### Matrix-headlight per-pixel state (64 DIDs) -0x3B00-0x3B3F (left) and 0x3B40-0x3B7F (right) — per-pixel PWM -state for 32-pixel matrix-LED arrays. - -### New niche adaptations (40 channels) -Seat massage program/intensity (driver + passenger), seat lumbar -+ bolster + cushion firmness, ambient global brightness + dynamic -mode + welcome/leaving/coming-home duration + speed dependence, -Webasto deep calibration (fuel priming pulses, glow-plug preheat, -combustion-air min/max PWM, fuel-pump min/max Hz, target CO2), -trip auto-reset thresholds, cluster + HUD day/night brightness, -HUD geometry offsets, CarPlay/Android Auto audio priority + 5GHz -preference, voice wake-word sensitivity + local recognition, -Car2X warning distance. - -### New live PIDs (36) -On-board charger telemetry (input V/A, efficiency, temp), DC-DC -(HV in, LV out, current, efficiency, temp), front+rear inverter -IGBT temps, front+rear motor stator temps + torque + rpm, HV -battery (SOC, SOH, pack V/A, max/min/avg cell V, max/min cell -temp, isolation kΩ), DC + AC charge actual power, thermal loop -(coolant temp, pump rpm, chiller state, PTC heater W, heat-pump -COP, compressor rpm). - -### New routines (20) -Programming history clear, SW signature recheck, bus topology -rediscover, camera shading recalibrate, radar alignment self-check, -matrix headlight pixel sweep, ambient zone color sweep, FoD -subscription refresh, EV cell balance force, capacity remeasure, -OBC/DC-DC self-test, seat position end-stop + massage zone calib, -Webasto burn-off + fuel-quality recal, HUD geometry recal, cluster -TFT pixel test, MMI touchpad self-test, microphone array test. - -### New coding blocks (4 blocks, 75 fields) -Ambient lighting per-zone enable bitmap (31 fields), FoD features -enabled bitmap (17 fields), matrix headlight features (14 fields), -MMI features (12 fields). - -### New actuator tests (32) -Matrix headlight L+R pixel sweep, OLED rear-light choreography, -ambient zone tests (red/green/blue/white/chase + welcome/leaving -choreography), seat massage + climate demos, Webasto burn-off + -glow plug check, HUD + cluster TFT test patterns, MMI touch grid, -microphone array loopback, speaker sweeps (FL/FR/RL/RR/center/sub), -EV OBC + DC-DC self-tests, HV chiller + heat-pump self-tests, -HV thermal pump priming, camera shading capture. - -### DTC extended-data (+312) -Adds VAG-specific P-codes in 1Axx, 1Bxx, 1Cxx, 1Dxx, 1Exx, 1Fxx, -25xx, 26xx, 27xx, 28xx, 29xx, 2Axx, 2Bxx ranges — each with -4 extended records (occurrence_counter, aging_counter, -miles_since_cleared, oem_status_byte). - -## [3.34.0] - 2026-05-08 — VW catalog deep-push toward ceiling (~31% ODIS) - -Continuing the public-source crawl + VCDS-dataset reference push -toward the ~50-60% ceiling for non-commercial sources. This release -nearly doubles total entries again (2,377 → 4,557) with the biggest -gains in coding bit-fields (210 → 782, 3.7×) and DTC extended-data -(220 → 852, 3.9×). - -### catalogs/vw.json — 2,377 → 4,557 entries (456 KB → 850 KB) - -| Section | v3.33 | v3.34 | Change | -|---|---|---|---| -| ECUs | 75 | **75** | — | -| DIDs | 1,197 | **1,417** | +220 | -| Routines | 261 | **348** | +87 | -| Coding blocks | 16 (114 fields) | **124 (782 fields)** | +108 / +668 fields | -| Adaptations | 216 | **412** | +196 | -| Actuator tests | 76 | **206** | +130 | -| Live PIDs | 56 | **341** | +285 | -| DTC extended-data | 56 | **852** | +796 (15.2×) | - -### Coding bit-fields (210 → 782 fields, 124 blocks) -Biggest single-section growth: extended BCM long-coding bytes 5-15, cluster extended (16 fields), engine extended (18 fields), climate extended (10), ABS extended, EPB, Haldex, Quattro, EV drivetrain + battery + OBC + EVCC extended (V2G/V2L/V2H + ISO 15118 PnC + DIN 70121 + CCS/CHAdeMO/GB-T/NACS), ADAS Lane Assist + ACC + AEB (with pedestrian/cyclist/intersection + min/max speed + warning lead), TPMS extended (winter/summer + loaded threshold), Park Assist extended, Telematics extended (20 fields), IVI extended (21 fields), Alarm extended, KESSY extended (UWB + phone-as-key), Lighting matrix (matrix segments + AFS + laser + OLED + welcome animation), Rear lighting, HUD extended (16 fields), Ambient lighting extended (10 fields), Panoramic roof + electrochromic dimming, Trunk, Driver seat extended (15 fields with memory + bolster + thigh extension + massage + heat zones + leather), Audi Drive Select coding (13 modes), Rear-axle steer, DCC extended, Pre-Sense extended, Climate per-zone (driver/passenger/rear-L/rear-R), Engine warmup + idle strategy, AdBlue/SCR + DPF, Convenience lighting + audible chimes + windows + mirror coding, Child safety + ISOFIX, EV thermal management + regen + charge strategy, Active aero, Valet mode, Teen driver, Audi Active Lane Assist + MMI extended (RS Track Data Recorder + Function on Demand + Phone Box + Alexa + passenger screen + offline voice + charging planner), Performance recorder, Magnetic Ride, Predictive Active Suspension, Side Assist, TSR, TJA + Trained Parking + Highway Assist, Active Cruise extended, Night Vision extended, Drive data recorder, Engine torque limits, Exhaust flap + sound synthesis + AVAS, Quattro extended (Torsen/Haldex/Ultra/e-axle types), Sport differential extended, Haldex Gen5 extended, Tow hitch extended, Audi Emergency Assist, RSE extended, Personalisation, Wireless charging extended, Trunk motor extended, Park-assist camera, Rear window blind, Headrest motor, EV e-axle, EV inverter (SiC tech), HomeLink. - -### DTC extended-data records (56 → 852, 15.2×) -Full P0xxx generic range, VAG-specific P1xxx + P2xxx + P3xxx + HV/hybrid (P0Axx-P0Dxx), C-codes (per-wheel + ABS pump + valves + EPS + EPB + air suspension + iBooster + ADAS + radar), B-codes (airbag squibs + crash sensors + door switches + BCM + per-bulb circuits + headlight aim/swivel motors + tow hitch + DCC dampers + sunroof + tailgate + per-seat motors + heater zones + mirror motors), U-codes (CAN + FlexRay + lost comm to all major ECUs + invalid data) plus per-cyl misfire aging (5-8) + environmental data records, catalyst/turbo/HV battery environmental data + status bytes, miles_since_cleared records for all major DTCs. - -### DIDs (663 → 1,417 in two releases) -Continued depth in: Audi Drive Select active mode + per-component assignments, Quattro per-side sport diff + diff oil temp, Magnetic Ride + per-corner MR fluid, Predictive Active Suspension actuator torque per corner, Audi Pre-Sense Front/Rear/Side, RS7-specific (Drift split rear + dynamic steering ratio + active aero + V8 cyl deact), EV drivetrain torque target/actual + per-wheel vectoring + dynamic lift + hill-climbing assist, EV pack age + capacity loss + DC/AC charge counts + thermal events + recall status, Audi MMI deep, engine extended runtime in mode (open-loop / closed-loop / warmup / overrun / above 4k/6k/redline lifetime), engine cold-start (freezing protection + grid heater + block heater), transmission per-gear shift counts 1-7+R, ABS event counters + per-wheel pad wear estimates, per-key data (ID + last used + battery + button count + profile), per-ECU programming + production dates + workshop codes for 14 ECUs, per-wheel brake temp + disc thickness + pad thickness + tire circumference + size string, Audi MMI deep diagnostics, track data recorder, Quattro Ultra (FWD-only state + clutch temp + engagement count), EV battery production (serial + supplier + production date + module count + cells per module), EV charge history (24h/7d/30d sessions + lifetime kWh + max kW + V2X total + failed sessions), service-history per-item (last oil change km + date + workshop, last inspection, last brake pad, last DSG oil, last Haldex, last battery + air/cabin filter + spark plugs + brake fluid + DPF clean + AdBlue refill at km), vehicle metadata (SALAPA + paint + interior + country + market + steering side + emission class + drivetrain class + assembly plant + production date + first registered + warranty), engine metadata (part number + displacement + cylinder count + layout + aspiration + fuel type + max power/torque + redline + compression + bore + stroke), transmission type + supplier + oil type + capacity, drivetrain layout, Audi chassis + model + year + facelift + trim + sport/RS package, Audi e-tron + VW ID. specifics (pack kWh + drivetrain class + charging protocol + max DC kW lifetime), PHEV pack + electric range + pure-EV distance + total + %. - -### Routines (98 → 348 in two releases) -Drive Select reset + Individual save + lap timer/g-meter clear + Drift unlock + Launch arm/disarm + sport diff calib + V8 deact test + Magnetic Ride + predictive susp + Pre-Sense full + Quattro proactive calib, EV battery (full pack balance + thermal purge + diagnostic charge full + 30s pulse capacity + resistance + isolation + pyrofuse arm + module V/temp scan + recall firmware), motor offset front+rear, OBC 3-phase + V2G test, DC-DC efficiency, charge port motor full cycle, EV thermal loop tests + heat-pump self-test, tow hitch full + Trailer Assist + per-circuit lighting tests, telematics full self-test + reset, oil replacements, brake disc break-in + pad calib, AEB with target, headlight + ambient lighting full inventories, DCC zero/drive calib + active ARB zero, KESSY lockout reset + full antenna, rear-axle steer full zero/sweep, panoramic + trunk anti-pinch + max height, Pre-Sense pretensioner + lane keep + predictive efficiency + emergency assist, MMI voice/navi/radio/DAB rescan + per-speaker + microphone + fan tests. - -### Adaptations (81 → 412 in two releases) -Drive Select defaults + RS Mode 2 unlock + Individual per-component + Launch RPM + Drift max speed + lap timer auto-record / Magnetic Ride + predictive susp anticipation + active ARB / Pre-Sense defaults + brake aggression + belt force / Quattro proactive + baseline rear + max rear / RS dynamic chassis + V8 deact threshold + active aero / EV per-mode torque + speed + regen blending + one-pedal + charge limits + off-peak + thermal precondition / MMI defaults + EQ + balance/fader / engine after-run + heaters + redline + max torque/power + top speed / transmission max torque + thermal limits + creep + kickdown / ABS brake assist + AEB + hill descent + off-road/snow + brake blending regen / lighting DRL + coming/leaving + high-beam min + emergency brake signal + aim offsets + dynamic curve + matrix glare-free / panoramic + trunk + seat + telematics + alarm + park + tow + Webasto + RSE + HUD + ambient defaults / Engine lambda authority idle/load + pre/post-cat target + lean-burn threshold + stratified burn / intake/tumble/swirl flap min/max / low-pressure EGR + cooler bypass / diesel smoke + injection timing / SCR efficiency + AdBlue concentration / DSG K1/K2 pre-fill timing + micro-slip + max torque / per-wheel speed offsets + yaw/lateral g/SAS zero / EPS torque zero + assist curve + low-speed extra / KESSY radius + walk-away + max keys / TPMS summer/winter + winter temp + loaded threshold / park assist speeds + slot offsets / EV charge default SOC + max AC/DC + min battery temp + low/critical SOC / EV motor max torque + regen max + one-pedal decel + creep speed. - -### Actuator tests (76 → 206 in two releases) -Per-cyl injector pulses 1-8, per-cyl ignition coil pulses 1-4, per-glow-plug heat tests 1-4, VVT solenoid tests intake/exhaust per bank, valvelift, intake runner, tumble flap, vacuum pump, wastegate, VTG, low-pressure EGR, exhaust throttle, SCR dosing test, AdBlue priming + heater, post-injection DPF heating, DSG per-PCS solenoids 5, DSG park-lock + pump, Haldex pump, ABS per-wheel inlet+outlet solenoids 8 + 60s pump bleed, EPB motors per side, matrix LED sweep, headlight swivel + aim per side, rear dynamic blinker, horn + alarm siren, panoramic roof + sunshade, electric trunk, fuel/charge door release, driver seat motors + lumbar + massage + heater max + vent max, EV charge port lock + battery cooling pump + battery PTC steps + DC-DC load + OBC handshake + compressor + inverter capacitor discharge + pyrofuse continuity, HVAC blower steps + AC clutch + defoggers + PTC steps, Drive Select demo sweep + RS Mode test + active anti-roll + active aero/spoiler + Magnetic Ride + predictive susp + rear-axle steer + Pre-Sense self-test, iBooster + traction control + ABS full self-test + yaw/lateral g zero offset, matrix LED per-segment + high-beam dynamic calib + country pattern demo + rear blinker demo, ambient zone color demos + full inventory, alarm horn chirp + full alarm, KESSY entry + immobilizer release tests, Emergency Assist + Lane Assist intervention tests, EV charge AC/DC/PnC handshake + V2G/V2L tests + thermal full loop + Octovalve sweep + cell balance check. - -### Live PIDs (56 → 341 in two releases) -J1979 standard PIDs + comprehensive engine/transmission/ABS/EPS/cluster/climate/BCM/ADAS/TPMS/EV streams + per-cylinder live data (injector V offset 1-4, ignition dwell + secondary V 1-4, diesel pre/post injection 1-4, cylinder balance 1-4) + DSG K1/K2 torque + microslip + pump duty + launch armed + per-wheel brake temp + intervention counters + EPS deep + climate live + EV per-cell stream + motor phase currents + OBC + DC-DC + battery thermal streams. - -### Estimated ~31% ODIS coverage -Up from ~22% in v3.32. Public-source ceiling for combined approach is ~50-60%. Coding fields are the leader at ~39% — close to ceiling. DTC ext at 11% (up from 3%) has room. Adaptations at 16.5%. Live PIDs at 23%. Routines at 23%. - -## [3.33.0] - 2026-05-08 — VW catalog push toward ceiling (~30% ODIS) - -Continuing the public-source crawl + VCDS-dataset reference push. -Real ceiling for non-commercial sources is 50-60%; this release -moves from ~22% to ~30% with a strong gain in DTC extended-data -(now ~8% of full ODIS catalog, the area that lagged most in v3.32). - -### catalogs/vw.json — 2,377 → 3,230 entries (456 KB → 615 KB) - -| Section | v3.32 | v3.33 | Change | -|---|---|---|---| -| ECUs | 75 | **75** | unchanged | -| DIDs | 1,070 | **1,197** | +127 | -| Routines | 261 | **348** | +87 | -| Coding blocks | 42 (210 fields) | **42 (210 fields)** | unchanged | -| Adaptations | 216 | **344** | +128 | -| Actuator tests | 160 | **160** | unchanged | -| Live PIDs | 123 | **233** | +110 | -| DTC extended-data | 220 | **621** | **+401 (2.8×)** | - -### DTC extended-data records — 220 → 621 (the biggest gain) -Full P0xxx generic range coverage: VVT both banks, lambda heaters all 4 sensors, ambient temp, fuel pressure system, IAT sensor 2, all O2 sensor states (low/high/slow/no-activity/heater) per position, fuel temp, fuel rail pressure, all 8 cylinder injectors high+low circuits, engine over-temp/over-speed, throttle B+C, fuel pump primary/secondary, turbo boost sensor, wastegate solenoid, injection pump, per-cylinder misfire aging (1-4), CKP intermittent, CMP low/high, ignition coils A-H per cylinder, glow plug heaters, EGR sensor A+B low/high, secondary air valves + pump relay, catalyst efficiency Bank 2 + warm-up, EVAP purge open/short/vent/leak, fuel level sensor range/low/high, exhaust pressure sensor, EVAP vent low/high, VSS A low/intermittent, cold-start rough idle, oil pressure sensor low/high, cooling fan speed, AC pressure sensor, intake air heater, system voltage low/high, brake switch, thermostat heater, sensor reference voltage A+B, TCC freeze-frame. - -VAG-specific P1xxx: lambda voltage too high, O2 heater short-to-plus B1/B2, O2 control limit, lambda Bank 1 short/open, long-term fuel trim B1+B2 too lean/rich, engine load implausible, O2 sensor heater electrical fault per position, cyl injector short to plus (1-4), cooling system, engine torque monitoring, camshaft Bank 1, CKP-CMP correlation, internal ECM monitoring, tank ventilation valve short, secondary air injection valve short/open, EVAP leak detection pump short, fuel pump relay malfunction/short, intake camshaft mechanical, TPS implausible, boost pressure control valve, terminal 30 low, MIL request from TCM, coolant signal from TCU implausible, transmission supply voltage, pressure modulation valve N218, engine intervention from TCM, multi-function range switch, aux transmission speed sensor. - -P2xxx: intake runner, fuel composition, VVT B Bank 1 low/high, post-cat lambda Bank 2 lean/rich, throttle actuator range/high/forced limited RPM/power management, system rich/lean at idle/off-idle/higher-load Bank 1+2, lambda contamination, fuel pressure regulator 2, ignition coil A primary low/high, per-cyl knock threshold (1-6), EVAP leak detection pump, switching valve, vent valve stuck closed, turbo boost control position sensor. - -P3xxx + P0Axx-P0Dxx HV / hybrid: HV battery system performance, powertrain limp mode, contactors stuck closed/open + pre-charge, hybrid battery overheat + temp too high, B+ B- contactors, DC-DC 12V current low/high, drive motor phase U/V/W performance, hybrid PCM performance, thermal management, OBC AC input voltage, charge port lock motor. - -C-codes: per-wheel speed sensor range/signal/freq error (4), per-wheel inlet+outlet valve circuits (8), pump motor, valve relay, master cylinder pressure, brake switch, yaw + lateral + longitudinal sensors, ESP disabled, steering position, EPB calibration + pad-wear, adaptive damper actuator per corner, air suspension reservoir, rear-axle steering position, side radar FL/FR calibration. - -B-codes: per-seat belt switches, pretensioner squibs, curtain airbag squibs, crash sensors (5 positions), knee airbag, BCM door ajar switches per door (4), wiper motors front/rear, headlight swivel motors per side, window motors per door (4), sunroof motor, trunk motor, KESSY antennas per corner (4), immobilizer auth/key learn, heated seats per zone (4) + heated steering wheel + Webasto, TPMS sensor per wheel learn fail (4), park assist sonars 8 positions, surround view cameras (4), HUD, telematics modem + eCall test, ambient lighting per zone (4). - -U-codes: HS CAN '+' circuit low/short, MS CAN performance, FlexRay channel A+B bus-off, lost comm transfer case + 4WD clutch + multi-axis accel + steering angle + EPS + body 'B' + side restraints + immobilizer + cruise distance range sensor + body gateway + HVAC + hybrid PCM + onboard charger, invalid data from cruise + ABS + brake + BCM + front camera + front radar + gateway + telematic. - -### DIDs added (+127) -Audi Drive Select active mode + per-component assignments (engine/steering/dampers/DSG/sport diff/exhaust/climate + Individual mode + RS Mode 2 + Drift mode + Launch control + lap timer best/last + g-meter max), Quattro per-side sport diff clutch lock + diff oil temp + actuator current, Magnetic Ride state + per-corner MR fluid current, Predictive Active Suspension + per-corner actuator torque, Audi Pre-Sense Front/Rear/Side states + intervention count + Active Lane Assist torque + Efficiency Assistant + Predictive Efficiency, Quattro deeper (clutch pressure + target/actual rear torque + temp + oil quality + proactive engaged), RS7-specific (Drift split rear, dynamic steering ratio, active aero, dynamic chassis stiffness, torque vectoring, V8 cyl deact mode), EV drivetrain torque target/actual total + split + per-wheel vectoring + dynamic lift + hill-climbing assist + creep, EV pack age (avg cell age + calendar age + capacity loss + DC/AC charge counts + avg/max DC kW + thermal events + low-temp events + recall status), Audi MMI deep (active profile + lifetime boots + voice engine + Function on Demand + active radio band + DAB ensemble + track metadata + paired phone + navi distance + TJA eligible), engine extended (post-intercooler temp + back pressure + ambient pressure + altitude + air density + runtime in open/closed-loop/warmup/overrun/above 4k/6k/redline lifetime), engine cold-start (freezing protection + grid heater + block heater + freeze plug temp + oil pre-lub + water pump electric state + after-run + after-run remaining), transmission torque (capacity max + lifetime max + clutch temp target + max lifetime + oil pressure max + per-gear shift counts 1-7+R), ABS event counters (full braking + emergency stop + skid L/R + max master pressure + max yaw + max lateral g + per-wheel pad wear estimates). - -### Routines added (+87) -Audi Drive Select reset + Individual save + lap timer / g-meter clear + Drift unlock + Launch arm/disarm + torque split calib + torque vectoring calib + active aero calib + V8 cyl deact test + Magnetic Ride calib + predictive susp calib + Pre-Sense full self-test + Quattro proactive calib. EV battery (full pack balance + thermal purge + diagnostic charge full + 30s pulse capacity + resistance + isolation + pyrofuse arm + module V scan + module temp scan + recall firmware), motor offset front+rear, OBC 3-phase + V2G test, DC-DC efficiency test, charge port motor full cycle, EV thermal compressor + battery + motor + cabin loop tests + heat-pump self-test, charging session log clear. Tow hitch full calib + Trailer Assist calib + brake circuit test + per-circuit lighting test (left/right/brake/reverse/license). Telematics full self-test + data session reconnect + PDP reset + clear paired phones + factory pair + clear MMI profiles. Haldex/transfer-case/rear-diff oil replacement, brake disc break-in + pad calibration, AEB self-test with target, ABS pump priming. Headlight full zero calibration + matrix LED full inventory + swivel zero per side + rear lighting dynamic test + ambient lighting full inventory. DCC dampers zero + drive calib + active ARB zero. KESSY immobilizer lockout reset + full antenna test. Rear-axle steer full zero + sweep test. Panoramic roof force close + anti-pinch calib + trunk lid anti-pinch calib + max height program. Audi Pre-Sense seat belt pretensioner calib + lane keep camera static/dynamic + predictive efficiency + emergency assist. MMI voice engine reset + navi DB reload + radio seek + DAB rescan + per-speaker test + microphone calib + head-unit fan test. - -### Adaptations added (+128) -Audi Drive Select defaults (default mode + RS Mode 2 unlocked + Individual per-component defaults + Launch RPM + Drift max speed + lap timer auto-record + g-meter default), Magnetic Ride default + predictive susp anticipation + active ARB aggression, Pre-Sense defaults (front/rear/side default ON + brake aggression + belt pretension force), Quattro proactive + baseline rear torque + max rear (drift limit), RS dynamic chassis + torque vectoring + V8 deact threshold + active aero threshold, EV power limits per mode (eco/normal/sport torque + max speed + regen blending + one-pedal decel + charge limits + DC limit + off-peak schedule + min battery temp + thermal precondition), MMI defaults (voice assistant + natural language + haptic + default screen + EQ + balance/fader + 3D sound), engine (after-run max + block heater min + grid heater min + redline + rev limiter + max torque/power + top speed), transmission (max input torque + thermal limit + emergency temp + creep torque + kickdown threshold), ABS (brake assist aggression + AEB default + hill descent + off-road / snow modes + brake blending regen %), lighting (DRL default + coming/leaving home + high-beam assist min + emergency brake signal + aim offsets + dynamic curve + matrix glare-free + emergency flash count), panoramic roof (anti-pinch + default position) + trunk (kick sensitivity + auto-close + min temp), seat (easy-entry offset + memory link to key + lumbar default + massage program/intensity), rear seat climate + heat defaults, telematics (data quota + OTA auto-install + only at charge + remote unlock + geofence + speed/curfew alerts), alarm (horn volume + interior motion + tilt + panic), park assist (volume + visual-only + remote park + max search speed), tow assist (max trailer + aggression), Webasto (max runtime + min battery V + max starts/day), RSE (max volume L+R + auto-dim), HUD (brightness + show nav/acc/speed/phone + position offset), ambient light (brightness drive/park + animate with doors/locking). - -### Live PIDs added (+110) -Engine streams (torque request driver/total/friction, fuel trim idle/partial-load Bank 1, lambda B2S1+B2S2, lambda IP/Vs B1S1, injector dead time, ethanol, EGR throttle, SCR NH3 storage, oil quality, alternator V/I/load %, A/C compressor load, cooling fan duty, brake booster vacuum, idle target/state/inhibit reasons), DSG (input/output/diff speeds, K1+K2 target pressures, oil pressure/quality, drive mode), Haldex motor current, Quattro torque to front + sport diff lock + per-side, ABS (per-wheel slip 4, per-wheel pressure 4, brake pedal pressed/position, yaw target, ESP mode), steering (SAS calib, EPS motor temp, assist mode, rear-axle steer angle), cluster (odometer, trip A, recent consumption, service distance, outside temp), climate (blower, compressor active + displacement, evap temp, AQ VOC, humidity, PM2.5), BCM (lock state, door bitmask), ADAS (front camera state, lane keep state, object count, intervention count, Emergency Assist state), EV deeper (pack SOC + SOH + capacity remaining, contactor neg state, derate active/reason, motor stator/rotor temp + DC link V, drive ready + mode + regen level + regen torque, charge port voltage/current/target SOC/time remaining/lock state/temp), OBC (state + AC input V/I + efficiency), DC-DC (HV input V + LV current), BSG (state + temp + 48V battery temp), 12V battery sensor (terminal V + current + SOH), ADAS master state, Drive Select active, RS Mode active, Magnetic Ride state, oil level/quality streams. - -### Honest coverage status -Estimated **~30% of full ODIS depth** for VW group (up from ~22% in v3.32, ~10% in v3.31). The remaining gap to the public-source ceiling (~50-60%) is mostly in deep coding-block bit-fields (each writeable DID has dozens of bit-level options on real ODIS) and in OEM-only DTC environmental-data records. Future v3.34+ releases continue both the VW depth push toward ceiling and the same JSON-only migration applied to the other 45 OEMs. - -## [3.32.0] - 2026-05-08 — VW catalog ~2× expansion (target ~25% ODIS depth) - -Approach: combined public-source crawl (Ross-Tech wiki, OBDeleven -public DB, VCDS adaptation tables) with VCDS-derived dataset -references. Realistic ceiling for this approach is ~50-60% of full -ODIS depth — this release pushes from ~10% to ~22%. - -### catalogs/vw.json — 1,146 → 2,377 entries (456 KB) - -| Section | v3.31 | v3.32 | Growth | -|---|---|---|---| -| ECUs | 32 | **75** | 2.3× | -| DIDs | 663 | **1,070** | 1.6× | -| Routines | 98 | **261** | 2.7× | -| Coding blocks | 16 (114 fields) | **42 (210 fields)** | 2.6× / 1.8× | -| Adaptations | 81 | **216** | 2.7× | -| Actuator tests | 76 | **160** | 2.1× | -| Live PIDs | 56 | **123** | 2.2× | -| DTC extended-data | 56 | **220** | 3.9× | - -### ECUs added (43 new sub-modules) -Per-door modules ×4, per-seat modules ×3, DCC adaptive dampers, rear-axle steering, panoramic roof, electric trunk, battery sensor, oil-level sensor, fuel pump module, active engine mounts, BSG mild-hybrid + 48V/12V converter, EV electric A/C compressor + heat pump, ambient lighting, digital cockpit, ADAS master, telematics, alarm, immobilizer Gen5, HUD, premium audio, level sensors, HV junction box, MEB rear e-axle, steering column, steering lock, immobilizer, side radar 4-corner, surround view, sound actuator, headlight bend control, transfer case, rear-seat console, smart 12V socket, night vision, headlight country control. - -### DIDs added (~400 new) -Engine: lambda IP/Vs/internal-resistance per sensor, injector voltage offset + dead-time + open-close per cylinder, diesel pre/main/post injection per cylinder, ignition coil dwell + secondary V + spark plug load per cylinder, ethanol content, EVAP test state, EGR throttle + cooler bypass + low-pressure EGR, SCR ammonia storage + dosing pump current/pressure + concentration measurement, DPF burn-off oxygen + max temp lifetime + sensor voltage, torque loss attribution, CKP/CMP signal amplitudes, vacuum pump pressure, active engine mounts. Per-door (window position/motor/lock/handle pull), per-seat (position H/V/tilt/recline/lumbar + memory + heater/vent/massage), DCC dampers per-corner, rear-axle steering, panoramic roof, electric trunk, BSG mild-hybrid (state/torque/temp/V/I/SOC + DC-DC + boost lifetime + regen energy lifetime), 12V battery sensor (V/I/temp/age/Ah/SOH/starts/low-V), oil level sensor, fuel pump module, EV electric compressor + heat-pump mode, ADAS Travel Assist 3.0 (state/L2/swarm/Predictive ACC/Emergency Assist/auto lane change/Pre Sense/AEB intervention), Night Vision (pedestrians/animals/warnings), rear camera, premium amplifier (state/brand/temp/3D), telematics (LTE/eCall/IMEI/ICCID/data/GNSS), ambient lighting per-zone, active grille shutter, active rear spoiler, EV per-module cell V min/max (12 modules × 2), cell balancing + module SOH + pack internal resistance + DC negotiated current + session avg/peak power, MEB rear e-axle motor + inverter + decoupling clutch, HV junction box + pyrofuse, Audi virtual cockpit + HUD, anti-theft alarm + immobilizer Gen5, steering column controls + MFL counts, active anti-roll bar (front+rear torque target+actual), park assist deep (steering torque + speed + slot dimensions + memory recording), trailer hitch (load + lighting test + brake voltage + Trailer Assist), IVI navigation (route + ETA + traffic + speed limit + road class), RSE (left+right state + brightness), headlight cornering bend motors, performance metrics (max RPM/speed/boost/lateral g/long g/torque/power lifetime + 0-100 + quarter mile + launch count + over-rev + over-temp), eco coaching, environmental sensors (rain/light/twilight/humidity/PM2.5/CO/NO₂), smart socket, EV scheduled charging + V2G, Webasto deep-dive. - -### Routines added (~163 new) -Engine adaptation/learn (lambda b1+b2, knock reset, misfire reset, EVAP test, secondary air, EGR/throttle/intake-runner/charge-air-throttle position, compression test per-cyl, cylinder balance, glow plug resistance, diesel injector balance, IMV adaptation, DPF oxygen calib + pressure offset zero + temp sensor offset, SCR/AdBlue tests + concentration calib, NOx pre/post calib, lambda heater test, dewpoint test, VVT solenoid test per bank, valvelift actuator test); engine output tests (fuel pump pressure learn, injector quantity test, intake throttle, wastegate, VTG, EGR actuator + cooler bypass, thermostat, coolant pump, oil pump pressure); transmission/AWD (DSG pressure characteristic learn, clutch lifetime reset, synchroniser test per gear, creep pressure learn, ATF level check, park-lock test, all solenoids test, oil quality reset, speed sensor offset, Haldex drain+fill + pump test, Audi sport diff calibration + torque split test); ABS/EPB (per-wheel speed offset calib, G/yaw/sensor offsets, brake pad change assist front+rear, full pump bleed, brake-disc dry-wipe, EPB caliper open/close per side, EPB static brake test); EPS (steering torque zero, full assist test, centring calib, rear-axle steer zero + full sweep); airbag (clear crash data, pyro resistance test, PODS calib, seatbelt buckle test, PASD toggle); BCM (window position relearn, mirror end-stop, sunroof end-stop, seat memory calib, door handle pull learn, anti-pinch calib, horn test, lighting test sweep); KESSY/immobilizer (key program/remove/clear, antenna test, PIN release/change); lighting (matrix sweep, high-beam assist calib, dynamic curve calib, aim per side, country program, rear dynamic blinker, brake segment test); IVI (factory reset, map update install, speaker sweep, microphone test, amp self-test); ADAS (front camera static+dynamic, radar static+dynamic, side radar 4-corner, lane assist camera, AEB self-test, ACC radar alignment, emergency assist self-test, night vision calib, rear camera calib, surround view 4-camera calib); TPMS, park assist, tow hitch, Webasto, climate (full flap basic setting, evaporator dry, compressor test, refrigerant pressure test, heat pump self-test, cabin filter reset, air quality calib); EV (HV isolation test, contactor sequence, pyrofuse arm, motor offset learn front+rear, OBC + DC-DC self-test, cell balance routine, diagnostic charge, thermal system purge, charge port lock test, AC + DC handshake test, capacity measurement, battery cool + heat tests); alarm (interior motion + tilt + glass break + horn/siren); telematics (eCall test, modem self-test, GNSS test, data session reset); DCC dampers, active anti-roll bar, active engine mount test, level sensor calib front+rear, HUD calib, digital cockpit factory reset, ambient lighting zone test. - -### Coding blocks added (26 new — 96 new fields) -Per-door modules ×4 (driver/passenger comfort + rear child-lock + window speeds + anti-pinch force), per-seat modules ×2 (memory slots + lumbar 4-way + massage + ventilation + easy-entry), DCC, rear-axle steering, panoramic roof, electric trunk (kick sensor + max height), 12V battery sensor (type + capacity + serial), telematics (eCall + We Connect + remote + tracking + carrier lock), alarm, tow hitch (motorised + Trailer Assist + max weight), Webasto (default runtime + remote start), premium amp (brand + speakers + sub + 3D), RSE (screens + headphones), HUD (AR + overlays), Night Vision (pedestrian + animal warnings), BSG mild-hybrid (boost strategy + coast mode), EV drivetrain (RWD/AWD/GTX), EV battery (capacity + chemistry + supplier + module count), EV OBC (max kW + 3-phase + V2G + V2L power), Trailer Assist, ambient lighting (RGB + zones + welcome show), wireless charging (Qi + power). - -### Adaptations added (~135 new) -Engine (start window, glow plug pre/after, idle offset warm, oil pressure / coolant warning, over-rev threshold, torque mgmt aggression), fuel (octane, E85, low-fuel L+km), lambda authority per bank, knock correction, ignition (offset + max), turbo (max boost, overboost burst, wastegate min/max), DPF (regen min/max temp, request/cancel %, AdBlue warning + block-engine km), EGR, VVT (max advance per bank), ESS Start-Stop (battery V, coolant, incline, AC inhibit, trailer inhibit), torque smoothing, throttle response, DSG (creep torque, launch RPM, overheat, pre-fill, default mode, thermal limit, emergency mode, micro-slip, kickdown, park-lock), ABS/AEB (brake assist threshold, AEB min/max speed, warning lead, hill-hold release/duration, trailer stability sensitivity, brake-disc dry-wipe, pedal pulsation), EPS (assist curve, lane-assist torque, returnability, speed-dependent table), cluster warnings (low fuel km, seatbelt chime speed/duration, door-open chime, brake pad/oil quality warnings, max warnings, default view), BCM comfort (window full-press speed, mirror dip, auto-headlight lux, auto-wiper sensitivity, lane change blink, panic lock, auto-relock, locator lights, horn chirp + volume, seat heater + steering wheel + rear defog defaults), ambient lighting (default colour per zone × 4 + brightness day/night + animate + welcome), IVI (CarPlay/AA auto-launch, voice wake, speed-comp volume, navi voice, default audio source), KESSY (passive unlock distance, walk-away, proximity chirp, max keys), TPMS (warning + severe + winter front/rear + loaded front/rear), park assist (first/second warning distance, volume, visual-only), ADAS (ACC min/max speed, default distance, overshoot, lane assist threshold + haptic, blind-zone intensity, AEB default on, emergency assist inactivity), EV charge (mode default, min/max battery temp, DC pre-heat offset, default regen D/B, creep speed, eco torque limit). - -### Actuator tests added (~84 new) -Per-cylinder fuel injector pulses (8), per-cylinder ignition coil pulses (4), per-glow-plug heat tests (4), VVT solenoid tests intake/exhaust per bank (4), valvelift, intake runner, tumble flap, vacuum pump full stroke, wastegate full stroke, VTG full stroke, low-pressure EGR, exhaust throttle, SCR dosing test, AdBlue pump priming + tank heater, post-injection DPF heating, DSG per-PCS solenoid tests (5), DSG park-lock + pump, Haldex pump, ABS per-wheel inlet+outlet solenoids (8 — full pump bleed 60s), EPB motor per side, matrix LED sweep, headlight swivel + aim per side, rear dynamic blinker, horn chirp, alarm siren, panoramic roof + sunshade open/close full cycles, electric trunk, fuel/charge door release, driver seat motors + lumbar + massage + heater max + vent max, EV charge port lock, battery cooling pump, battery PTC steps 1/2/3, DC-DC load, OBC handshake, EV compressor, inverter capacitor discharge HV-safety test, pyrofuse continuity. - -### Live PIDs added (~67 new) -J1979 standard (fuel level 0x2F, distance with MIL 0x21, distance since clear 0x31, time with MIL 0x4D, time since clear 0x4E, oil temp 0x5C, fuel rate 0x5E, exhaust pressure 0x73, DPF temp 0x7C, NOx 0x83, fuel rate extended 0x9D), engine streams (torque actual, pedal stream, wastegate, DPF regen flag, DPF inlet/outlet temp, SCR inlet/post NOx, AdBlue dosing, fuel consumption total + L/h, per-cyl misfire 1-4, per-cyl knock retard 1-4, VVT intake/exhaust actual B1, turbo speed, oil temp), DSG (ratio, torque request, K1+K2 wear), Haldex (pressure, torque to rear), ABS (pump current, roll rate), EPS (motor current, driver torque), ADAS (ACC set speed, object 1 distance + velocity), TPMS (4 wheel pressures + temps), EV (pack power kW, cell V delta, pack min/max temp, isolation, front+rear motor speed/torque, BSG torque, 48V V+SOC). - -### DTC extended-data records added (~164 new) -VVT (P0010-P0022 across both banks), lambda heater circuits (P0030-P0056 four sensors), ambient temp, fuel system (P0089/P0093/P0094 leaks + pressure), MAF (P0101-P0103), MAP (P0106-P0108), IAT, coolant temp (P0116-P0118), throttle (P0122/P0123), thermostat (P0128), O2 sensors across all positions (P0130-P0157), throttle B (P0220-P0223), random misfire P0300 + freeze-frame, knock sensors (P0327/P0328), CKP/CMP (P0335/P0340/P0345), EGR (P0401-P0404), secondary air (P0410/P0411), catalyst (P0421 + P0420 freeze-frame), EVAP (P0440-P0456), cooling fans (P0480/P0481), vehicle speed (P0500/P0501), idle (P0506/P0507), processor (P0601/P0602/P0606), fuel pump (P0628/P0629), transmission (P0705/P0710/P0715/P0717/P0720, gear ratios P0729-P0735, P0750/P0760/P0775), DSG mechatronic (P176B/P176C/P189C), DPF (P2003/P2031/P2080/P2081 EGT, P22F1 diff pressure, P2459 frequency, P247F ash), post-cat lambda (P2096/P2097), pedal sensors (P2122/P2123/P2138), lambda biased (P2196/P2197), HV stack (P0AA1/P0AA4/P0AC4/P0AFA/P0AFE/P0B1A/P0B1B/P0CDA/P0D2B/P0D38), AdBlue (P0AA6 freeze-frame + aging counter), ABS wheel sensors (C0040-C0051), ABS pump/valves (C0060/C0070), SAS (C1000/C1001), EPB motors (C1500/C1501), air suspension (C1A20), airbag squibs (B1009/B100A/B1015/B1016/B1090/B10E0), camera obstructed (B100D), CAN bus (U0001/U0002/U0010/U0100-U0428), heat pump (B1A20), trailer (B1A22), side radar 4 corners (B2299-B229C). - -### Honest coverage status -Estimated **~22% of full ODIS depth** for VW group. To reach the 50-60% theoretical ceiling of public-source + VCDS-dataset import requires another similar-sized push, particularly in DTC extended-data (current ~3% of ODIS catalog size) and per-ECU adaptation channels. Future v3.33+ releases continue both the VW depth push and the same JSON-only migration applied to the other 45 OEMs. - -## [3.31.0] - 2026-05-08 — VW Group at FULL depth + JSON-only architecture - -This release is the new template every OEM will follow: 100% of the -diagnostic catalog lives in JSON, **zero hardcoded data in Pascal**, -and the catalog itself is at production-tool depth. Updates ship as -JSON edits — no recompile — and the format is portable to any -language with a JSON parser. - -### Architecture changes -- **All hardcoded `BuildCatalog` data removed** from `OBD.OEM.VW.pas`. The Pascal class is now pure logic (session negotiator, seed-key starter algorithm, `DecodeDID` formatting overrides, file-name reference). Every ECU / DID / Routine / coding block / adaptation / actuator test / live PID / DTC extended-data entry lives in `catalogs/vw.json`. -- **`vw-extended.json` consolidated into `vw.json`** — single source of truth per OEM. -- **VIN routing moved to JSON** — `OBD.OEM.VW.ApplicableToVIN` now calls the new `VINMatchesCatalog('vw.json', VIN)` helper which reads `applicable_wmis` from the catalog. Adding or removing a brand WMI is a JSON edit. -- New helper `VINMatchesCatalog(FileName, VIN)` in `OBD.OEM.Catalog.Loader`. - -### VW catalog at full depth (catalogs/vw.json — 235 KB, 1,146 entries) -- **32 ECUs**: engine, transmission, Haldex, Quattro transfer case, ABS, steering angle, cluster, airbag, EPS, climate, gateway, BCM, KESSY, front lighting (Matrix LED), rear lighting, IVI (MIB3/4), sound actuator, surround view, ADAS (Travel Assist), 4× side radar (Side Assist), tow hitch, Webasto parking heater, TPMS, park assist, ev_dcdc, ev_obc, evcc, ev_motor, ev_battery. -- **663 DIDs** across all ECUs: - - Engine (200+): per-bank lambda + lambda heater current/temp, short/long-term fuel trim per bank + idle + partial-load, ignition advance, per-cylinder knock retard (8 cyl) + knock sensor voltage (4), per-cylinder misfire counters, MAP / MAF (kg/h, voltage, per stroke) / pedal / throttle / runner / tumble flap, turbo target + wastegate + VTG + turbo speed, EGR target + cooler temp + position, full DPF chain (regen-active, inlet/outlet temps, diff pressure, distance loaded, soot calc/measured, ash, regen count + duration + distance avg/since), AdBlue chain (level, temp, dosing quantity, SCR efficiency, NOx pre/post), per-cylinder injector pulse width / resistance / diesel quantity (8 cyl), VVT actual/target intake/exhaust per bank, valvelift state per bank, cylinder deactivation + balance per cylinder, oil temp/level/quality, fuel total/recent, charge air pre/post intercooler temp, exhaust pre/post turbo + cat temps per bank, HPFP target/actual/duty/delivery, LPFP pressure/duty, fuel temp/tank pressure, EVAP purge valve duty + flow rate, glow plug duration + per-plug current (4), alternator V/I/load %, battery SOC + current, AC compressor + cooling fan + coolant pump duty, thermostat position, coolant pressure, brake booster vacuum + electric vacuum pump duty, secondary air pump + valve, exhaust flap + duty, crankcase pressure, torque request to/from TCM, intervention bitmask, idle target/deviation/IAC, start-stop state + inhibit reasons (9-bit bitmask), cold-start enrichment. - - Transmission (60+): DSG state + current/target gear, input/output speed + slip, ratio, torque request/actual, K1+K2 clutch pressure (target + actual) + torque + wear + microslip count + prefill duration, ATF/oil temp/pressure/quality, mechatronic temp, hydraulic pump duty, 5× pressure-control solenoid current, park-lock state + motor current, drive mode (Eco/Comfort/Sport/Individual/Race-Track), launch control armed, manual mode, paddle press counts, kickdown count, reverse engage count, lifetime shift count + upshift + downshift, shift quality score, shift duration last up/down. Haldex pressure/temp/motor current/torque-to-rear/oil quality. Quattro torque distribution + sport diff active + lock %. - - ABS / ESP (50+): per-wheel speed + slip + brake pressure + brake temp (model), master cylinder pressure, brake pedal pressed/position, yaw rate target/actual, lateral g target/actual, longitudinal g, roll rate, pitch rate, ABS pump motor current + lifetime cycles, intervention counters (ABS, TCS, ESP, AEB, brake assist, hill hold, roll stability), brake-pad remaining (4 wheels) + warning bitmask, ESP mode current (8 modes), trailer detected/brake assist, programmed tire circumference. - - Airbag / SRS (22): crash event count + last timestamp, per-bag state (driver, passenger, side L/R, curtain L/R, knee, center) with deployed / open-circuit / short-circuit / fault states, per-seat pretensioner state (4 seats), per-seat belt buckle (4), PODS occupant detection, PASD active, crash-output fuel cut + central unlock. - - Cluster (28): odometer km + miles, trip A/B/long-term distance + avg speed + consumption + recent + lifetime avg, max speed lifetime, engine on-time total + trip + idle, service distance + days, oil distance + days, inspection distance, service indicator state, MIL state, active driver profile, brightness + ambient lux, outside temp, low-fuel warning, remaining range. - - Climate (24): cabin temp, target temp 4-zone (left + right + rear-L + rear-R), blower speed, AC compressor active + displacement, refrigerant pressure high/low + temp, evaporator temp, recirc, defroster, rear defogger, air quality VOC, vent face/floor/windshield positions, mix flap left/right, PTC heater state + current, cabin filter remaining. - - BCM (50+): door open bitmask (8 bits — 4 doors + hood + trunk + fuel door + sunroof), central lock state, lifetime lock + unlock counts, per-window cycles (4) + motor current (4), trunk + hood + sunroof cycles, light hours per circuit (low-beam + high-beam + DRL + position + fog front + fog rear + brake + reverse + turn L/R + license + interior + trunk), high-beam + brake + reverse + hazard + turn cycles, bulb failure 24-bit bitmask, wiper cycles (front low/high/intermittent + rear), washer pump cycles (front + rear + headlight) + fluid low warning, seat heater hours per zone (4) + ventilation hours (2) + heated steering hours, rear defog hours, mirror fold count + heater hours, ambient lighting color (32-bit RGBA) + brightness. - - KESSY (14): learned-key slot count + max + last presented, remote wake count, passive-entry touch count, antenna strength per corner (4) + interior, proximity zone enum, phone-as-key paired, walk-away lock, immobilizer state. - - Lighting (9): Matrix LED 24-segment state, high-beam assist enum, headlight aim L/R, dynamic curve light L/R, country pattern (Europe LHD/RHD/NA/Japan/Australia), rear dynamic blinker state, brake-light segment count. - - IVI (10): MIB SW + map version + region, CarPlay + Android Auto state (wired/wireless), Bluetooth paired count, Wi-Fi state, LTE signal dBm, volume, active audio source enum. - - ADAS / Travel Assist (18): camera + radar status + calibration state, lane recognition + curvature, ACC set speed + distance + state, lane-keep state, TSR last detected limit, object count + 2 nearest with distance + velocity, AEB armed, Travel Assist intervention count. - - Side radar / Side Assist (7): per-corner radar state (4) + blind-zone L/R + rear cross-traffic. - - TPMS (19): sensor ID per wheel (4) + pressure + temp + battery state per wheel, target pressure front + rear, warning state. - - Park Assist (11): state, active sonar count, per-sonar distance (8 sensors), auto-park progress. - - Tow hitch (5): detected + lighting + brake-signal verification + motor position + tongue weight. - - Webasto (4): state + burner temp + runtime remaining + lifetime starts. - - EV stack (90+): pack voltage + current + power, SOC + window min/max, SOH, capacity nominal + remaining, chemistry enum, cell voltage min/max/delta/avg + cell IDs, pack temp min/max/avg/delta + inlet/outlet, **12 module temperatures**, isolation resistance, contactor states (positive + negative + pre-charge), pre-charge resistor temp, charge cycle count, total energy charged + discharged, overvoltage + undervoltage + overtemp event counters, derate active + reason enum (8 reasons), front + rear motor stator + rotor + inverter temps, front motor RPM + torque + 3-phase currents + DC-link voltage, drive ready state + drive mode + regen level + regen torque, charge status (8 states), session kWh + power + voltage + current, target SOC + time remaining, port lock + temp + type accepted (8 connectors), AC max current + DC max kW, lifetime AC + DC charge counts, OBC state + input/output V/I + efficiency + temp, DC-DC state + V/I/temp, battery cooling pump + heater states/duty/current, range + consumption recent + lifetime. -- **98 routines** across all ECUs (calibrations, basic settings, resets, actuator tests, EV-specific routines). -- **16 coding blocks with 114 fields** — BCM 16-byte long-coding (19 fields), cluster, engine, climate (4-zone + heat-pump flags), ABS, KESSY, EPS (variable-ratio + assist mode default), airbag (knee + center + PODS flags), lighting (Matrix LED + zones), TPMS (sensor type + threshold + spare-tire), ADAS (8 fitted-flags), EV charge (limits + V2G + port type), Haldex (gen + default split), IVI (CarPlay + sound system + connected services + speed-cam warnings), park assist, gateway (vehicle equipment list). -- **81 adaptations** — engine idle / throttle stops / IAC / cold-start / DPF thresholds / oil-quality warning, service interval distance + days + oil quality + indicator mode + inspection + brake fluid, trip auto-reset distances + speed warning, BCM (DRL mode + auto-lock + comfort closing + selective unlock + ambient color/brightness + lane-change blink count), cluster (speed warning + needle sweep + units imperial + consumption / temperature / speed / pressure units + language with 16 languages), Haldex/Quattro torque splits, TPMS (threshold + target front/rear), park assist warning distance + volume, EV charge target + AC current limit + DC power limit + preheat + drive mode + regen + one-pedal, headlight aim offsets + high-beam assist min speed, ACC default following distance + max speed + lane-assist threshold, climate max blower + default temp + auto-recirc speed + PTC priority, DSG oil change interval + launch torque limit + creep speed + kickdown + eco/sport shift points, ABS brake-disc dry-wipe speed + ESP off threshold + trailer max weight + tire circumference, KESSY walk-away distance + proximity unlock, wiper intermittent intervals + rain sensor sensitivity + rear wiper in reverse, Webasto default runtime + target cabin temp. -- **76 actuator tests** — cooling fan low/high, fuel pump prime, EGR valve step 0-100%, glow plugs, secondary air pump, EVAP purge, exhaust flap open/close, wastegate, intake runner, tumble flap, vacuum pump, thermostat, coolant pump, lambda heater B1S1, ABS bleed + per-wheel solenoid (4), EPB actuation, per-window UP+DOWN motors (8 — 4 windows × 2 directions), central lock cycle/lock/unlock, trunk/fuel door release, sunroof open/close, horn, headlight low/high beam L/R + DRL L/R + matrix LED sweep, tail/brake/reverse/fog (front+rear)/license/interior/trunk lamp tests, wipers low/high + rear, washer pumps (front+rear+headlight), HVAC blower step + compressor + recirc/face/floor/defroster flaps + rear defogger + PTC step1, EV charge port lock + battery cooling pump + battery heater + DC-DC load + OBC handshake, park-assist sonar sweep, tow-hitch deploy/stow. -- **56 live PIDs** — 11 J1979 service01 PIDs (load, coolant, MAP, RPM, speed, ignition timing, intake temp, MAF, throttle, control module voltage, ambient temp) + 45 mode 0x22 streams across engine, transmission, ABS, steering, EPS, cluster, climate, EV. -- **56 DTC extended-data records** — P0301-P0308 misfire occurrence + miles + aging + freeze-frame, P0420 + P0430 catalyst occurrence + aging, P0299 + P0234 turbo occurrence + freeze-frame, P0171/172/174/175 fuel-trim occurrence, P2002 + P244A/B + P20EE + P204F DPF/SCR records, P0011/0014 VVT occurrence, P052E crankcase, P0700 + P0741 + P17BF/P17C0 transmission, P0AA6 HV isolation occurrence + miles + OEM status, P0A2A/P0A7A/P0AC0 EV motor + battery, P0D29/P0D2A charge coupler, C1135 brake-pedal switch, U0073 CAN bus-off + freeze-frame, U0146 gateway, U0184 IVI, B116F smart-key, B1318 BCM low voltage. - -### Why this matters -- A community contributor or in-house engineer can now extend VW coverage by editing `vw.json`. No Delphi setup, no rebuild. -- Porting the framework to another language only needs a JSON parser plus the same record types — no Pascal-specific data extraction. -- The same template applies to every other OEM: subsequent v3.32+ releases migrate BMW, Mercedes, Porsche, etc. to the same JSON-only pattern. - -### Out of scope for v3.31 (queued) -- Migration of the other 45 OEM extensions to the JSON-only pattern (v3.32+). -- Per-OEM full-depth content expansion for non-VW brands (v3.33+). - -## [3.30.0] - 2026-05-08 — VW Group reference deep-dive (Phase B start) - -This is the first OEM brought to the per-ECU enrichment + -extended-catalog depth that subsequent OEMs will follow as a -template. VW was chosen because the public diagnostic surface -(VCDS / Ross-Tech wiki / OBDeleven) is the best-documented in -the industry — it's where the patterns get validated. - -### Added (`catalogs/vw-extended.json` — new file, ~700 lines) -- **+85 DIDs** beyond `vw.json`'s 41, bringing VW to **126 DIDs total** across **19 ECUs**: - - Engine (0x7E0): per-bank lambda (b1s1 / b1s2 / b2s1 / b2s2), short + long fuel trim per bank, ignition advance, per-cylinder knock retard + misfire counters, MAP / MAF / pedal / target throttle, turbo target boost + wastegate position, EGR target + cooler temp, full DPF chain (regen-active flag, inlet temp, outlet temp, diff pressure, distance loaded), AdBlue dosing + SCR inlet temp + outlet NOx + remaining range - - Transmission (0x7E1): DSG K1 + K2 clutch pressures, target gear, input + output shaft speeds, torque request to engine, oil quality model %, lifetime shift count - - ABS / ESP (0x710): four wheel speeds, lateral g, master brake-cylinder pressure, brake-pad remaining (front + rear), TCS + ESP intervention counters - - EPS (0x718): torque request + motor current - - Comfort / BCM (0x746): door-open bitmask, lifetime lock count, per-window cycle counts (FL / FR), low-beam + high-beam hours-on - - KESSY (0x748): learned-key slot count + last-presented key ID - - Cluster (0x714): Trip A + B + long-term distance, average + recent consumption, distance + days to next service, distance to next oil change - - Climate (0x740): cabin temperature, left + right zone targets, blower speed, A/C compressor active - - EV stack (0x7E5 / 0x7E6 / 0x7E7): pack voltage / SOC / SOH, motor temp, charge status enum, charge power (up to 200 kW DC on ID.x), remaining range -- **+12 routines**: throttle body alignment, idle relearn, camshaft adaptation, DPF ash reset, 12V battery registration, DSG basic setting, brake-pad reset (front + rear), TPMS relearn, EPS calibration, park-assist calibration, Travel Assist camera + radar calibration -- **+11 ECUs** registered: Haldex, airbag, EPS, BCM (J393), KESSY, IVI (MIB3 / MIB4), front camera + radar (Travel Assist), TPMS (J502), Park Assist (J791), and the EV evcc / motor / battery trio - -### Added (Schema v2 sections in `vw-extended.json`) -- **6 coding blocks** with **27 fields total**: `vag_bcm_long_coding` (16-byte payload — comfort unlock, auto-lock speed, comfort window close, DRL + DRL-via-high-beam, rear fog, headlight country pattern, coming-home delay), `vag_cluster_long_coding` (needle sweep, language, imperial units, shift indicator, speed warning), `vag_engine_long_coding` (adaptive cruise, start-stop, dual-mass-flywheel, exhaust flap), `vag_climate_long_coding`, `vag_abs_coding` (ESP sport mode, off threshold, brake-disc dry-wipe, trailer mode), `vag_kessy_coding` -- **15 adaptation channels**: idle RPM target, throttle stop, service-interval distance + days, oil quality remaining, trip-A reset threshold, DRL operating mode, auto-lock speed, comfort window close, audible speed warning, Haldex default torque split, TPMS warning threshold, park-assist warning distance, EV charge target SOC, EV AC charge current limit -- **12 actuator tests**: cooling fan low + high speed, fuel pump prime, EGR valve step 0-100%, glow-plug heating, ABS pump bleed, window motor (FL up + down), central lock cycle, horn beep, HVAC blower step 0-7, tail lamp test -- **15 live PIDs**: 14 mode 0x22 streams (MAP, fuel rail pressure, b1s1 lambda, ignition advance, engine load, MAF, DPF diff pressure, FL wheel speed, master brake pressure, EPS torque, cabin temp, EV pack voltage + SOC, EV charge power) + 1 J1979 service01 PID 0x0C (RPM) -- **11 DTC extended-data records** across P0301-P0304 misfires (occurrence counters), P0420 catalyst (occurrence + aging), P0299 turbo underboost (occurrence + freeze-frame template), P0AA6 HV battery isolation (occurrence + OEM status byte), plus a P0301 `miles_since_cleared` record - -### Added (Pascal) -- `OBD.OEM.VW` overrides the new `BuildExtendedCatalog` hook from v3.29 and calls `MergeExtendedCatalogJSON('vw-extended.json', ...)`. The flat-section additions (DIDs, routines, ECUs) merge through a second `MergeCatalogJSON('vw-extended.json', ...)` call in `BuildCatalog`. -- `Tests.OEM.VW.Deep` — 17 cases asserting per-ECU DID coverage (lambda per bank, misfire counters, DSG clutch pressures, four wheel speeds, cluster trip + service counters, EV stack), new ECU registration, schema v2 sections (coding blocks with the BCM DRL field, adaptation bounds, actuator-test safety warning, live PID modes, DTC extended-data kinds), and that the extension implements `IOBDOEMExtensionV2`. - -### Why this matters -- VW is now at production-tool depth for live data, configuration, calibration, and DTC analysis. A coding tool can read `vag_bcm_long_coding`, render it as a form, capture user edits, and write the modified payload back. A diagnostic tool can drive the cooling fan, prime the fuel pump, or step the EGR valve through `actuator_tests[]`. A live-data dashboard can stream all the per-cylinder telemetry. Everything else in the framework is built on the same schema, so the next release can apply the template to BMW / Mercedes / Porsche / etc. without redesign. - -### Out of scope for v3.30 (queued for v3.31+) -- Subsequent OEM deep-dives (BMW, Mercedes, Porsche, JLR, MINI, Bentley, Rolls-Royce, Volvo, Polestar, Renault, Stellantis, Ferrari, McLaren, Aston, Dacia, Lada, then Asian + American + Chinese + HD). - -## [3.29.0] - 2026-05-08 — Schema v2: extended catalog (Phase A) - -### Added (additive schema — every existing v3.28 catalog continues to parse) -- **Coding blocks** (`coding_blocks[]`) — writeable DIDs with bit-field structure. Each block carries a `payload_size` and a `fields[]` list of `bit` / `uint8` / `uint16_be` / `uint32_be` / `int16_be` / `int32_be` / `ascii` / `enum` / `bitmask` fields with `byte_offset`, `bit_offset`, `bit_width`, `default`, `min`, `max`, and per-enum `values` maps. UI tools render this as a coding form (checkboxes for bits, combos for enums, spinners for numerics). -- **Adaptations** (`adaptations[]`) — numbered adaptation channels (VAG-style). Read with SID 0x22, write with SID 0x2E. Each entry carries `min` / `max` / `default` / `unit` plus optional `enum` `values` map for clamped + factory-reset support. -- **Actuator tests** (`actuator_tests[]`) — forced-output catalog (cycle the cooling fan, fire injector N, EVAP solenoid, ABS pump bleed, etc.). Each entry carries `id` (RoutineControl RID), `duration_ms`, `safety_warning` (surfaced in the UI before firing), and `response_kind` / `response_label`. -- **Live PIDs** (`live_pids[]`) — streamable signals with framing layout. `mode` is `service01` (J1979) or `service22` (16-bit OEM PIDs). Each entry carries `frame_offset` (byte offset into response payload) plus `decoder` (kind / scale / offset / unit). -- **DTC extended-data records** (`dtc_extended_data[]`) — per-DTC record templates for UDS 0x19 0x06. Kinds: `occurrence_counter`, `aging_counter`, `miles_since_cleared`, `freeze_frame_template`, `oem_status_byte`, `environmental_data`. - -### Added (Pascal API) -- New record types in `OBD.OEM.pas`: `TOBDCodingField` / `TOBDOEMCodingBlock` / `TOBDOEMAdaptation` / `TOBDOEMActuatorTest` / `TOBDOEMLivePID` / `TOBDDtcExtendedDataRecord`, plus shared `TOBDOEMDecoderKind` / `TOBDCodingFieldKind` / `TOBDAdaptationKind` / `TOBDActuatorResponseKind` / `TOBDLivePIDMode` / `TOBDDtcExtendedDataKind` enums. -- New companion interface `IOBDOEMExtensionV2` (separate GUID — keeps `IOBDOEMExtension` binary-compatible). Adds `CodingBlocks`, `Adaptations`, `ActuatorTests`, `LivePIDs`, `DtcExtendedDataRecords` accessors. Implemented by `TOBDOEMExtensionBase` so every existing extension automatically supports it. -- New override-point `TOBDOEMExtensionBase.BuildExtendedCatalog`. Default is a no-op so the 46 v3.28 OEM extensions continue to compile + work unchanged. -- New loader helper `MergeExtendedCatalogJSON` in `OBD.OEM.Catalog.Loader`. Same merge semantics as `MergeCatalogJSON`: by-key replacement (DID, channel, identifier+ecu, mode+pid+ecu, code+record). -- `OBD.OEM.Catalog.JSON` extended to parse the five new sections, with `ParseOEMDecoderKind` / `ParseCodingFieldKind` / `ParseAdaptationKind` / `ParseActuatorResponseKind` / `ParseLivePIDMode` / `ParseDtcExtendedKind` helpers. -- Test fixture `catalogs/test-schema-v2.json` exercising every new section. -- `Tests.OEM.SchemaV2` — 22 cases across parser, kind-string mapping, merge semantics, and a regression assertion that every v1 catalog still parses under the v2 loader. - -### Changed -- `docs/CATALOG_FORMAT.md` adds the **Schema v2** section with examples for each new array and a Pascal opt-in snippet. - -### Why this matters -- Schema v2 is the prerequisite for the rest of the per-OEM diagnostic-depth roadmap (Phase B per-ECU enrichment, Phase C coding tables, Phase D actuator + adaptation catalogs, Phase E live PID expansion, Phase F DTC depth). Shipping the schema first means subsequent phases drop content into pre-validated structures rather than redesigning the data model mid-flight. Backwards compatibility is total — no v3.28 catalog or extension needs editing. - -## [3.28.0] - 2026-05-08 — Unified coding / WriteDataByIdentifier API - -### Added -- **`OBD.OEM.Coding.Common`** — canonical `TOBDCodingFunctionKind` enum (19 coding kinds: vehicle order / FA / commission, As-Built code, FCA wiTech proxi, market region, Rolls-Royce Starlight, daytime running lights, auto-lock/unlock, rear fog lamp, needle sweep, ACC enable, lane-assist enable, TPMS threshold, headlight country, trailer mode, language, units imperial, TPMS calibration, comfort window, soft-top auto). The parallel of v3.25 `ServiceFunction` but for `WriteDataByIdentifier` (SID 0x2E) flows. -- `FindCodingFunction(Ext, Kind, out Func)` — first-match lookup of a writeable DID across any OEM extension's catalog. -- `ListCodingFunctions(Ext)` — enumerate every classifiable coding-write DID, ready for a "Coding" menu in a tool. -- `BuildWriteDataByIdentifier(DID, Data)` — wraps a payload with `2E DID-hi DID-lo …`. -- `BuildCodingFrame(Func, Data)` — same, against a resolved coding function. -- `ParseCodingResponse(Response, DID)` — checks the positive response (`6E DID-hi DID-lo`) and confirms the DID matches. -- `CodingFunctionKindName(Kind)` — display labels for UI binding. -- `Tests.OEM.CodingCommon` — 19 cases across registry classification, lookup against shipped Rolls-Royce + Mazda catalogs, frame builder, response parser, display labels. - -### Why this matters -- A coding tool no longer needs hard-coded dispatchers for "BMW writes FA, Bentley writes commission, Mazda writes as-built, FCA writes proxi". `FindCodingFunction(Ext, cfVehicleOrder)` works across every OEM that ships an FA-equivalent block, and `ListCodingFunctions` populates the tool's coding menu without OEM-by-OEM enumeration. - -## [3.27.0] - 2026-05-08 — Existing-OEM catalog deepening - -### Changed (16 catalogs deepened to baseline) -- **`byd.json`** — 6 → 29 DIDs (+ 6 routines). Adds Yangwang quad-motor + DiSus suspension + tri-motor stack + DiPilot lidar + DiLink IVI + brand code (BYD / Denza / Yangwang / FangChengBao) + drivetrain enum (DM-i / DM-p / EV / DM-o) + 8-in-1 thermal-mgmt controller + four-corner DiSus heights + Tank-Turn drive mode. -- **`tesla.json`** — 6 → 29 DIDs (+ 7 routines). Adds tri-motor Plaid (rear-2 inverter at 0x7E3) + Cybertruck four-wheel-steering + air-suspension controller + Octovalve thermal + FSD camera array + drive-mode enum (Chill/Standard/Sport/Plaid/Track) + four air-suspension heights + Supercharger station ID + V3/V4 charge power + 16 V LV battery + 4680 chemistry tag. -- **`honda.json`** — 9 → 26 DIDs (+ 10 routines). Adds Honda Sensing camera/radar ECU + i-MMD operating-mode enum (EV/Series/Engine drive) + IMA hybrid SOC + temp + Honda e / Prologue HV stack (35 / 85 kWh) + chassis code, engine code (L15B7 / K20C1 Type R / J35Y8) + brake-pad remaining + 10 routines including Honda Sensing calibration + i-MMD battery test + TPMS relearn. -- **`mazda.json`** — 8 → 27 DIDs (+ 8 routines). Adds i-Activ AWD coupling + M Hybrid 24V/48V mild-hybrid + CX-60/90 PHEV + MX-30 EV + e-SkyActiv R-EV separate ECUs + chassis code (KE/KF/KK/MJ) + DPF soot load + boost + 8 routines including DPF force regen + battery registration + TPMS relearn. -- **`subaru.json`** — 8 → 28 DIDs (+ 8 routines). Adds Solterra dual-motor + 71.4 kWh HV pack + AC/DC charge controller + Starlink IVI + EyeSight stereo camera + e-Boxer mild-hybrid + chassis code (GP/SK/VB) + WRX engine code + X-MODE active flag + market code + trim level + 8 routines including EyeSight calibration + battery register + TPMS relearn. -- **`mitsubishi.json`** — 5 → 25 DIDs (+ 7 routines). Adds Outlander PHEV Twin-Motor (front + rear inverters) + 13.8 / 20 kWh PHEV pack + CHAdeMO + V2H/V2G charge enum + S-AWC torque split + drive-mode enum + AdBlue level + DPF soot load + Triton/L200 diesel DPF controller + MI-PILOT ADAS + 4N16 / 4B12 engine codes + PHEV operating-mode (EV/Series/Parallel) + 7 routines. -- **`geely.json`** — 6 → 28 DIDs (+ 6 routines). Adds dual-motor stack (front + rear inverters) + Aegis short-blade LFP pack + DHT-Pro hybrid 3-speed + Galaxy OS / Flyme Auto / LYNK OS IVI + Mobileye-derived Pilot Assist + brand code + model code + drive-mode enum (Eco/Comfort/Sport/Snow/Off-road) + four-corner motor data + charge port stack + 6 routines. -- **`nio.json`** — 5 → 30 DIDs (+ 7 routines). Adds Aquila ADAS suite (33 sensors / 4 lidar) + Adam 4×Orin-X compute + Banyan IVI / NOMI + active air-suspension + ET9 X-By-Wire rear-axle steering + swap count + pack capacity + 800 V architecture + Power Up to 500 kW liquid-cooled charge + 4-lidar status enum + 7 routines including X-By-Wire rear-steer calibration + Aquila ADAS calibration. -- **`xpeng.json`** — 5 → 29 DIDs (+ 7 routines). Adds X-Power AWD front motor + silicon-carbide rear inverter + Livox Tele-15 / Hesai lidar + Xmart OS 8155 / 8295 cabin computer + active air-suspension (G9 / X9) + X9 rear-wheel steering + S4 800V supercharger (480 kW) + drivetrain enum (RWD / X-Power AWD) + pack chemistry (NCM / LFP / short-blade) + 7 routines including XPILOT / XNGP calibration. -- **`gwm.json`** — 5 → 27 DIDs (+ 7 routines). Adds Hi4 / Hi4-T hybrid controller + dual-motor inverters + Honeycomb LFP / SVOLT pack + Tank crawl-mode controller + Coffee Pilot ADAS + tank drive-mode enum (Normal/Eco/Sport/Sand/Mud/Snow/Mountain/Crawl/Tank-Turn) + diff-lock state enum (Off/Center/Rear/Front+Rear) + low-range bool + brand code (HAVAL/WEY/ORA/TANK/POER) + 9HAT/9DCT TCU + 7 routines. -- **`cummins.json`** — 6 → 26 DIDs (+ 5 routines). Adds DEF doser module + hydrocarbon doser + combustion-diagnostic ECU + emissions family + displacement + J1939 SPN-mapped coolant / oil temp + oil pressure + boost + rail pressure + intake-air temp + fuel temperature + EGR valve position + DPF/SCR full chain (inlet temp / diff pressure / SCR inlet / NOx / DEF dosing / consumption) + DPF ash reset + EGR calibration + cylinder-balance test routines. -- **`detroit.json`** — 5 → 27 DIDs (+ 5 routines). Adds CPC (Common Powertrain Controller) + DEF doser + DD13/DD15/DD16/DD8 engine model code + DT12 clutch position + oil temp + current gear + full DPF/SCR chain (ash load / inlet temp / diff pressure / SCR inlet / NOx) + DT12 clutch calibration + EGR calibration + DEF quality test routines. -- **`scania.json`** — 5 → 27 DIDs (+ 5 routines). Adds Tachograph (TCO) + Visibility (VIS) + Lane Warning System (LWS) + Cab Climate (CCS) + BCS Body & Chassis + Scania BEV stack (motor + 624 kWh battery) + chassis type (R/S/G/P/L/XT) + Opticruise current gear + oil temp + EBS brake-pad remaining + retarder active % + DPF/SCR full chain + 5 routines including Opticruise calibration + brake-bleed. -- **`man.json`** — 5 → 26 DIDs (+ 5 routines). Adds Instrument Cluster (IC) + Lane Guard System (LGS) + Trailer Coupling Control (TTC) + MAN eTruck stack (motor + 480 kWh battery) + engine model (D08 / D26 / D38) + TipMatic / TraXon current gear + oil temp + DPF/SCR full chain + EBS brake-pad remaining + 5 routines including TipMatic calibration + brake-bleed. -- **`paccar.json`** — 5 → 25 DIDs (+ 6 routines). Adds aftertreatment ATD2 (SCR) + Bendix Wingman Fusion radar + Kenworth severe-duty hydraulic options + brand code (Peterbilt / Kenworth / DAF / Leyland) + chassis code expanded (579/567/T880/W990/XF/XG/XG+/Anthem) + factory code expanded (Denton/Chillicothe/Madison/Eindhoven/Leyland) + engine model (MX-11 / MX-13 / Cummins X15) + DPF/SCR full chain + transmission gear + oil temp + EBS brake-pad + Wingman radar status + 6 routines. -- **`volvotrucks.json`** — 5 → 28 DIDs (+ 6 routines). Adds Tachograph (DTCO 4.0) + VADS Active Driver Support + Lane Keeping Support (LKS) + Volvo FE/FH Electric stack (motor + 180/540 kWh battery) + brand code (Volvo/Mack/Renault Trucks) + engine model (D11/D13/D16/MP7/MP8) + I-Shift / mDRIVE current gear + oil temp + EBS brake-pad + VEB+ engine-brake active % + DPF/SCR full chain + remaining range + 6 routines including VADS calibration. - -### Changed (WMI hygiene, continued from v3.26) -- `paccar.json` no longer lists `SCB` in `applicable_wmis` (Bentley territory; PACCAR Leyland Trucks is `SAR`). - -### Total -- **DIDs added: ~330 across 16 OEMs** (was 110, now ~440). -- **Routines added: ~75 across 16 OEMs** (was 36, now ~111). -- All 16 catalogs now meet the established baseline (~25-30 DIDs / 5-10 routines per OEM). - -## [3.26.0] - 2026-05-08 — Six more OEMs (ultra-luxury British + Russian + Eastern-European) - -### Added (6 new full-depth OEM extensions) -- **`OBD.OEM.AstonMartin`** — Aston Martin Lagonda (1 WMI: SCF Gaydon + St Athan). 16-ECU map covering DB12 / Vantage / DBX / DBX 707 / Vanquish + Valhalla PHEV (charge controller + front-axle e-motor + 6.6 kWh PHEV pack at 0x7E5/0x7E6/0x7E7). 25 DIDs including Q by Aston Martin paint + trim codes, manettino-equivalent damper / drive modes, eDiff lock, four-corner air-suspension on DBX, oil pressure / level / runtime, Valhalla PHEV pack voltage / SOC / motor temp. 5 routines. -- **`OBD.OEM.Bentley`** — Bentley Motors (1 WMI: SCB Crewe). 16-ECU map covering Continental GT / GTC / Flying Spur / Bentayga + V8 PHEV variants (14.1 / 25.9 kWh). 27 DIDs including Bentley Mulliner paint code, commission number, drive mode, air-suspension mode, Dynamic Ride 48 V active-anti-roll status, Flying Spur Mulliner rear-wheel steering angle, four air-suspension heights, PHEV stack. 7 routines including Dynamic Ride calibration + rear-wheel steering calibration. -- **`OBD.OEM.RollsRoyce`** — Rolls-Royce Motor Cars (1 WMI: SCA Goodwood). BMW Group sub-brand inheriting BMW E-Sys / ISTA — 17-ECU map covering Phantom (RR1) / Ghost (RR21) / Cullinan (RR31) + Spectre EV (RR23) at 0x7E5/0x7E6/0x7E7 with 102 kWh Gen5 BMW eDrive pack. 28 DIDs including factory + current I-Stufe, FA SALAPA option codes, RR model code, Bespoke programme commission number, Starlight Headliner constellation pattern, Magic Carpet Ride active flag, four air heights, rear-wheel steering, Spirit OS version, Spectre pack voltage / SOC / SOH / front + rear motor temps / charge status / range. 7 routines including Bespoke Starlight constellation programming. -- **`OBD.OEM.McLaren`** — McLaren Automotive (1 WMI: SBM Woking MPC). 17-ECU map covering 720S / 750S / 765LT / GT + Artura V6 PHEV (front-axle e-motor + 7.4 kWh PHEV pack). 27 DIDs including MSO paint code, MonoCell carbon-tub serial, dual-bank turbo temperatures, PCCM handling + powertrain modes, active rear-wing position enum, Vehicle-Lift status, DCT clutch A/B temperatures, brake pad remaining, Artura PHEV stack. 7 routines including 7-DCT (SSG) calibration + active-aero calibration + lift-axle test. -- **`OBD.OEM.Lada`** — AvtoVAZ / Lada (3 WMIs: XTA Tolyatti + XTC Izhevsk + XTV Bronto). 13-ECU map covering Granta / Vesta / Niva Legend / Niva Travel / Largus with VAZ-21127 / 21179 / 21214 engines + JATCO JF015E CVT / 5AMT / 4AT. 26 DIDs including model code, engine code, transmission code, APS immobilizer state enum, EPS torque + motor current, intake MAF / temperature, throttle / pedal position, manifold pressure, Niva transfer-case mode (2H/4H/4L/N), CVT oil temperature + ratio, AMT clutch position. 6 routines including APS immobilizer key learning + 5AMT clutch calibration. -- **`OBD.OEM.Dacia`** — Automobile Dacia / Renault Group budget brand (4 WMIs: UU1 + UU3 Mioveni + LBR + LRY Dongfeng-Renault Wuhan). 16-ECU map covering Sandero / Logan / Duster / Jogger / Bigster + ECO-G LPG bi-fuel + Spring EV (26.8 kWh) + Bigster Hybrid 140. 27 DIDs including Renault Group part number, model code, engine code (TCe / ECO-G / Hybrid 140 / 5AQ), assembly plant (Mioveni / Wuhan / Tangier), LPG tank level + active flag, Spring EV pack voltage / SOC / SOH / motor temp / charge status / range, Duster 4x4 mode. 6 routines. - -### Added (DTC starters — full depth, 148 entries combined) -- `dtc-aston-martin.json`: 22 codes (8-cylinder misfires P0301-P0308 for V8 / V12, M177 turbo over/underboost, oil pressure, catalysts, Valhalla HV isolation, DBX air-suspension, Bilstein DTX damper, comm-loss). -- `dtc-bentley.json`: 24 codes (V8 + W12 misfires, turbo, oil, catalysts, hybrid HV isolation + battery deterioration + AC charge coupler, air-suspension, Dynamic Ride 48 V, rear-wheel steering, KESSY). -- `dtc-rolls-royce.json`: 25 codes (V12 + V8 misfires, turbo, oil, catalysts, Vanos, Spectre HV stack, AC + DC charge coupler, Magic Carpet air-suspension, rear-wheel steering, Spirit OS comm-loss, Starlight LED). -- `dtc-mclaren.json`: 24 codes (V8 cylinder misfires P0301-P0308, twin-turbo, dry-sump oil pressure, catalysts, Artura HV stack + AC charge coupler, 7-DCT TCC performance, Vehicle-Lift sensor, active rear-wing sensor, PCCM comm-loss). -- `dtc-lada.json`: 26 codes (MAF / coolant / TPS / O2 sensor circuits, lean / rich, 4-cylinder misfires, CKP / CMP, catalyst, EVAP, fuel pump, clutch switch, CKP self-learn, APS immobilizer auth, ABS, comm-loss, BCM low voltage). -- `dtc-dacia.json`: 27 codes (MAF / coolant circuits, lean / rich, TCe turbo, 4-cylinder misfires, catalyst, EVAP, ECO-G LPG injector + pressure, Spring EV HV stack + charge coupler, JF016E CVT, brake-pedal switch, Duster AWD coupling, comm-loss, UCH low voltage). - -### Changed (WMI hygiene — collision fixes) -- `OBD.OEM.PACCAR` no longer claims `SCB` (real-world WMI for PACCAR Leyland Trucks is `SAR`; `SCB` is exclusively Bentley). Regression test guards both directions. -- `OBD.OEM.Renault` no longer claims `UU1` / `UU3` / `UU6` — Dacia is delegated to its own extension. Regression test guards both directions. - -### Total -- **OEMs: 40 → 46** (six new full-depth extensions). -- **DTC entries: 148 new starter codes** across the v3.26 OEMs. - -## [3.25.0] - 2026-05-08 — Unified service-function API - -### Added -- **`OBD.OEM.ServiceFunction`** — canonical `TOBDServiceFunctionKind` enum (19 functions: oil-life reset, EPB service, SAS calibration, battery registration, DPF regen, TPMS relearn, throttle / idle / transmission / crank / immo / fuel-trim relearn, brake bleed, air-suspension calibration, hybrid battery test, Haldex calibration, basic setting, clear adaptations, DEF quality test) plus a name-token registry that maps the per-OEM routine names (`ferrari_oil_life_reset`, `mb_oil_maintenance_reset`, `reset_service_indicator`, ...) to the canonical kind via case-insensitive substring matching. -- `FindServiceFunction(Ext, Kind, out Func)` — first-match lookup against any OEM extension's routine catalog. Tools can now write *one* call to issue, e.g., an oil-life reset and have it work across every OEM that ships the routine. -- `ListServiceFunctions(Ext)` — enumerate every classifiable routine on an OEM extension, ready for a "Service" menu in a diagnostic tool. Skips routines that don't classify (returns no `sfUnknown` entries). -- `BuildServiceFunctionFrame(Func, Input)` — wraps the resolved RID with the StartRoutine SID + sub-function (`31 01 RID ...`). -- `ServiceFunctionKindName(Kind)` — display labels for UI binding ("Oil Life Reset", "EPB Service Mode", "Steering-Angle Sensor Calibration", ...). -- `Tests.OEM.ServiceFunction` — 25 cases across registry classification, lookup against shipped Ferrari / Mahindra / Tata / MINI catalogs, enumeration, frame builder, and display labels. - -### Why this matters -- A diagnostic tool no longer has to hard-code which OEM names its oil-life reset `oil_life_reset` vs `oil_maintenance_reset` vs `reset_service_indicator`. The same code works for every OEM that ships an oil-life routine, and `ListServiceFunctions` lets the tool's UI populate the "service" menu without listing OEMs by hand. - -## [3.24.0] - 2026-05-07 — Six more OEMs (Ferrari / Lucid / Mahindra / Tata / MINI / smart) - -### Added (6 new full-depth OEM extensions) -- **`OBD.OEM.Ferrari`** — Ferrari N.V. (1 WMI: ZFF Maranello). 16-ECU SD3 / Leonardo map covering ME engine + Marelli ECU + 7/8-DCT + secondary V8/V12 controller + SF90 / 296 / 12Cilindri hybrid stack (inverter + e-motor + HV battery) + manettino + Magneride + lift axle + PCCB-equivalent. **24 DIDs** including Ferrari model code (F142, F154, F160), paint code, individual options, Maranello assembly data, warranty block, oil pressure / level / temperature / runtime, rear-axle temp, hybrid pack voltage / SOC / SOH, manettino position enum (Wet / Sport / Race / CT-off / ESC-off / Qualify), Magneride mode enum, lift-axle status enum, four tire-surface temperatures. **6 routines** (DCT calibration, Magneride, lift-axle test, oil-life reset). -- **`OBD.OEM.Lucid`** — Lucid Group (1 WMI: 50A Casa Grande AMP-1). 15-ECU map for the Air sedan + Gravity SUV: VCU + front motor + tri-motor stack (Sapphire) + 900 V BMS + Wunderbox integrated charger + Pixel cluster + DreamDrive ADAS + lidar + Glass Canopy + heat-pump (CO₂) + thermal mgmt + air suspension. **22 DIDs** including model code (Air / Gravity / Sapphire), drivetrain (Pure / Touring / Grand Touring / Sapphire), battery pack (88/92/112/118 kWh), 900 V pack voltage / SOC / SOH / temp min/max, range, consumption, charge status / session kWh / 350 kW power, three motor temperatures, four-corner air-suspension heights, drive mode enum (Smooth / Swift / Sapphire Track / Tow). **5 routines**. -- **`OBD.OEM.Mahindra`** — Mahindra & Mahindra (3 WMIs: MAJ Chakan/Nashik + MA6 Bengaluru + M3M BE EV Pune; deliberately avoids MA1 to prevent JLR-Pune collision). 12-ECU map for engine (mHawk diesel / mStallion petrol) + Aisin AT / Punch CVT + BE EV charge controller + drive motor + AdrenoX IVI + ADAS Level 2 + air suspension (XUV700 AX7L). **23 DIDs** including model code (XUV700, ScorpioN, Thar), variant code (AX5/AX7/AX7L/Z8/Z8L), engine code, oil temperature, coolant temp, boost pressure, common-rail pressure, fuel level, runtime, DPF soot load, BE EV pack voltage / SOC / SOH / motor temp / charge status, AT/CVT temp, two-corner air heights. **6 routines**. -- **`OBD.OEM.Tata`** — Tata Motors (3 WMIs: MAT passenger Pune+Sanand + MAR commercial Jamshedpur+Lucknow + KMU Tata Daewoo Korea; JLR — also Tata-owned — uses its own extension). 12-ECU map for Revotron / Revotorq / Kryotec / TGDI engines + iCNG bi-fuel module + Ziptron / Acti.ev EV stack + iRA Connected Car / Harman IVI + ADAS Level 2 (Harrier / Safari / Curvv). **23 DIDs** including model code (Nexon / Punch / Curvv / Harrier / Safari), variant code (XE/XM/XT/XZ/XZ+), engine code (Revotron 1.2T, Kryotec 2.0L), oil + coolant temperature, boost, common-rail pressure, fuel level, CNG tank pressure, runtime, DPF soot load, Ziptron pack voltage / SOC / SOH / motor temp / charge status / range, brake-pad remaining. **7 routines**. -- **`OBD.OEM.MINI`** — MINI / BMW Group sub-brand (2 WMIs: WMW Oxford UK + SAW Spotlight Automotive China JV). Full BMW E-Sys / ISTA architecture inheritance: 13-ECU map (DME B38/B48/B58 + EGS Aisin/7DCT + DSC + KOMBI + FRM + CAS + ZGW + iDrive + IHKA + ACSM + MINI Cooper E / SE / Aceman EV stack). 23 DIDs including factory + current I-Stufe, FA SALAPA option codes, MINI chassis code (R56, F56, F60, J01, J05), oil temperature / level / runtime, boost pressure, fuel level + consumption, MINI Cooper E pack voltage / SOC / SOH / range / motor temp / charge status, brake-pad remaining, oil quality, remaining oil-service distance. **6 routines**. Inherits the BMW session negotiator (security access required for both extended + programming sessions; 1500 ms heartbeat). -- **`OBD.OEM.Smart`** — smart Automobile Co. / Mercedes-Geely 50/50 JV (2 WMIs: WME Hambach + L7M Xi'an China). 14-ECU map covering both legacy two-seater (451 / 453) and current Geely SEA platform (#1 / #3 / #5 SUV): VCU + front + rear motor inverters + 66/100 kWh BMS + on-board charger + cluster + HUD (#5 Premium) + Pilot Assist (Mobileye) + air suspension (#5). **20 DIDs** including model code, drivetrain (RWD/AWD/Brabus), battery pack (66 kWh BYD-LFP / 100 kWh CATL-NMC), software release, mileage, ambient temp, pack voltage / SOC / SOH / temp min/max, range, consumption, charge status / session kWh, motor temps, brake-pad remaining. **5 routines**. - -### Added (DTC starters — full depth, 144 entries combined) -- `dtc-ferrari.json`: 18 codes (cylinder misfires P0301-P0308, V8 turbo / V12 NA oil pressure + boost, hybrid system on SF90 / 296 / 12Cilindri, lift-axle, Magneride, CAN-FD). -- `dtc-lucid.json`: 31 codes (HV isolation, motor temp x3, charge coupler / lock, Wunderbox over-temp, BMS / IVI / DreamDrive comm-loss, DreamDrive front camera + lidar, glass canopy, Pixel cluster backlight, tri-motor torque vectoring). -- `dtc-mahindra.json`: 25 codes (mStallion turbo, mHawk diesel rail / EGR / DPF, Aisin AT, BE EV battery, AdrenoX comm-loss, AX7L air suspension). -- `dtc-tata.json`: 26 codes (Revotron T-GDi turbo / catalyst, Kryotec diesel rail / DPF, DCA transmission, iCNG fuel-pressure, Ziptron HV system + comm-loss, Harman iRA comm-loss). -- `dtc-mini.json`: 23 codes (cylinder misfires for B38 3-cyl + B48 4-cyl, VANOS solenoid stuck open/closed, Valvetronic eccentric-shaft sensor, B48 oil pump pattern, MINI Cooper E HV system, RDC tire-pressure, FlexRay bus-off). -- `dtc-smart.json`: 21 codes (HV isolation, AC + DC charge coupler, BMS / Pilot Assist comm-loss, heat-pump compressor, #5 air-suspension reservoir). - -### Tests -- `Tests.OEM.LuxuryAndIndian` — 19 new test cases: VIN routing for all 6 OEMs (Ferrari ZFF + Fiat ZFA disambiguation, Lucid Casa Grande, Mahindra all 3 plants, Tata MAT/MAR/KMU including Tata Daewoo, MINI WMW + SAW, smart WME + L7M), Mahindra-vs-JLR-Pune collision guard, catalog spot-checks (Ferrari manettino + lift axle, Lucid Wunderbox + DreamDrive, Mahindra BE EV controller, Tata iCNG + Ziptron, MINI security-access requirement, smart Geely SEA architecture), decoder spot-checks for each OEM's distinguishing DID. - -### Changed -- `Packages/RunTime.dpk` adds the 6 new units. The OEM registry now resolves **40 OEMs** total — 29 passenger + 6 heavy-duty + 5 Chinese. -- `examples/diagtool/DiagTool.dpr` self-registers the 6 new extensions. - -### Notes -- Combined v3.24 contribution: **131 new DID + routine entries** + **144 new DTC entries** across 12 catalog files. Catalogs ship at full depth (24-31 entries each), matching v3.22 / v3.18 / v3.7 baseline depth — not the slim starters of v3.14 / v3.17. -- All 12 new catalog files validated to parse cleanly via external `json.load`. - -## [3.23.0] - 2026-05-07 — OBD-II application helpers (readiness + freeze-frame + vehicle health) - -### Added -- **`OBD.ReadinessMonitor`** — decoder for SAE J1979 PID 0x01 (Monitor Status Since Codes Cleared). Returns a `TOBDReadinessReport` with MIL state, DTC count, and per-monitor readiness state for **17 monitor kinds** covering both spark-ignition (catalyst, heated catalyst, EVAP, secondary air, A/C refrigerant, oxygen sensor, oxygen sensor heater, EGR) and compression-ignition (NMHC catalyst, NOx aftertreatment, boost pressure, exhaust gas sensor, PM filter, EGR/VVT diesel) layouts plus the three universal continuous monitors (misfire, fuel system, components). `FormatReadinessSummary` produces a one-line status-bar string like `"MIL off, 0 DTCs, 5/8 readiness monitors complete (spark-ignition)"`. -- **`OBD.FreezeFrame`** — Service 02 wire helpers. `BuildFreezeFrameRequest(PID, FrameNum)` builds the `02 PID FrameNum` request; `ParseFreezeFrameResponse(bytes, expectedPID)` parses the `42 PID FrameNum DATA…` reply (with negative-NRC / wrong-SID / wrong-PID error paths) into a `TOBDFreezeFrameEntry`. `FormatFreezeFrameTriggerDTC` decodes the 2-byte payload of PID 0x02 (the DTC that triggered the freeze frame) into the canonical 5-character form, reusing the v3.7 ISO 15031-5 encoder. -- **`OBD.VehicleHealth`** — high-level `TOBDHealthCapture.Capture` orchestrator that aggregates everything an app actually wants in one call: - - VIN read (Service 09 PID 02) → auto-resolve OEM extension via `TOBDOEMRegistry.FindByVIN`. - - Active DTCs (Service 03), each annotated with the OEM catalog's description + severity. - - Pending DTCs (Service 07), same annotation pipeline. - - Readiness monitors (PID 0x01) decoded through `OBD.ReadinessMonitor`. - - Live values: battery voltage (PID 0x42), engine RPM (PID 0x0C), vehicle speed (PID 0x0D), coolant temperature (PID 0x05), engine load (PID 0x04). - - **Computed health score 0..100** with a documented penalty rubric (MIL on -10, critical DTC -20, warning DTC -8, info DTC -3, unknown DTC -10, pending DTC -2, each not-ready monitor -1; clamped to 0). - - One-line summary string suitable for a status bar. -- Each step is **best-effort** — a failed read populates the matching `*Error` field but doesn't abort the rest, so tools surface the partial result as "we got X but Y failed". This is exactly the contract a real diagnostic tool's "snapshot" button needs. -- `Tests.OBD.Helpers` — 17 new test cases. ReadinessMonitor (10): all-zeros baseline, MIL+DTC count, continuous monitor ready / not-ready, gasoline non-continuous catalyst, diesel-flag-and-monitors set, too-short rejection, summary-string format, monitor-kind / state name canonicalization. FreezeFrame (7): request encoding, positive-response parsing, too-short / wrong-SID / wrong-PID rejection, negative-NRC handling, trigger-DTC round-trip. - -### Changed -- `Packages/RunTime.dpk` adds the three new units. -- `tests/Tests.dpr` registers `Tests.OBD.Helpers`. - -### Notes -- This is the **application-enabling** milestone. Tools built on the framework can now call: - ```pascal - Capture := TOBDHealthCapture.Create(Async); - Snap := Capture.Capture; - StatusBar.SimpleText := Snap.SummaryLine; - // Snap.HealthScore drives the colour-coded indicator - // Snap.ActiveDTCs feeds the DTC list view - // Snap.Readiness powers the readiness-monitor grid - // Snap.BatteryVoltage / EngineRPM / etc. feed the live gauges - ``` -- The reference VCL tool (`examples/diagtool`) shipped in v3.20 already exposes the lower-level primitives (Service 03 read, PID 0x05 / 0x0C / 0x0D / 0x42 polling, DescribeDTC); a future milestone (v3.24+) will add a "Snapshot" tab that calls the v3.23 `TOBDHealthCapture` directly. -- `TOBDHealthCapture` is intentionally stateless — each `Capture` call re-reads everything. Production tools that want a live dashboard should run a polling loop on a worker thread and use the framework's existing async primitives. - -## [3.22.0] - 2026-05-07 — Premium / EV / heavy-commercial OEMs - -### Added (6 new OEM extensions) -- **`OBD.OEM.Porsche`** — Porsche AG (2 WMIs: WP0 + WP1, Stuttgart Zuffenhausen + Leipzig). Separate from VW Group because PIWIS is its own toolchain. **16-ECU map** covering DME engine + PDK transmission + PASM active suspension + PDCC active anti-roll + Taycan electric front/rear inverters + HV battery + PCM + climate + SRS + PCCB ceramic-brake + ESP + LWL fiber-bus + KESSY. **27 DIDs** including model code, paint code, interior code, M-Nummern options, factory commission, PCM PNO block, oil pressure / level / temperature / runtime, charge-air boost, Taycan pack voltage / SOC / SOH / range / charge status, PDK clutch wear, PASM ride heights, PCCB disc temperatures. **9 routines** (PDK calibration, PASM, PDCC, SAS, TPMS, KESSY relearn, battery register). -- **`OBD.OEM.JLR`** — Jaguar Land Rover (4 WMIs: SAJ Castle Bromwich + SAL Solihull + SAD Halewood + MA1 Pune India). **17-ECU map** including PCM + TCM + RDM rear drive (RR BEV) + EV charge controller + EV motor + IPC + HUD + CJB + RJB + ABS/DSC + ASM active suspension + SRS + TCB telematics + ATC climate + Pivi Pro IVI + ADAS + smart key. **23 DIDs** including model code, assembly plant, calibration ID, Topix release, factory options, vehicle mileage, oil temperature / life, runtime, boost pressure, fuel level, ambient temp, I-Pace HV pack voltage / SOC / SOH / range / charge status, four-corner air-suspension heights, Terrain Response selected mode enum. **9 routines** (oil-life reset, SAS, air-suspension calibration, battery registration, DPF regen, smart-key relearn, brake bleed). -- **`OBD.OEM.Iveco`** — Iveco S.p.A. (2 WMIs: ZCF Italy + VCF Spain). **14-ECU map** with FPT Cursor / NEF / S-FE engine + EuroTronic / HI-TRONIX AMT + power steering + Knorr-Bremse EBS + Intarder retarder + DID + body computer + VCM + BCM + ACM aftertreatment + TPMS + forward radar + eDaily EV charge + drive motor. **20 DIDs** with model code, emissions package, engine serial, chassis serial, options, mileage, engine hours, oil pressure / temperature, fuel rate / lifetime, boost, coolant temp, DEF tank level / quality, DPF soot load / temperatures / distance-since-regen, eDaily HV pack voltage / SOC / motor temp. **7 routines**. -- **`OBD.OEM.Isuzu`** — Isuzu Motors (7 WMIs: JAA / JAB / JAL / JAN / JAH Japan, 5RY / 4GD US Charlotte MI). **11-ECU map** for engine ECM (4HK1 / 6HK1 / 6WG1 / RZ4E / 4JJ1) + Aisin / MZW / Smoother AT + power steering + ABS / ESC + Telma retarder + IDD cluster + body computer + ASC stability + cab body + aftertreatment + TPMS. **20 DIDs** with chassis code, engine code, calibration ID, emissions family, mileage, engine hours, oil pressure / temperature, coolant temp, RPM, boost, common-rail pressure, fuel rate / lifetime, DEF tank level, DPF soot load / inlet+outlet temp / distance-since-regen, transmission oil temp + clutch wear, 24 V battery voltage. **7 routines**. -- **`OBD.OEM.Rivian`** — Rivian Automotive (1 WMI: 7PD Normal IL). **16-ECU map** including VCU + four motor inverters (FL / FR / RL / RR for the quad-motor R1) + driver display + Driver+ ADAS + camera fusion + BCM + rear body / Gear Tunnel / Tailgate + heat pump + central gateway + BMS + charge port + thermal management + air-suspension. **22 DIDs** including model code, drivetrain (Quad / Dual / Performance Dual / Tri-motor), battery pack ID, software release, mileage, 12 V battery voltage, HV pack voltage / SOC / SOH / temp min/max, remaining range, recent consumption, charge status, charge-session kWh, four motor temperatures, four-corner air-suspension heights, drive-mode enum (All-Purpose / Conserve / Sport / Off-Road Auto / Off-Road Rally / Off-Road Drift / Off-Road Rock Crawl / Tow). **5 routines**. -- **`OBD.OEM.Polestar`** — Polestar Performance AB / Geely (2 WMIs: LPS Polestar 2 + LFP Polestar 4 — does NOT collide with Volvo Cars). **15-ECU map** with CEM + DIM + HUD + SRS + ABS + PDM + climate / heat pump + Sensus Android Automotive IHU + TCAM telematics + BMS + on-board charger + front + rear motor inverters + Pilot Assist + Luminar lidar (P3 / P4). **23 DIDs** including model code, drivetrain, motor package, software release, options, mileage, ambient temp, range, consumption average, HV pack voltage / SOC / SOH / temp min/max, charge status / session kWh, motor temps, front + rear axle torque request, brake-pad remaining. **6 routines**. - -### Added (DTC starters, 100 entries combined) -- `dtc-porsche.json`: 22 codes (cylinder misfires P0301-P0306, hybrid system on Taycan, ceramic brakes, KESSY, CAN-FD bus). -- `dtc-jlr.json`: 18 codes (Ingenium turbo, AJ-V8 catalysts, ZF8HP TCC, I-Pace HV system, air suspension compressor + leak + reservoir, Pivi Pro touchscreen, telematics). -- `dtc-iveco.json`: 15 codes (Cursor fuel rail, EGR, J1939 SPN-FMI for DPF / SCR / DEF inducement, Daily 3.0 DEF heater). -- `dtc-isuzu.json`: 17 codes (4HK1/6HK1 VGT, RZ4E fuel rail, MZW transmission, J1939 SPN-FMI for DPF / SCR). -- `dtc-rivian.json`: 14 codes (HV isolation, drive-motor temp, DC/DC, VCU CRC, BMS comm, IVI, air suspension, quad-motor torque vectoring). -- `dtc-polestar.json`: 14 codes (HV isolation, motor temp, DC/DC, battery cooling, BMS / IHU / TCAM comm-loss). - -### Tests -- `Tests.OEM.Premium` — 19 new test cases: VIN routing for all 6 OEMs (Porsche WP0/WP1, JLR all 4 plants, Iveco IT/ES, Isuzu JP/US, Rivian Normal IL, Polestar non-Volvo-Cars), Polestar-vs-Volvo-Cars collision guard, catalog spot-checks (Porsche PDK + PASM, JLR air-suspension routine, Iveco FPT engine, Isuzu aftertreatment ECU, Rivian quad-motor count, Polestar EVCC + Pilot Assist), decoder spot-checks for each OEM's distinguishing DID. - -### Changed -- `Packages/RunTime.dpk` adds the 6 new units. The OEM registry now resolves **34 OEMs** total — 23 passenger + 6 heavy-duty + 5 Chinese. -- `examples/diagtool/DiagTool.dpr` self-registers the 6 new extensions in its uses clause so VIN-based routing in the reference VCL tool covers them out of the box. - -### Notes -- Catalogs ship the depth tool-builders actually need: ~30 DIDs and ~6-9 routines per OEM, similar to the established VW (52) / BMW (43) / Ford (35) / Toyota (32) catalogs from prior milestones. Combined v3.22 contribution: **185 new DID + routine entries + 100 new DTC entries** across 12 JSON catalogs. -- Per-OEM entries remain `verified: false` per the v3.3 provenance contract — sourced from the published community references the v3.18 vocabulary documents (piwis-community, sdd-community / topix-public, iveco-easy-community, idss-community, rivian-community, polestar-community). -- All 12 new catalog files validated to parse cleanly via external `json.load`. - -## [3.21.0] - 2026-05-07 — Catalog deepening (round 2) - -### Added (universal catalogs) -- **`catalogs/dtc-iso-15031.json` — +54 verified P/U codes** drawn from SAE J2012 (the master DTC nomenclature). New entries cover: cam-shaft / crank correlation (P0009-P0024 range), turbocharger boost solenoids (P0033-P0245), fuel volume / pressure regulator (P0001/P0002/P0090/P0182), oxygen sensor variants (P0096-P0099 IAT2 sensor), cylinder contribution / balance (P0263 / P0271), single-cylinder misfire (P0314), knock sensor 1+2 (P0325/P0327/P0331), camshaft phasing intermittent (P0344), EGR sensor 'A' low (P0405), warm-up catalyst bank 2 (P0432), EVAP loose-fuel-cap (P0457), fuel level sensor (P0461/P0463), EVAP vent-valve circuit (P0498), oil pressure switch (P0521), system voltage malfunction (P0560), control-module options error (P0610), steering control circuit (P0635), sensor reference voltage 'B' (P0651), ECM/PCM power relay sense (P0688), brake switch 'B' (P0703), transmission range PRNDL (P0705), turbine speed (P0716), gear-1..4 incorrect ratio (P0731-P0734), shift solenoid A/B (P0750/P0755), engine-start request (P082E), park/neutral switch (P0850), drive-cycle monitor not complete (P1000), CAN-A performance (U0028), gateway 'A' lost-comm (U0146), immobilizer lost-comm (U0167). Total **149 verified universal DTC entries** (up from 95). -- **`catalogs/obd2-pids.json` — +5 verified PIDs** in the 0xA7-0xC8 range: NOx sensor corrected (0xA7), NOx alternative encoding (0xAB), supported PIDs in 0xC1-0xE0 range (0xC3, the next supported-PIDs bitmask after 0x80/0xA0), fuel cetane rating (0xC4), engine friction percent torque (0xC8). Total **85 verified universal OBD-II PID entries**. - -### Added (per-OEM enrichment, round 2) -- **VW (`catalogs/vw.json`)** — +5 DIDs: diesel common-rail pressure (0xF430), AdBlue tank level (0xF431), distance-since-last-DPF-regen (0xF433), charge-air temperature (0xF435), DSG oil pressure (0x0290). **52 entries total.** -- **BMW (`catalogs/bmw.json`)** — +5 DIDs: oil quality (0xD305), remaining oil-service distance (0xD306), front + rear brake-pad remaining (0xD307/D308), xDrive torque split (0xD500). **43 entries total.** -- **Ford (`catalogs/ford.json`)** — +4 DIDs: EcoBoost intercooler IAT (0xDE08), engine runtime lifetime (0xDE09), oil life remaining (0xDE0A), powertrain immobilizer status enum (0xDF05). **35 entries total.** - -### Notes -- Universal DTC + PID catalogs are the highest-leverage growth vector — every OEM extension inherits them via the `MergeCatalogJSON('dtc-iso-15031.json', …)` / `MergeCatalogJSON('obd2-pids.json', …)` calls in `BuildCatalog`. Per-OEM enrichments require per-OEM PRs; SAE/ISO universal data is one source citation per batch. -- Citation discipline: every new universal entry cites either SAE J2012 (DTC nomenclature) or SAE J1979 / ISO 15031-6 (OBD-II PID table), so they qualify for `verified: true` per the v3.18 acceptable-citations table. -- Per-OEM additions remain `verified: false`, sourced from the published community references the v3.18 provenance vocabulary documents (ross-tech-wiki, obdeleven-public, esys-community, bimmer-utility, forscan-community, motorcraft-pubs). -- All 40 catalog JSON files validated to parse cleanly (external `json.load` round-trip). - -## [3.20.0] - 2026-05-07 — Reference desktop tool (VCL) - -### Added -- **`examples/diagtool/`** — full reference VCL diagnostic tool that exercises every shipping framework API end-to-end. Built programmatically (no `.dfm`) so the project has just two files (`DiagTool.dpr` + `DiagTool.MainForm.pas`) — drop them into any Delphi 11/12 VCL project as a starter template. -- **Connection wizard** — port + baud combo boxes drive `TOBDConnectionSerial` / `TOBDConnectionAsync` lifecycle (Connect / Disconnect with proper teardown). -- **OEM auto-detect** — Read VIN button issues OBD-II Service 09 PID 02; the response routes through `TOBDOEMRegistry.FindByVIN` and the form labels update to show display name + manufacturer key + the chosen session negotiator. -- **Session control** — Extended → button calls `TOBDDiagSession.BeginSession(sstExtendedDiagnostic, $7E0)` so the OEM-specific choreography (VW SH+CRA / BMW E-Sys / Mercedes XENTRY F198 / Ford ST 32 / GM SP 6 / Stellantis F198) and the heartbeat thread come for free. End Session reverses cleanly. -- **Live Data tab** — refreshes battery voltage / engine RPM / vehicle speed / coolant temperature via standard SAE J1979 Service 01 PIDs (0x42 / 0x0C / 0x0D / 0x05). -- **DTCs tab** — Service 03 read populates a list; Service 04 clear is gated by a confirmation dialog. Selecting a code calls `IOBDOEMExtension.DescribeDTC` and the right-pane memo shows the catalog entry (description + severity + possible causes + repair hints + source + verified flag). -- **DIDs tab** — combo-box auto-populates from the OEM's `DataIdentifiers` catalog (universal `uds-standard.json` entries + per-OEM overlay). Read DID issues `TOBDDiagSession.ReadDID`, runs the response through `IOBDOEMExtension.DecodeDID`, and appends a transcript line per read. -- **Routines tab** — combo-box of catalogued `RoutineControl` identifiers; Start (31 01) issues `TOBDDiagSession.StartRoutine` and prints the status payload. -- All 28 OEM extensions self-register via the `.dpr` uses clause so VIN-based routing works for any of the 17 passenger / 6 heavy-duty / 5 Chinese OEMs. -- `examples/diagtool/README.md` documents the architecture, the build steps, and the deliberate limitations (single-ECU model, no SecurityAccess UI, synchronous reads on the UI thread for clarity). - -### Changed -- Nothing. The tool consumes the framework as-is. - -### Notes -- The companion console example (`examples/diagsession_console`, v3.13) is the minimal proof-of-concept; the v3.20 VCL tool is the proof-of-product showing every framework API in one place. Together they cover the spectrum from "bare-minimum integration" to "ship-ready GUI tool". - -## [3.19.0] - 2026-05-07 — Engine-OEM auto-routing - -### Added -- **`IOBDOEMExtension.ApplicableToECUSupplier(const SupplierID: string): Boolean`** — companion to `ApplicableToVIN` for OEMs that ship engines / modules into other manufacturers' chassis. Engine OEMs (Cummins, Detroit Diesel) and supplier-only modules use this branch when the chassis VIN routes elsewhere. The `SupplierID` is what the ECU returns from J1939 PGN 65259 'Make' or ISO 14229 DID 0xF18A (system_supplier_identifier). -- **`TOBDOEMRegistry.FindByECUSupplier(SupplierID): IOBDOEMExtension`** — walks every registered extension and returns the first that claims the given supplier ID. Empty string short-circuits to nil. -- `TOBDOEMExtensionBase` ships a default `ApplicableToECUSupplier` that returns False — every existing extension is **upward-compatible** and only the engine OEMs (Cummins + Detroit Diesel) opt in to the new probe. -- **`OBD.OEM.Cummins.ApplicableToECUSupplier`** — claims `'CUMMINS'` and the legacy `'CMI'` (Cummins Inc) token some pre-2010 ECMs emit on F18A. Case-insensitive, whitespace-trimmed. -- **`OBD.OEM.DetroitDiesel.ApplicableToECUSupplier`** — claims `'DETROIT'`, `'DDC'`, and the older `'DETROITDDC'` single-token form some MCM-1 modules use. -- `Tests.OEM.SupplierRouting` — 10 new test cases: positive-match for all known tokens (Cummins / CMI / Detroit / DDC / DETROITDDC), negative-match for cross-OEM tokens, registry-level routing for both engine OEMs, empty-string short-circuit, default-False guarantee for non-engine OEMs (VW, Toyota), case-insensitive + whitespace-trim guarantee. - -### Changed -- `IOBDOEMExtension` adds one method. The registry routing now has two probes — VIN first, then supplier — so a tool can call: - ```pascal - Ext := TOBDOEMRegistry.FindByVIN(Vin); - if Ext = nil then - Ext := TOBDOEMRegistry.FindByECUSupplier(SupplierFromF18A); - ``` - to handle the mixed-fleet case (Cummins X15 in a PACCAR Peterbilt vs. a Volvo VNL). - -### Notes -- `TOBDDiagSession` (v3.11) doesn't yet auto-cascade through the two probes — that's a Phase-9-ish ergonomic addition. For now production tools call the two registry helpers explicitly per the snippet above. - -## [3.18.0] - 2026-05-07 — Catalog deepening + verification protocol - -### Added (per-OEM catalog enrichment) -Across the existing 17 passenger OEM catalogs, **~70 new DID + routine entries** were added (all `verified: false` per the v3.3 provenance contract until cross-validated). Highlights: -- **VW (`catalogs/vw.json`)** — +9 DIDs incl. oil pressure (0xF40B), Lambda Bank 1 Sensor 1, DPF soot load (0xF420), EGR actual position, turbo actual boost, DSG transmission oil temp + DSG K1/K2 clutch wear (0x028E/F). +5 routines: KESSY proximity relearn, EPB service, Haldex calibration, DPF force-regen, security access level 3. -- **BMW (`catalogs/bmw.json`)** — +10 DIDs incl. DME / EGS software ID, oil level mm, oil temp, charge-air temp + boost, engine runtime, EGS oil temp + clutch wear, DSC yaw rate (0xC100). +5 routines: EZS / KESSY relearn, EMF / EPB service, RDC tire-pressure relearn, BMS battery registration, BMW TPI DPF regen. -- **Ford (`catalogs/ford.json`)** — +9 DIDs incl. engine hours, engine starts, IAT / ECT / throttle, EcoBoost MAP, PowerStroke DPF soot load, PATS status enum + key count. +5 routines: PCM KAM reset, oil life reset, PATS key program, DPF force-regen, EPB service. -- **Toyota (`catalogs/toyota.json`)** — +8 DIDs incl. engine run time, throttle / IAT / ECT, hybrid inverter temp, hybrid battery max + min block voltage, vehicle grade. +3 routines: smart-key relearn, hybrid battery test, oil maintenance reset. -- **Mercedes-Benz** — +5 DIDs (oil pressure, oil level mm, DPF soot load, AdBlue tank level, steering angle). +3 routines (EIS relearn, DPF regen, battery registration). -- **GM** — +5 DIDs (engine run time, oil pressure, oil life, throttle, immobilizer status enum). +2 routines (oil life reset, PassKey relearn). -- **Stellantis** — +3 routines (DPF regen, oil life reset, PSA BSI battery reg). -- **Honda + HMG + Nissan + Subaru + Mazda + Renault + Volvo** — 3 DIDs + 2-3 routines each: oil life, hybrid / EV pack data, brand-specific routines (battery registration, SAS calibration, oil-life reset, DPF regen). - -### Added (verification protocol) -- **`docs/CATALOG_FORMAT.md`** gains a comprehensive **acceptable-citations table** documenting what `source` values qualify an entry for `verified: true` (ISO standard / SAE standard / capture fixture / OEM-published spec — and explicitly excluding NDA-protected dealer DBs and "I tried it and it worked"). -- A **provenance vocabulary table** lists every `source` token the shipped catalogs use (~30 entries: ISO / SAE / GMLAN / TIS2Web / Motorcraft / ForScan / Ross-Tech / OBDeleven / E-Sys / bimmer-utility / XENTRY / HHTwin / Techstream / HDS / GDS / KDS / Consult / SSM / OpenECU / M-MDS / CLIP / VIDA / Tesla Toolbox / SDT / MUT-III / INSITE / DDDL / DAVIE4 / PTT / SDP3 / MAN-cats / BYD / Geely / NIO / Xpeng / GWM communities) so PR authors know which token is appropriate without reading the full source. -- New **`Tests.OEM.CatalogSmoke`** fixture: cycles every shipped JSON catalog through `TOBDOEMJSONCatalog.Create` and asserts the file parses without raising, declares a non-empty `manufacturer_key` (where applicable), and contributes at least one DID or routine. The regression guard that catches a trailing-comma typo or a bad decoder kind before tagging — **31 catalogs covered**. - -### Changed -- `tests/Tests.dpr` registers the new smoke fixture. - -### Notes -- Production callers filter `Verified` for production-critical paths: - ```pascal - for D in Ext.DataIdentifiers do - if D.Verified then UseInProduction(D); - ``` -- Universal `uds-standard.json` + `obd2-pids.json` + `dtc-iso-15031.json` remain the largest pools of `verified: true` entries (built from ISO / SAE published tables). Per-OEM catalogs grow toward `verified: true` as community contributors cite published specs in their PRs. - -## [3.17.0] - 2026-05-07 — Chinese OEMs (BYD / Geely / NIO / Xpeng / GWM) - -### Added (5 new Chinese OEM extensions) -- **`OBD.OEM.BYD`** — BYD Auto Co. Ltd. (3 WMIs: L6T, LGX, 8GA — Xi'an + Changsha + Brazil). 9-ECU e-Platform 3.0 map: VCU + drive motor + Blade-battery BMS at 0x782 + charge port + iBooster electronic brake + DiPilot driver assistance + climate. Blade battery pack ID + model code DIDs; pack voltage / SOC / SOH; charge-status enum. -- **`OBD.OEM.Geely`** — Geely Auto + Lynk & Co + Zeekr (5 WMIs: LB3, LFM, LJV, LBE, LGZ). 10-ECU map across CMA / SEA / SPA / BMA platforms. Geely platform code + market code DIDs; covers ICE + PHEV (Hi4-shared) + Geometry/Zeekr EV charge controller. Volvo Cars (Geely-owned) stays on `OBD.OEM.Volvo` — collision guard test included. -- **`OBD.OEM.NIO`** — NIO Inc. (2 WMIs: LJN, LBL). EV-only; 10-ECU map for the Hefei plant: VCU + dual-motor (front + rear inverters) + swappable BMS at 0x782 + charge port + Aquila autonomous-driving sensor suite + Banyan/Aspen IVI computer (NOMI). NIO model code + battery-swap pack ID DIDs; pre-swap handshake routine for the NIO Power Swap network. -- **`OBD.OEM.Xpeng`** — Xpeng Motors (2 WMIs: LJY, LMZ — Zhaoqing + Guangzhou). EV-only; 10-ECU map covering the XPILOT ADAS computer + dual-motor stack + Xmart OS cabin computer. Xpeng model code + XPILOT software version DIDs. -- **`OBD.OEM.GreatWall`** — Great Wall Motor (4 WMIs: LGW, LGE, LGT, X9X). Covers the five GWM brands (Haval / WEY / ORA / Tank / Poer) on one platform. 10-ECU map incl. Hi4 hybrid controller, ORA / Coffee EV charge controller, Coffee Pilot ADAS. GWM brand code + platform code (Lemon / Tank / Coffee) DIDs. -- Five matching JSON catalogs (`catalogs/{byd,geely,nio,xpeng,gwm}.json`) and DTC starters (`catalogs/dtc-{byd,geely,nio,xpeng,gwm}.json`). EV-specific decoders for pack voltage, SOC, SOH, charge-status enum. - -### Changed -- `Packages/RunTime.dpk` adds the 5 new units. The OEM registry now resolves **28 OEMs** total (17 passenger + 6 heavy-duty + 5 Chinese). - -### Notes -- `Tests.OEM.China` ships 16 new test cases: VIN routing for all 5 OEMs (Volvo-Cars-vs-Geely-Zeekr collision guard included), catalog spot-checks (BYD Blade BMS at 0x782, NIO Aquila, Xpeng XPILOT, GWM Hi4 hybrid, Geely EVCC), decoder spot-checks for each OEM's distinguishing DID. -- WMI assignments for Chinese OEMs are issued by MIIT under GB 16735 and are sometimes inconsistently documented across sources. The shipped set covers the most-cited assignments per OEM; production users add edge-case WMIs via `OBD.OEM..ApplicableToVIN` overrides if needed. - -## [3.16.0] - 2026-05-07 — Heavy-duty (J1939) OEM extensions - -### Added (6 new heavy-duty OEM extensions) -- **`OBD.OEM.HD`** — shared base for J1939-coupled OEMs. `TOBDHDSessionNegotiator` widens the tester-present heartbeat to 3000 ms so UDS-on-J1939 doesn't race with the broadcast DM1 stream. Constants for the J1939-71 source-address allocations the framework references (`J1939_ADDR_ENGINE_1` = 0, `J1939_ADDR_TRANSMISSION_1` = 3, `J1939_ADDR_BRAKES_SYSTEM` = 11, `J1939_ADDR_AFTERTREATMENT_1` = 66, …). Helpers `FormatSPNFMI(SPN, FMI)` and `ParseDM1DTC(Bytes, Offset)` round-trip the DM1 packed-DTC layout into the canonical `"SPN0094-FMI4"` string used by the catalog. -- **`OBD.OEM.Cummins`** — engine-only OEM (X15 / L9 / B6.7 / ISX15 / ISL9). No VIN match — resolved via `TOBDOEMRegistry.FindByKey('CUMMINS')` once the engine OEM is detected from PGN 65259 (component identification). 3-ECU map (engine + DPF/SCR aftertreatment); engine serial + calibration ID + DEF tank level + DPF soot load DIDs. -- **`OBD.OEM.DetroitDiesel`** — engine-only (DD13 / DD15 / DD16 with GHG17 emissions package). Daimler Truck NA brand; appears as the ECM on Freightliner Cascadia / Western Star. 4-ECU map (MCM + DT12 AMT + DPF + SCR); Detroit-specific calibration / emissions-family DIDs. -- **`OBD.OEM.PACCAR`** — Peterbilt + Kenworth + DAF + Leyland (8 WMIs incl. 1XP/1NP/5KJ/1NK/1XK/2NK/XLR/SCB). 7-ECU map (engine, transmission, Bendix/Wabco brakes, Driver Information Cluster, Cab + Body controllers, aftertreatment); chassis code + factory code DIDs. -- **`OBD.OEM.VolvoTrucks`** — Volvo Trucks + Mack Trucks + Renault Trucks (9 WMIs incl. 4V4/YV2/4V2/1M1/1M2/4V5/4V1/VG6/VF6). Separate from Volvo Cars (Geely-owned, covered in `OBD.OEM.Volvo`). 8-ECU map (EMS/EMC + I-Shift/mDRIVE + EBS + MID 140 + MID 144 VECU + aftertreatment + TPMS); chassis code + emissions-package DIDs. -- **`OBD.OEM.Scania`** — Scania AB / Traton (4 WMIs: VLU, YS2, XLE, 9BS). 8-ECU map (EMS DC09/13/16 + Opticruise OPC + EBS + retarder + ICL + COO coordinator + ACM aftertreatment + AWD forward radar); chassis number + specification code + engine serial DIDs. -- **`OBD.OEM.MAN`** — MAN Truck & Bus / Traton (2 WMIs: WMA, 9BW). 8-ECU map (EDC + TipMatic + EBS + PriTarder retarder + ZBR central computer + FHRR driver assist + BWS body computer + ACM); MAN-specific chassis code + factory options + engine serial DIDs. -- Six matching JSON catalogs (`catalogs/{cummins,detroit,paccar,volvotrucks,scania,man}.json`) with starter DIDs (engine hours / fuel used / DEF tank level / DPF soot load) — all `verified: false` per the v3.3 provenance contract. Six matching DTC starters using the SPN-FMI canonical form (`SPN0094-FMI4`, `SPN3251-FMI16`, `SPN5571-FMI16`, …) covering common heavy-duty fault codes: low fuel rail pressure, DPF differential pressure / soot load, DEF inducement, J1939 communication abnormal update rate. - -### Changed -- `Packages/RunTime.dpk` adds the 6 HD units + the shared `OBD.OEM.HD` base. The OEM registry now resolves **23 OEMs** total — 17 passenger + 6 heavy-duty. - -### Notes -- Engine-only OEMs (Cummins, Detroit Diesel) intentionally return `False` from `ApplicableToVIN` since they don't ship vehicles. Production callers detect the engine OEM from the J1939 component-identification PGN (or DID 0xF18A on UDS-capable trucks) and resolve via `TOBDOEMRegistry.FindByKey(…)`. Phase 8 (engine-OEM auto-routing from a J1939 component-identification probe) is a natural follow-up. -- `Tests.OEM.HD` ships 22 new test cases: SPN-FMI helper round-trip, `ParseDM1DTC` decoding (including a vector for SPN 0148 / FMI 4), VIN routing for all six OEMs (incl. Volvo-Trucks-vs-Volvo-Cars disambiguation guard), 3000 ms heartbeat assertion, ECU-map presence checks (Cummins engine ECM at J1939 address 0, Detroit DPF + SCR, Volvo I-Shift + MID 140, Scania OPC, MAN PriTarder), `FindByKey` resolution, and decoder spot-checks for each OEM's chassis-code DID. - -## [3.15.0] - 2026-05-07 — More OEMs + universal catalog enrichment - -### Added (5 new OEM extensions) -- **`OBD.OEM.Renault`** — Renault Group: Renault SA + Dacia + Alpine + Renault Korea (11 WMIs incl. VF1/VF2/VS5/VR1/3W2/UU1/UU3/UU6/VFA/VFD/KNM). 9-ECU CLIP map (UCH at 0x760, instrument cluster, ABS, SRS, climate, PAS, EVCC for Zoe/Megane E-Tech). Renault calibration ID + market code + options-block DIDs; `'RNLT'` XOR-mask seed-key starter. -- **`OBD.OEM.Volvo`** — Volvo Cars (Geely-owned, separate from Volvo Trucks) (6 WMIs incl. YV1/YV4/LYV/LVS/LVY/7JR). 10-ECU VIDA / DiCE map (CEM at 0x740, DIM cluster, Sensus IHU, EVCC for EX30/EX90). Build week + factory + PNO option DIDs; **5000 ms** tester-present interval (matches VIDA's extended session). -- **`OBD.OEM.Tesla`** — Tesla, Inc. (4 WMIs incl. 5YJ/LRW/XP7/7SA — Fremont + Shanghai + Berlin + Austin). 8-ECU map covering Powertrain, Vehicle Gateway, BMS at 0x782, Autopilot at 0x724, Cabin/IHU, Charge Port. Tesla firmware version + hardware-platform DIDs; battery-pack voltage / SOC / SOH; charge status enum. -- **`OBD.OEM.Suzuki`** — Suzuki Motor Corp + Maruti Suzuki India (9 WMIs incl. JS1/JS2/JSA/JSB/TSM/LSJ/MA3/MBH/ML8). 7-ECU SDT-II map; Suzuki/Maruti chassis-code DID; KWP2000 two's-complement seed-key starter. -- **`OBD.OEM.Mitsubishi`** — Mitsubishi Motors (8 WMIs incl. JA3/JA4/JMB/JMY/4A3/4A4/MMB/6MM). 8-ECU MUT-III map incl. AWC for Outlander PHEV at 0x762, ETACS body controller; SST DCT calibration routine; chassis-code + market-code DIDs. -- Five matching JSON catalogs (`catalogs/{renault,volvo,tesla,suzuki,mitsubishi}.json`) and DTC starters (`catalogs/dtc-{renault,volvo,tesla,suzuki,mitsubishi}.json`) — each with 5-8 manufacturer-specific entries. - -### Fixed -- **WMI `VR1` moved from Stellantis to Renault.** VR1 is the Renault Tangier (Morocco) plant — incorrectly listed under Stellantis since v3.2 (the Stellantis-Renault confusion: PSA + FCA = Stellantis; Renault is separate). The fix updates both `OBD.OEM.Stellantis.ApplicableToVIN` and `catalogs/stellantis.json`. Regression guard test `StellantisNoLongerClaimsVR1` lives in `Tests.OEM.Extras2`. - -### Added (universal catalog enrichment) -- **`catalogs/obd2-pids.json` — 17 new verified entries** filling gaps in the SAE J1979 / ISO 15031-6 ranges 0x60-0xA6: dual-MAF (0x66), EGR temperature, boost / VGT control, exhaust pressure, EGT bank 1 + 2 (0x78 + 0x79), engine run-time variants (0x7E + 0x7F), NOx sensor (0x83), hybrid/EV system data (0x9A), diesel after-treatment (0x9B), odometer PID (0xA6). Brings the universal OBD-II catalog to ~80 verified entries. -- **`catalogs/dtc-iso-15031.json` — 47 new verified P-codes + U-codes** covering camshaft phasing (P0011/P0014/P0016/P0017), fuel-rail pressure (P0087/P0088/P0190), MAP / TPS sensor faults (P0107-P0123), oxygen sensors (P0030-P0150 range), turbocharger boost (P0234/P0299), cylinder 7+8 misfire, glow-plug, EGR / SAI, EVAP small-leak (P0442), idle control, system voltage, ECM internal failure, fuel pump, transmission torque-converter clutch, DPF (P2002), post-cat fuel trim, IAT correlation, CAN bus-off (U0073), MS-CAN (U0010), instrument cluster comm-loss (U0155). Brings the universal DTC catalog to ~95 verified entries. - -### Changed -- `Packages/RunTime.dpk` adds the 5 new OEM units. The OEM registry now resolves **17 OEMs from VIN** covering ~95% of the global passenger fleet by WMI prefix. - -### Notes -- `Tests.OEM.Extras2` ships 21 new test cases: VIN routing for the 5 new OEMs, regression guard for the Stellantis VR1 fix, catalog spot-checks (ECU map presence, Volvo extended heartbeat, Tesla autopilot ECU, Mitsubishi AWC), decoder spot-checks (Renault calibration ID, Volvo PNO code, Tesla firmware version, Suzuki + Mitsubishi chassis codes), and universal-catalog growth assertions (odometer + NOx PIDs present, P0017 + P2002 verified DTCs present). -- Every per-OEM starter remains `verified: false` per the v3.3 provenance contract; universal SAE / ISO entries are `verified: true`. - -## [3.14.0] - 2026-05-07 — OEM coverage expansion (Asia/Pacific fleet) - -### Added -- Six new OEM extensions covering the Japanese + Korean fleet, all built on the v3.3-v3.13 framework (catalog + ECU map + session negotiator + seed-key registry + DTC catalog + DID decoders): - - **`OBD.OEM.Toyota`** — Toyota / Lexus / Daihatsu (16 WMIs incl. JTD/JTE/JTH/JTJ/JTK/JTM/JTN/2T1/2T2/4T1/4T3/5TD/5TE/5TF/5TY/JDA). 8-ECU TechStream map (engine, transmission, hybrid, ABS, SRS, immobilizer, body, cluster) plus Toyota-specific F1A0 calibration ID list, F1A1 ECU serial, hybrid-battery DIDs at 0x7E2. - - **`OBD.OEM.Honda`** — Honda / Acura (14 WMIs incl. JHM/JHL/JHF/JH4/1HG/19U/19V/2HG/2HK/2HN/3HG/5J6/5FN/5FP). 7-ECU HDS map; Honda-specific chassis-code (F1A0) + factory-code (F1A2) DIDs; XOR-mask seed-key starter. - - **`OBD.OEM.HyundaiKia`** — Hyundai / Kia / Genesis (15 WMIs incl. KMH/KM8/KMF/KMT/5NP/5NM/5NX/KNA/KND/KNH/KNB/5XX/5XY/KNF/KMK). 10-ECU GDS / KDS map incl. EV charge controller at 0x7E5; ROM ID + calibration ID + vehicle-option DIDs; 1500 ms tester-present interval (matches GDS default). - - **`OBD.OEM.Nissan`** — Nissan / Infiniti / Datsun (12 WMIs incl. JN1/JN6/JN8/1N4/1N6/3N1/5N1/5BZ/JNK/JNR/JNX/MNT). 9-ECU Consult III+ map incl. IPDM at 0x745, AVM at 0x768, Leaf/Ariya EV charge controller at 0x793; chassis-code + market-code DIDs. - - **`OBD.OEM.Subaru`** — Subaru (5 WMIs incl. JF1/JF2/JF3/4S3/4S4). 7-ECU SSM4 map incl. dedicated AWD controller at 0x7E2; CVT relearn routine; byte-rotate seed-key starter. - - **`OBD.OEM.Mazda`** — Mazda (6 WMIs incl. JM1/JM3/JM7/JMZ/4F2/4F4). 8-ECU M-MDS map incl. RBCM at 0x726 (Mazda-specific rear body controller); Mazda As-Built code + market code DIDs. -- Six matching JSON catalogs (`catalogs/{toyota,honda,hmg,nissan,subaru,mazda}.json`) with starter DIDs (~6-8 per OEM) — all `verified: false` per the v3.3 provenance contract. -- Six matching DTC starter catalogs (`catalogs/dtc-{toyota,honda,hmg,nissan,subaru,mazda}.json`) with 7-8 manufacturer-specific codes each (P-codes for engine/trans, B-codes for body, U-codes for comm-loss). Production users contribute via JSON edits without recompiling. -- `Tests.OEM.AsiaPacific` — 19 new test cases: VIN routing for every OEM (positive matches + cross-OEM rejection + unknown-VIN check), catalog spot-checks (Toyota engine ECU, Honda seed-key starter, HMG 1500 ms heartbeat, Nissan IPDM, Subaru AWD controller, Mazda RBCM), and DID decoder spot-checks for each OEM's custom decode paths. - -### Changed -- `Packages/RunTime.dpk` adds the six new units. The `OBD.OEM.Registry` now resolves 12 OEMs from VIN (up from 6). - -### Notes -- Toyota covers most of the global Japanese-built fleet; Honda picks up American Honda manufacturing; HMG is the third-largest automaker globally; Nissan + Subaru + Mazda round out the Japanese mid-tier and the AWD-focused niche. -- Seed-key starters are placeholders (community-pr provenance, `verified: false`). Real algorithms live behind dealer NDAs; production users register their own at app startup via `Ext.SeedKeyRegistry.RegisterAlgorithm($01, …)`. -- Combined with the European (VW, BMW, Mercedes, Stellantis) + American (Ford, GM) extensions from v3.2, the framework now covers ~85% of the global passenger-vehicle fleet by VIN-prefix. - -## [3.13.0] - 2026-05-07 — OEM Catalog Phase 7 (golden-check helper + reference CLI) - -### Added -- **`OBD.OEM.GoldenCheck`** — framework-neutral spot-check helper. `CheckGoldenVectors(Ext, Vectors)` runs each `(DID, Payload, ExpectedSubstring, Description)` tuple through the OEM extension's `DecodeDID` and returns a list of `TOBDGoldenFailure` records with the actual output and a pre-formatted reason — empty when every vector passed. Callers decide whether to `Assert.Fail` the batch, surface the count, or post-process. -- `Tests.OEM.GoldenCheck` — 4 helper-behaviour tests (passes / missing-substring / empty-output / empty-substring matches non-empty), plus `TPerOEMGoldenTests` with curated golden vectors for all four shipping OEM extensions (VW + BMW + Mercedes + Ford), 12 vectors total covering VIN, mileage, battery voltage, manufacturing date, programming status. These are the spot-check suite to run before tagging. -- **`examples/diagsession_console/DiagSessionDemo.dpr`** — small reference console tool that drives `TOBDDiagSession` end-to-end against any ELM327-compatible adapter on a serial port. Demonstrates the v3.11 high-level API: connect, pick OEM extension by VIN prefix, `BeginSession(sstExtendedDiagnostic, $7E0)`, `ReadDID(F190 / F189 / D050)` with decoded output, `EndSession`. ~75 lines — the canonical "hello, OEM" template a tool-builder copy-pastes from. - -### Changed -- `Packages/RunTime.dpk` adds `OBD.OEM.GoldenCheck`. - -### Notes -- This closes the seven-phase OEM extension plan started in v3.3. Every phase is now ✅ in `docs/OEM_EXTENSION_PLAN.md`. The framework now ships: - - DID + routine + DTC catalogs with provenance flags (v3.3 + v3.7) - - Per-ECU sub-catalogs (v3.4) - - Per-OEM session negotiators + plan runner with heartbeat (v3.5) - - Pluggable seed-key algorithms (v3.6) - - VW long coding / BMW FA + I-Stufe / MB SCN / Ford AsBuilt codecs (v3.8) - - UDS RoutineControl framework (v3.9) - - Capture-replay validation (v3.10) - - High-level `TOBDDiagSession` wrapper (v3.11) - - DoIP / ISO 13400-2 frame builders + parsers (v3.12) - - Golden-vector spot-checks + reference CLI (v3.13) -- Future growth lives along the orthogonal axes documented across `docs/OEM_EXTENSION_PLAN.md`: scaling each per-OEM JSON catalog from `verified: false` starter to `verified: true` production data, registering NDA-protected seed-key algorithms at app startup, and contributing real ECU captures into `tests/fixtures/captures/`. The framework no longer needs structural work to absorb that growth. - -## [3.12.0] - 2026-05-07 — OEM Catalog Phase 6.2 (DoIP / ISO 13400-2) - -### Added -- **`OBD.OEM.DoIP`** — ISO 13400-2 frame builders + parsers for the Ethernet transport modern (post-2018) cars use for UDS: - - `BuildDoIPHeader` / `ParseDoIPHeader` — the 8-byte protocol header (Version + InvVersion + PayloadType + PayloadLength) with the inversion check. - - `BuildRoutingActivationRequest` (default + WWH-OBD + central-security + OEM-specific activation types) and `ParseRoutingActivationResponse` (handles both 2010 9-byte and 2012 13-byte payload variants — the OEM-specific 4-byte tail). - - `BuildVehicleIdentRequest` (broadcast on UDP/13400) + `BuildVehicleIdentRequestByVIN` + `ParseVehicleAnnouncement` returning VIN, logical address, EID, GID, FurtherActionRequired, optional sync status. - - `BuildAliveCheckRequest` / `BuildAliveCheckResponse`. - - `BuildDiagnosticMessage(Source, Target, UserData)` / `ParseDiagnosticMessage` — wraps an arbitrary UDS request in the DoIP envelope so a `TOBDDiagSession` (v3.11) can use a TCP DoIP connection identically to a CAN connection. -- Enums for the documented payload types, activation types, and routing-response codes (success, vehicle-confirmation, all 7 standard rejection codes). -- `Tests.OEM.DoIP` — 22 new test cases: header (version-inversion encoding + check, big-endian payload-type / length round-trip, malformed inversion + short-buffer rejection), routing activation (default + OEM-specific activation type, v2010 + v2012 response parsing, truncation rejection, wrong-payload-type returns False), vehicle ident (empty payload broadcast, VIN-too-short rejection, VIN round-trip, VehicleAnnouncement field extraction including 17-char VIN + 6-byte EID/GID + sync status), diagnostic message (UDS wrapping with header + addresses, empty-user-data rejection, address + payload extraction, full round-trip, alive-check pair). - -### Changed -- `Packages/RunTime.dpk` adds `OBD.OEM.DoIP`. - -### Notes -- The DoIP unit is transport-agnostic by design — it produces and consumes byte arrays. Pair it with the existing `OBD.Connection.UDP` / `OBD.Connection.Wifi` for the actual sockets, and the `TOBDDiagSession` wrapper drives the UDS layer on top exactly the same way it does for CAN. -- Phase 7 (ODX-D import + golden-test helper) is the final milestone in `docs/OEM_EXTENSION_PLAN.md`. - -## [3.11.0] - 2026-05-07 — OEM Catalog Phase 6.1 (high-level diagnostic session) - -### Added -- **`OBD.OEM.DiagSession`** — `TOBDDiagSession` is the high-level wrapper that turns the lower-level OEM machinery into the API a tool actually calls. One class binds an OEM extension to a connection and exposes `BeginSession`, `EndSession`, `UnlockSecurityAccess`, `ReadDID`, `StartRoutine`, `StopRoutine`, `RequestRoutineResults`, plus a `State` accessor and a `LastError` string for the simple failure-reporting path tools want. -- The wrapper owns the tester-present heartbeat thread end-to-end: `BeginSession` starts it, `EndSession` (and the destructor) stop it gracefully. Re-entering the same session is idempotent; cross-session transitions stop the heartbeat first so the next session-control request doesn't race against it. -- `UnlockSecurityAccess(Level, [Algorithm])` runs the full UDS 27 LL → 67 LL SEED → 27 LL+1 KEY exchange. By default it pulls the algorithm from the OEM extension's `SeedKeyRegistry`; the optional `Algorithm` parameter lets production users plug their NDA-protected algorithm in at the call site without registering it globally. -- `ReadDID(DID, out Payload: TBytes)` and `ReadDID(DID, out Decoded: string)` — the second form runs the bytes through the OEM's `DecodeDID` so tool UIs can render the human-readable string directly. -- `StartRoutine(RID, InputData, out Status)` / `StopRoutine(RID)` / `RequestRoutineResults(RID, out Status)` thread negative-response NRCs into `LastError` instead of raising, matching the tool-friendly contract `BeginSession` / `EndSession` use. -- `Tests.OEM.DiagSession` — construction-time guards (`RejectsNilConnection`, `RejectsNilExtension`). The bytes-on-the-wire integration sits with the existing console flashing example which already drives the same primitives end-to-end. - -### Changed -- `Packages/RunTime.dpk` adds `OBD.OEM.DiagSession`. - -### Notes -- This is the integration milestone — the layer that proves the v3.3-v3.10 work composes cleanly. A new tool now writes: - ```pascal - Session := TOBDDiagSession.Create(Conn, OEM); - Session.BeginSession(sstExtendedDiagnostic, $7E0); - Session.UnlockSecurityAccess($01); - Session.ReadDID($F190, Vin); - Session.StartRoutine($0F00, [], Status); - Session.EndSession; - ``` - …and the framework handles the OEM-specific session choreography, the security-access dance, the heartbeat thread, the SID echo stripping, and the negative-response routing for them. -- Phase 6.2 (multi-bus / DoIP routing activation, FlexRay) is the next milestone. - -## [3.10.0] - 2026-05-07 — OEM Catalog Phase 5 (capture-replay validation) - -### Added -- **`OBD.OEM.Captures`** — replay-driven validation of OEM extensions against recorded `.obdlog` conversations. Walks a `TOBDReplayer`'s entries, pairs each Sent line with its next Received line, normalises ELM327 framing (multi-line `0:` / `1:` prefixes, `SEARCHING…`, prompts), extracts the UDS service ID + DID + payload from the request and the matching response, and runs every `0x22 ReadDataByIdentifier` pair through the OEM extension's `DecodeDID`. -- `TOBDCapturePair` — one structured request/response from the conversation: `RequestText`, `ResponseText`, `ServiceID`, `DID` (when 0x22), `PayloadBytes` (with the SID + DID echo stripped on positive replies), `IsNegative` + `NegativeResponseCode` for `7F SID NRC` replies. -- `TOBDCaptureDecoded` — the validator's per-pair report: which OEM catalog entry it matched (`DidIsCatalogued` + `DidName`) and the decoder's `Display` output. Negative replies and non-0x22 service IDs flow through with their pair attached for caller-side post-processing. -- High-level helpers: `ExtractCapturePairs(entries)`, `ValidateAgainstExtension(pairs, ext)`, `ValidateCaptureFile(path, ext)` for the round-trip "give me a `.obdlog`, give me an OEM extension, tell me what each pair decodes to". `NormalizeResponseText` is exposed so callers can pre-process recorded data outside the validator. -- `tests/fixtures/captures/sample-{vw,bmw,mercedes,ford}.obdlog` — synthetic conversations exercising VIN reads, mileage, I-Stufe, programming-status, calibration-id, and a deliberate negative response per file. Cover the most common DIDs the v3.4 + v3.7 catalogs already decode. -- `Tests.OEM.Captures` — 12 new test cases. Extract layer: ELM multi-line stripping, prompt / SEARCHING handling, request/response pairing, DID extraction from `22 HiDID LoDID`, negative-response capture, response-echo stripping for non-0x22 services, hanging-request handling. Validator layer: VW capture decodes the F190 VIN read and surfaces the negative reply; BMW capture decodes I-Stufe + mileage; Mercedes capture decodes the F19E programming-status enum; Ford capture decodes the calibration-ID DF01; negative responses round-trip the NRC byte. - -### Changed -- `Packages/RunTime.dpk` adds `OBD.OEM.Captures`. - -### Notes -- The shipped fixtures are synthetic — exactly the bytes a real ECU would return for the catalogued DIDs, but hand-authored. Real ECU captures donated by the community are the natural growth path; the framework already accepts whatever `TOBDRecorder.SaveToFile` produces, so contributors only need to capture-and-commit. -- Phase 6 (DoIP / FlexRay / multi-bus extensions on top of the existing protocol layer) is the next milestone. - -## [3.9.0] - 2026-05-07 — OEM Catalog Phase 4 (RoutineControl schemas) - -### Added -- **`OBD.OEM.RoutineControl`** — UDS Service 0x31 (RoutineControl) wire helpers + argument schemas. Implements ISO 14229-1 §10.5.4 end-to-end: build a request, parse the positive / negative response, and project the status payload through a per-routine field schema for human-readable rendering. -- `TOBDRoutineRequestBuilder` — fluent builder for the request payload. `AddUInt8`, `AddUInt16BE`, `AddUInt32BE`, `AddInt16BE`, `AddInt32BE`, `AddAscii(s, FixedLength)` (zero-pads and rejects too-long input), `AddRawBytes`, `AddBcdDate(YY, MM, DD)`, `AddBcdYear`. `ToFrame(SubFunction, RID)` wraps the payload as `31 SF HiRID LoRID …`; `Clear` resets for re-use. -- `TOBDRoutineResponseReader` — cursor-based reader for the response status payload. `ReadUInt8 / ReadUInt16BE / ReadUInt32BE / ReadInt16BE / ReadInt32BE / ReadAscii(N) / ReadHexBytes(N) / ReadBcdDate`. `ReadAscii` strips trailing `#0` padding (the way most ECU firmware writes ASCII). Under-reads raise `EOBDRoutineError` with cursor + remaining-byte info for easier debugging. -- Top-level wire helpers: `BuildStartRoutine(RID, [InputData])`, `BuildStopRoutine(RID)`, `BuildRequestRoutineResults(RID)`, and `ParseRoutineResponse(Response, ExpectedSF, ExpectedRID)`. The parser distinguishes positive `71 SF RID …` replies (returns the status payload as `TBytes`) from negative `7F 31 NRC` replies (raises `EOBDRoutineError` with the NRC in the message) and from short / wrong-SID replies. -- `TOBDRoutineSchema` + `TOBDRoutineField` + `TOBDRoutineFieldKind` — output schemas mirror the v3.3 DID decoder format (uint8/16BE/32BE, int variants, ASCII, hex, BCD date, enum with named values, bitmask with bit names). `DecodeRoutineOutput(Schema, Bytes)` walks the response and produces one `TOBDDecodedField` per output (`Display` string + `Raw` slice). Truncated responses decode the prefix only — useful when an OEM optionally trails extra status bytes. -- `Tests.OEM.RoutineControl` — 27 new test cases covering: builder (uint/int big-endian round-trip, signed -1 → 0xFF FF FF FF, ASCII pad + too-long rejection, BCD date / year, ToFrame wrapping, Clear), reader (multi-byte BE, ASCII zero-pad strip, BCD date, hex slice, under-read rejection, HasMore tracking), wire frames (start with / without data, stop, request-results, parse positive, parse rejects wrong SID / SF / RID, parse on negative response, empty status payload), and schema decoding (uint8 with scale + offset + unit, ASCII + uint32 mileage, bitmask with named bits, enum with hex fallback, truncation handling). - -### Changed -- `Packages/RunTime.dpk` adds `OBD.OEM.RoutineControl`. - -### Notes -- `TOBDRoutineSchema` is the structural primitive — production callers typically pair it with a per-OEM `TDictionary` keyed by RID (Phase 4.1, future). The framework intentionally doesn't ship an OEM-wide schema registry yet because real schemas live in OEM-private ODX files. -- Phase 5 (real-capture `.obdlog` test fixtures cross-validating the catalog decoders) is the next milestone. - -## [3.8.0] - 2026-05-07 — OEM Catalog Phase 3 (coding / variant-write encoders) - -### Added -- **`OBD.OEM.Coding`** — shared base for OEM coding codecs. Exposes `HexStringToBytes` (strips whitespace + `-_:.` separators, rejects odd-length / non-hex), `BytesToHexString` (with optional separator), and bit-level `GetBit` / `SetBit` over a `TBytes`. -- **`OBD.OEM.Coding.VW`** — `TOBDVWLongCoding` mutable VAG long-coding string. Constructed from the hex returned by DID 0xF1A0 / 0xF1AF, gives byte and bit accessors, `HasNonZeroByte` for the dealer-tools "is this fresh coding?" check, and round-trips back via `ToHex`. Length is per-controller and fixed at construction; out-of-range writes raise `EOBDCodingError`. -- **`OBD.OEM.Coding.BMW`** — two records: - - `TOBDBMWFA` — vehicle-order option list. Parses comma / semicolon / whitespace-separated tokens, normalises to upper case, de-duplicates on add, sorts on `ToString` so equal orders always serialise identically (audit-friendly). - - `TOBDBMWIStufe` — `Project-YY-MM-Build` versioning quad. `Parse` validates each segment; `CompareTo` orders by Project → Year → Month → Build; `AtLeast` returns False across different projects (you should never compare an F-series to a G-series I-Stufe). -- **`OBD.OEM.Coding.Mercedes`** — `TOBDMercedesSCN` structured SCN (Standard-Codierung-Nummer). The framework treats segments as opaque strings — Hardware / Project / Build — and only validates the structure (3 segments, alphanumeric-only). Per-segment semantics live in caller-supplied lookup tables since they're FIN-keyed and NDA-protected. -- **`OBD.OEM.Coding.Ford`** — `TOBDFordAsBuiltBlock` for the per-DID 5-byte format used by FORScan / IDS exports. `ComputeChecksum` implements the documented FORScan algorithm (sum of all 5 data bytes mod 256); `IsValid` validates a parsed block; `Reseal` recomputes after editing. `ParseFordAsBuiltText` walks a multi-line export, skipping blank lines and `;` / `#` comments. -- `Tests.OEM.Coding` — 38 new test cases: hex/bit helpers (round-trip, separator stripping, odd-length rejection, bad-character rejection, bit operations + out-of-range), VW long coding (construction, byte/bit ops, has-non-zero detection, hex round-trip, snapshot independence, out-of-range rejection), BMW FA (parsing, dedup, normalisation, removal, sort-on-serialise, case-insensitive lookup, empty rejection), BMW I-Stufe (parse round-trip, malformed input rejection, ordering by Y/M/Build, cross-project AtLeast, zero-padding), Mercedes SCN (3-segment parsing, segment-count rejection, illegal-character rejection, upper-casing, round-trip), Ford AsBuilt (checksum algorithm, line parsing, missing-checksum rejection, reseal-after-edit, comment skipping, round-trip). - -### Changed -- `Packages/RunTime.dpk` adds `OBD.OEM.Coding`, `.VW`, `.BMW`, `.Mercedes`, `.Ford`. - -### Notes -- These are the codec primitives — the wire format on each side. Per-controller bit-name maps for VW long coding and per-FIN SCN dictionaries belong to caller-supplied data files (and in many cases NDA-protected OEM catalogs); the framework gives you the mutable structure plus byte / bit accessors so an application can layer its own UI on top. -- Phase 4 (RoutineControl argument schemas — input encoders + output decoders) is the next milestone. - -## [3.7.0] - 2026-05-07 — OEM Catalog Phase 2 (DTC catalogs) - -### Added -- **`OBD.OEM.DTC`** — Diagnostic Trouble Code framework. `TOBDDtcCatalog` provides O(1) `FindByCode` over an indexed list of `TOBDDtcCatalogEntry` records (`Code`, `Severity`, `Description`, `PossibleCauses`, `RepairHints`, `Source`, `Verified`). Severities: `dtcSeverityInfo` / `Warning` / `Critical` / `Unknown`. -- ISO 15031-5 / SAE J2012 wire-format helpers: `FormatDtc(High, Low)` / `FormatDtc(TBytes)` decode the two-byte DTC into the canonical 5-character form (`P0301`, `B22A8`, `U0100`); `EncodeDtc(string)` round-trips back to bytes; `IsManufacturerDtc` flags the P1xxx / P3xxx / B1xxx / B3xxx / C1xxx / C3xxx / U1xxx / U3xxx ranges. -- JSON catalog format mirrors the v3.3 DID schema (provenance via `source` + `verified`). Supports both the canonical `{ default_source, dtcs: [...] }` envelope and a bare `[...]` array for trivial files. -- `OBD.OEM.DTC.Loader.MergeDtcCatalog(file, catalog)` reuses the catalog search path from v3.3 so DTC files live alongside the DID files in `catalogs/`. -- **`catalogs/dtc-iso-15031.json`** — universal SAE J2012 / ISO 15031-6 baseline of 47 P0xxx + U0xxx entries (misfires, oxygen sensors, EVAP, EGR, catalyst, transmission, communication-loss codes), all `verified: true`. -- **Per-OEM DTC starters** (all `verified: false`): `dtc-vw.json` (VAG cooling / TCM / DSG / mechatronic), `dtc-bmw.json` (VANOS / Valvetronic / DDE diesel boost / FlexRay), `dtc-mercedes.json` (CDI fuel / ESP / SRS / ESM), `dtc-ford.json` (KAM / EVAP / throttle limp), `dtc-gm.json` (HO2S / Tech 2 trans / SDM airbag), `dtc-stellantis.json` (FCA + PSA EVAP / BSI). 7-9 entries each, sourced from public service-manual summaries. -- `IOBDOEMExtension.DtcCatalog` + `DescribeDTC(Code, out Entry)` — every extension exposes its lazily-loaded catalog (universal baseline + per-OEM overlay) and a one-shot lookup helper. Production callers register additional entries on the catalog at runtime via `Cat.Add(Entry)`. -- `Tests.OEM.DTC` — 20 new test cases: every encoding case (P / C / B / U letters, manufacturer first-digits 1 + 3, byte round-trip, malformed-input rejection, manufacturer-vs-SAE detection, severity round-trip), and the catalog (top-level envelope vs bare array, case-insensitive lookup with whitespace trimming, duplicate-code replacement, possible-causes / repair-hints capture, default-source propagation, verified-flag default). - -### Changed -- `IOBDOEMExtension` gains `DtcCatalog` + `DescribeDTC`. `TOBDOEMExtensionBase` adds the lazy catalog accessor + virtual `SeedDefaultDtcCatalog` and `DtcCatalogFileName` override-points; all six OEM extensions chain to a baseline `dtc-iso-15031.json` load and append their per-OEM overlay. -- `Packages/RunTime.dpk` adds `OBD.OEM.DTC` and `OBD.OEM.DTC.Loader`. - -### Notes -- The universal `dtc-iso-15031.json` baseline is `verified: true` against SAE J2012 — safe to surface in production diagnostics. Per-OEM starters remain `verified: false` until cross-validated against an OEM service manual; the same provenance contract as v3.3 applies. -- Phase 3 (coding / variant-write encoders — VW long coding, BMW FA, Mercedes SCN, Ford AsBuilt) is the next milestone. - -## [3.6.0] - 2026-05-07 — OEM Catalog Phase 1.4 (seed-key plug-ins) - -### Added -- **`OBD.OEM.SeedKey`** — pluggable SecurityAccess (UDS service 0x27) algorithm framework. `IOBDSeedKeyAlgorithm` is a pure function (`ComputeKey(Seed, Level)`); `TOBDSeedKeyRegistry` maps levels (the odd byte in `27 LL`) to one or more candidate algorithms. Newer registrations win, so production users plug their NDA-protected real algorithm in at startup and the public starter steps aside automatically. -- Reference algorithm classes (publicly-documented, all `verified: false`): - - `TOBDSeedKeyKWP2000TwosComplement` — `key = (NOT seed) + 1` byte-wise with carry; the ISO 14229 textbook example. Several legacy KWP2000 modules accept it verbatim. - - `TOBDSeedKeyXorMask` — `key[i] = seed[i] XOR mask[i]` with the mask tiled when shorter than the seed; covers a class of aftermarket bypass dongles. - - `TOBDSeedKeyByteRotate` — caller-supplied shift, rotate (0..7) and mask; approximates publicly-described pre-UDS Ford / GM Class B variants. - - `TOBDSeedKeyConstant` — fixed key independent of seed; useful for lab fixtures and the few pre-2010 modules that accept Level 1 with a constant. -- Frame helpers: `RequestSeedFrame(Level)`, `SendKeyFrame(Level, Key)`, `ExtractSeed(Response, Level)` — round-trip the wire format with explicit error reporting (rejects even seed-request levels, wrong SID, level mismatch, empty key). -- `IOBDOEMExtension.SeedKeyRegistry` — every OEM extension exposes its registry; `TOBDOEMExtensionBase` lazily instantiates and seeds it via the new `SeedDefaultSeedKeyAlgorithms` override-point. -- All six OEM extensions ship a default starter algorithm at Level 1: VW + Mercedes + Stellantis use the KWP2000 two's-complement; BMW uses an XOR-mask placeholder from bimmer-utility; Ford uses a byte-rotate placeholder from the ForScan documentation; GM uses the public GMLAN Class B trial-mode constant. All `verified: false`. -- `Tests.OEM.SeedKey` — 28 new test cases: the four reference algorithms (textbook two's-complement vector, byte-wise carry across 0x12345678 → 0xEDCBA988, XOR mask tiling, rotation behaviour, constant-key seed-independence, empty-input rejection), the registry (register / find / find-all / unregister / level enumeration / clear / LIFO precedence), the frame helpers (request seed, send key, extract seed, every error path), and the per-OEM hookup (each of the six extensions has a starter at Level 1; production override shadows the starter; starters are unverified). - -### Changed -- `IOBDOEMExtension` gains `SeedKeyRegistry: TOBDSeedKeyRegistry`. `TOBDOEMExtensionBase.Destroy` cleans the per-instance registry up. -- `Packages/RunTime.dpk` adds `OBD.OEM.SeedKey`. - -### Notes -- **Real seed-key algorithms remain NDA-protected by every OEM.** Nothing shipped here will unlock a production ECU; the starters exist so the broader SecurityAccess flow (request → seed → key → respond) can be exercised end-to-end against a simulated ECU. Production users register their own algorithm at app startup; `RegisterAlgorithm` returns the new entry to the head of the level's list, so the public starter is automatically shadowed. -- Phase 2 (DTC catalogs — manufacturer-specific P1xxx / B / C / U codes) is the next milestone in `docs/OEM_EXTENSION_PLAN.md`. - -## [3.5.0] - 2026-05-07 — OEM Catalog Phase 1.3 (session negotiation) - -### Added -- **`OBD.OEM.Session`** — manufacturer-specific session-negotiation framework. `IOBDSessionNegotiator` describes an OEM's choreography for entering / leaving each diagnostic session as a *plan* (an ordered list of adapter and UDS steps plus a tester-present heartbeat spec). Plans are pure data, so the OEM core stays free of async dependencies. -- `TOBDSessionType` enum: `sstDefault`, `sstProgramming`, `sstExtendedDiagnostic`, `sstSafetySystem`, plus two reserved OEM-specific slots (`sstOEMSpecific1` / `sstOEMSpecific2`) for vendor session subtypes that don't fit the ISO 14229 four. -- `TOBDStandardSessionNegotiator` — pure ISO 14229 reference implementation (10 03 / 10 01, 3E 80 every 2000 ms, optional `AT SH ` header step). Used as the default for every extension that doesn't override. -- Six OEM negotiators, each modelling published service-tool behaviour: - - `TOBDVWSessionNegotiator` — emits `AT SH ` + `AT CRA ` before 10 03 (matches ODIS / VCDS). - - `TOBDBMWSessionNegotiator` — flags `RequiresSecurityAccess` for both extended diagnostic and programming (matches E-Sys); 1500 ms tester-present interval for older E-series DMEs. - - `TOBDMercedesSessionNegotiator` — appends a 22 F1 98 workshop-code probe after 10 03 (XENTRY default); 1500 ms heartbeat. - - `TOBDFordSessionNegotiator` — prepends `AT ST 32` (≈3.2 s adapter timeout) for programming sessions to absorb the FDRS pause. - - `TOBDGMSessionNegotiator` — locks the ELM327 to ISO 15765-4 11/500 (`AT SP 6`) before opening a session. - - `TOBDStellantisSessionNegotiator` — appends 22 F1 98 with an empty `ExpectedResponse` so PSA's required probe doesn't fail on FCA modules that NACK it. -- `IOBDOEMExtension.SessionNegotiator` — every extension exposes its negotiator; `TOBDOEMExtensionBase` caches the instance lazily and lets subclasses override `CreateSessionNegotiator`. -- **`OBD.OEM.Session.Runner`** — async-first plan executor: - - `TOBDSessionRunner.Execute(Plan)` walks each step against `TOBDConnectionAsync`, awaits its `IOBDFuture` reply, and validates the response against the step's `ExpectedResponse` prefix (empty prefix = "any non-empty reply passes" — that's what lets Stellantis' optional F198 step tolerate FCA NACKs). - - `TOBDSessionRunResult` returns a per-step audit trail (response text, success flag, error, wall-clock duration) so callers can log exactly which step failed and what the ECU said. - - `TOBDTesterPresentThread` — fire-and-forget heartbeat thread driven by the plan's `TesterPresentMs` / `TesterPresentRequest`. Exits cleanly on `StopGracefully` (cancels in-flight futures + waits for the thread to drain) and self-terminates if the connection drops, so a closed adapter doesn't spin. -- `Tests.OEM.Session` — 18 new test cases covering: standard negotiator (header step, default-vs-non-default heartbeat, EndSession 10 01, security-access flags, zero-address omits header) and the six per-OEM negotiators (VW SH+CRA, BMW security-access flags + 1500 ms heartbeat, Mercedes F198 probe, Ford ST 32 only on programming, GM SP 6 prefix, Stellantis F198 with empty `ExpectedResponse`); plus extension-level checks that each OEM resolves to the correct negotiator and the negotiator is cached across calls. - -### Changed -- `Packages/RunTime.dpk` adds `OBD.OEM.Session` and `OBD.OEM.Session.Runner`. - -### Notes -- The session negotiators describe the *protocol* choreography; security-access (seed-key) is intentionally out of scope here and lands in Phase 1.4 (`IOBDSeedKeyAlgorithm` registry per OEM / level). -- The runner is exercised end-to-end against a mock connection in Phase 1.4 once seed-key plays the second half of the session-entry handshake. The plan layer (negotiator outputs) is fully covered today. - -## [3.4.0] - 2026-05-07 — OEM Catalog Phase 1.2 (per-ECU sub-catalogs) - -### Added -- **Per-ECU sub-catalogs.** `IOBDOEMExtension` gains `ECUs: TArray` and `CatalogForECU(Address): TOBDOEMSubCatalog`. The framework now models the vehicle bus map: each catalogued DID and routine carries an `EcuAddress` field, and callers can request the subset that applies to a single ECU (engine 0x7E0 vs transmission 0x7E1 vs cluster 0x40, …) instead of walking a flat catalog where 0xF187 means whatever the answering ECU said. -- `TOBDOEMECU` record (`Address`, `Name`, `CommonName`) — describes one ECU on the bus. Helper `ECU(addr, name, common_name)` mirrors the existing `DID()` / `Routine()` builders. -- `TOBDOEMSubCatalog` record (`EcuAddress`, `DIDs`, `Routines`) — the filtered view returned by `CatalogForECU`. Globals (entries with `EcuAddress = 0`) flow through to every ECU; ECU-scoped entries are added when the address matches. -- JSON catalog schema additions: top-level `ecus` array (declares the bus map), top-level `default_ecu_address` (propagates to entries that omit `ecu_address`), and `ecu_address` is now also valid on routine entries. Schema documented in `docs/CATALOG_FORMAT.md`. -- `MergeCatalogJSON(file, var DIDs, var Routines, var ECUs)` overload merges the loaded `ecus` block alongside the DID + Routine merges. The original two-argument overload still works for callers that don't need the ECU map. -- All six OEM Pascal extensions (`OBD.OEM.{VW,BMW,Mercedes,Ford,GM,Stellantis}`) ship a hard-coded ECU map covering powertrain (engine, transmission), chassis (ABS / ESP / SRS), body (BCM / cluster / climate), and gateway addresses. Per-OEM `catalogs/.json` files now carry the same `ecus` block; the seed VW + BMW catalogs additionally annotate `ecu_address` per DID and per routine where the scope is known. -- `Tests.OEM.Catalog.TPerECUTests` — 7 new test cases: ECU list parsing, per-DID `ecu_address`, default-address propagation, explicit-address override, routine `ecu_address` parsing, `CatalogForECU` filter behaviour for scoped entries, and global-entry flow-through to every sub-catalog. - -### Changed -- `TOBDOEMExtensionBase.BuildCatalog` signature gains a third `var ECUs: TArray` parameter so subclasses populate DIDs, Routines, and the ECU map in a single hook. Callers outside this repository that subclassed `TOBDOEMExtensionBase` will need a one-line signature update. -- `OBD.OEM.Helpers.DID()` and `Routine()` zero-initialise their result records (so the new `EcuAddress` field is always defined) and gain three-argument overloads `DID(addr, name, desc, ecu_addr)` / `Routine(id, name, desc, ecu_addr)` for inline scoping. - -### Notes -- The ECU addresses shipped in the hard-coded Pascal maps and the seeded JSON `ecus` blocks are based on public-knowledge UDS request IDs (ISO 15765-4 0x7E0-0x7E7 for emissions, vendor-specific ranges from ross-tech, esys-community, forscan-community, tis2web-public, alfaobd / diagbox, xentry-community references). Per-DID `ecu_address` annotations remain `verified: false` until cross-checked against OEM specs or capture fixtures — the same provenance contract that landed in v3.3 applies. -- Phase 1.3 (manufacturer-specific session negotiation: `BeginSession` / `EndSession` / `StartTesterPresent` per OEM) is the next milestone in `docs/OEM_EXTENSION_PLAN.md`. - -## [3.3.0] - 2026-05-06 — OEM Catalog Phase 1.1 (DID scale-up infrastructure) - -### Added -- **External JSON catalog format** for OEM extensions, documented in `docs/CATALOG_FORMAT.md`. Schema v1 includes per-entry `source` and `verified` provenance flags so callers can filter unverified community data out of production-critical paths. -- `OBD.OEM.Catalog.JSON` (`src/Services/`) — `TOBDOEMJSONCatalog` loads and walks a v1 catalog file. Supports decoder kinds: `ascii`, `hex`, `uint8/16_be/32_be`, `int16_be`, `int32_be`, `bcd_date`, `enum` (with size + value lookup map), `bitmask` (with size + bit-name map), `seconds`. `DecodePayload(DID, Bytes)` formats raw ECU bytes per the catalog's decoder spec. -- `OBD.OEM.Catalog.CSV` — `TOBDCatalogCSVImporter` ingests RFC-4180 CSV with mandatory `did,name,description` columns plus optional `source,verified,ecu_address,decoder` columns. Decoder column accepts an embedded JSON sub-object via standard CSV double-quote escaping. Emits a v1 JSON catalog ready to drop into `catalogs/`. -- `OBD.OEM.Catalog.Loader` — bridges the JSON loader into `TOBDOEMExtensionBase.BuildCatalog`. Each OEM's extension calls `MergeCatalogJSON('.json', DIDs, Routines)` after populating its hard-coded fallback. JSON entries win on DID conflict; missing files leave the hard-coded set untouched (so binaries deployed without the catalog folder still work). -- `catalogs/uds-standard.json` — verified ISO 14229-1 universal F1xx range (31 DIDs + 4 routines, all `verified: true` against the ISO Annex F table). -- `catalogs/obd2-pids.json` — verified ISO 15031-6 / SAE J1979 OBD-II Service 01 PIDs (60+ entries, all verified, full unit conversions for RPM, MAF, fuel trim, oxygen sensors, fuel rate, catalyst temperatures, …). -- `catalogs/{vw,bmw,mercedes,ford,gm,stellantis}.json` — seeded per-OEM catalogs with community-sourced entries (all `verified: false`, with `source` cited per entry: ross-tech-wiki, esys-community, xentry-community, forscan-community, tis2web-public, alfaobd-community, diagbox-public, community-pr). -- All six existing OEM extensions (`OBD.OEM.VW`, `.BMW`, `.Mercedes`, `.Ford`, `.GM`, `.Stellantis`) now merge their JSON catalog + the universal `uds-standard.json` overlay on top of the hard-coded fallback. Per-OEM combined coverage jumps from ~15 hard-coded entries to 60–100+ entries depending on the manufacturer. -- `tools/import-csv/ImportCSV.dpr` — small console tool (`ImportCSV `) that drives `TOBDCatalogCSVImporter` for community catalog contributors who keep their data as CSV. -- `Tests.OEM.Catalog` — 16 test cases covering JSON parsing, every decoder kind (uint/int/ascii/hex/bcd_date/enum/bitmask/seconds), CSV → JSON round-trip, embedded-JSON decoder columns, comment lines, missing-mandatory-column rejection, default-source propagation, verified-flag default. - -### Notes -- This milestone ships the **infrastructure + provenance** for catalog growth, not a full OEM build-out. The `verified: false` entries in the per-OEM catalogs are starter community data and must NOT be trusted for production-critical decisions (flashing, security access). The path to `verified: true` is documented in `docs/CATALOG_FORMAT.md` (cite the OEM spec, or contribute a cross-validating capture in `tests/fixtures/`). -- Future phases of the OEM extension plan (1.2 per-ECU sub-catalogs, 1.3 session negotiation, 1.4 seed-key plugins, 2 DTC catalogs, 3 coding encoders, 4 routine schemas, 5 real-capture test fixtures, 6 multi-bus, 7 ODX importer) are tracked in `docs/OEM_EXTENSION_PLAN.md` as separate future milestones. - -## [3.2.0] - 2026-05-06 — Production Crypto + OEM Coverage (Proposal C) - -### Added -- `TOBDBCryptVerifier` (`src/Services/OBD.ECU.Signature.BCrypt.pas`) — production-grade firmware verification via Windows CNG (BCrypt). Handles **RSA-PKCS1-SHA256** and **ECDSA-P256-SHA256** out of the box. Imports SubjectPublicKeyInfo DER blobs through `CryptImportPublicKeyInfoEx2`; auto-detects the algorithm from the OID. ECDSA signatures in OpenSSL's ASN.1 DER form are transcoded to the fixed-size R||S the BCrypt API expects. No external DLLs — `crypt32.dll` and `bcrypt.dll` ship with every supported Windows version. -- `TOBDOpenSSLVerifier` (`src/Services/OBD.ECU.Signature.OpenSSL.pas`) — alternative verifier for shops that already ship OpenSSL or need RSA-PSS / non-stock curves. Dynamically loads `libcrypto-3.dll` (or v1.1 fallback) so projects without OpenSSL on the path don't fail to start; throws `EOBDOpenSSLNotAvailable` on construction when the library is missing. -- `IOBDHSMSession` + `TOBDHSMVerifier` (`src/Services/OBD.ECU.Signature.HSM.pas`) — contract for plug-in HSM-backed verification (PKCS#11, AWS CloudHSM, Azure Key Vault). Concrete sessions live in caller code; the framework exposes them as plain `IFirmwareSignatureVerifier` instances that slot into `TOBDECUFlashing` like any other. -- `TOBDNonceVault` (`src/Utilities/OBD.Security.Nonce.pas`) — anti-replay primitive: cryptographically-random nonces (Windows `RtlGenRandom`), TTL-based expiry, single-use redemption. Distinguishes unknown / expired / replay error states so audit logs can record which case fired. -- Four new OEM extensions: `OBD.OEM.Mercedes` (XENTRY-style — covers WDB / WDC / WDD / WDF / WD3 / WD4 / 4JG WMIs), `OBD.OEM.Ford` (covers 1FA-1FT, 2FA, 2FT, 3FA, 3FT, 1LN, 5LM, 1MR, 6FP, WF0), `OBD.OEM.GM` (Global B / GMLAN — covers 1G1, 1G2, 1G4, 1G6, 1G8, 1GC, 1GT, 2G1, 2GT, 3G1, 3GT, 5GR, 6G1), `OBD.OEM.Stellantis` (FCA + PSA — covers 1C3-1C6, 2C3-3C4, 1D4-3D4, 1J4/1J8, 1RR, ZFA-ZFC, 9BD, ZAR, ZAM, VF3, VF7, VR1, W0L, VXR). Each ships an initial DID + RoutineControl catalog and per-DID decoders for VIN, mileage, battery voltage, programming dates / status. **These are starter catalogs** — real production coverage is documented in [`docs/OEM_EXTENSION_PLAN.md`](docs/OEM_EXTENSION_PLAN.md). -- `examples/ecuflashing_console/` — end-to-end console example that loads firmware + signature + DER public key from disk, constructs `TOBDBCryptVerifier`, drives `TOBDECUFlashing` through every stage against a simulated ECU. Shows exactly which four callbacks need to be replaced with real OEM UDS sequences. -- `tests/fixtures/` — real RSA-2048 + ECDSA-P256 test vectors generated with OpenSSL 3.0 (DER public keys, signatures of "hello world"). Embedded in the test runner via `test-fixtures.inc` so the BCrypt + OpenSSL verifiers are exercised against actual cryptographic operations on the Windows runner. -- `Tests.ECU.Signature.BCrypt`, `Tests.ECU.Signature.OpenSSL`, `Tests.Security.Nonce`, `Tests.OEM.Extra` — 25+ new test cases covering verify-pass, tampered-firmware, tampered-signature, empty-input rejection (verifiers); issue / redeem / replay-rejection / expiry / reset (nonce); VIN routing and DID decoding for the four new OEMs. -- `docs/OEM_EXTENSION_PLAN.md` — concrete plan for taking the OEM catalogs from "starter" to "production-grade" via 7 phases (DID scale-up, per-ECU sub-catalogs, session negotiation, seed-key plugins, DTC catalogs, coding encoders, real-capture test fixtures, ODX/CSV import tooling). - -## [3.1.0] - 2026-05-06 — FMX Component Completion (Proposal A) - -### Added -- Framework-neutral renderer for every visual component, in `src/CustomControls/`: - - `OBD.Render.Tachometer`, `OBD.Render.TrendGraph`, `OBD.Render.DtcList`, - - `OBD.Render.Terminal`, `OBD.Render.Knob`, `OBD.Render.SegmentedSwitch`, - - `OBD.Render.LED`. Each ships a flat `TOBDRenderState` record and a `Render(Canvas, State)` function. VCL and FMX bindings both marshal their state into the record and delegate. -- FMX bindings, in `src/Components/`: - - `OBD.Tachometer.FMX`, `OBD.TrendGraph.FMX`, `OBD.DtcList.FMX`, - - `OBD.Terminal.FMX`, `OBD.Knob.FMX`, `OBD.SegmentedSwitch.FMX`, - - `OBD.LED.FMX`. Each extends `TSkPaintBox`, mirrors the VCL property surface with `TAlphaColor` colours, self-drives transitions via `TStopwatch` where applicable, handles FMX-style mouse / wheel / focus events. -- `Packages/RunTime.FMX.dpk` updated to ship every renderer + FMX binding. -- `Packages/DesignTime.FMX.dpk` (new) — IDE registration via `OBD.CustomControl.Register.FMX`. Drops every FMX component on the same "ERDesigns OBD" palette page as the VCL set. -- `examples/mobile_dashboard/` — FMX dashboard exercising all eight FMX components (Tachometer, three LinearGauges, TrendGraph with two series, DtcList, Terminal, two LEDs, SegmentedSwitch, Knob). Built entirely in code; runs on Win32, Win64, macOS, iOS, Android. - -### Changed -- Every VCL component listed above now marshals its `PaintSkia` state into the matching renderer record and delegates. Public API unchanged. Private `DrawSeries` / `DrawGrid` / `DrawLegend` (TrendGraph), `DrawRow` / `ColorForSeverity` / `StatusLabel` (DtcList), and `ColorForDirection` / `PrefixForDirection` (Terminal) helpers removed — their logic moved into the renderer. - -### Notes -- VCL `TOBDLed` keeps its existing snapshot-cache path because it integrates with VCL `TStyleManager`. The new FMX `TOBDLedFMX` uses the renderer directly. Unifying the two paths is a v3.2+ task that needs a platform-neutral style abstraction. - -## [3.0.0] - 2026-05-06 — FMX & OEM extensions - -### Added -- `OBD.Render.LinearGauge` — framework-neutral Skia renderer that the VCL `TOBDLinearGauge` and the new FMX `TOBDLinearGaugeFMX` both delegate to. Establishes the renderer-extract pattern that the remaining v3.1+ FMX bindings will follow. -- `TOBDLinearGaugeFMX` (`src/Components/OBD.LinearGauge.FMX.pas`) — first FMX visual component. Extends `TSkPaintBox`, mirrors the VCL property surface with `TAlphaColor` colours, drives its own ease-out-cubic value transition via `TStopwatch`. Lives in the new `Packages/RunTime.FMX.dpk` so VCL builds aren't dragged into FMX dependencies. -- `IOBDOEMExtension` + `TOBDOEMRegistry` + `TOBDOEMExtensionBase` (`src/Services/OBD.OEM.pas`) — extension framework for manufacturer-specific UDS coverage. Contract covers manufacturer key + display name, applicability check (typically by VIN WMI), DID + RoutineControl catalogs, per-DID decode. Registry is thread-safe and lookups are by VIN, by manufacturer key, or by enumerating `All`. -- `OBD.OEM.Helpers` — `DID()` and `Routine()` factory helpers for compact `[DID($1234, 'name', 'desc'), …]` literals when building catalogs. -- `OBD.OEM.VW` — reference VW Group extension (matches WVW / WV1 / WV2 / WAU / TRU / TMB / VSS WMIs). Ships a starter catalog of common UDS DIDs + routines and decodes `battery_voltage`, `vehicle_speed`, and `vin`. -- `OBD.OEM.BMW` — reference BMW extension (WBA / WBS / WBY / WMW / 5UX / 4US WMIs). Catalog includes `i_stufe` and `fa_assembly` DIDs (the inputs to E-Sys-style coding) and decodes `mileage`, `battery_voltage`, `vin`. -- `examples/oem_demo/` — console example: take a VIN, list the matching extension's catalog, optionally decode a DID payload from hex. -- `Tests.OEM` — 12 tests covering registry register/unregister/find, VIN matching for VW + BMW, idempotent register, unknown-DID fallback, all the implemented DID decoders. - -### Changed -- `TOBDLinearGauge.PaintSkia` now marshals its state into a `TOBDLinearGaugeRenderState` and calls `OBD.Render.LinearGauge.RenderLinearGauge`. Behaviour and published API unchanged; the rendering code moved. - -## [2.5.0] - 2026-05-06 — Hardening & ECU - -### Added -- `TOBDECUFlashing` (`src/Services/OBD.ECU.Flashing.pas`) — first-class flashing coordinator. Runs the strict pre-check → signature → snapshot → erase → write → finalise → verify pipeline; OEM-specific I/O plugs in via `OnHealthCheck` / `OnSnapshot` / `OnWriteChunk` / `OnFinalise` / `OnVerifyEcu`. Snapshot persists to `BackupPath`; `BlockSize` chunks the stream; `RequestCancel` honoured at every stage boundary; automatic rollback re-writes the snapshot on write/finalise/verify failure. Stage / progress / completed / failed events expose UI hooks. -- `IFirmwareSignatureVerifier` + `TOBDSha256SignatureVerifier` (constant-time hash compare) + `TOBDPermissiveSignatureVerifier` (development only) in `src/Services/OBD.ECU.Signature.pas`. `ComputeSha256` helper for one-liners. -- `TOBDSecureSettings` (`src/Utilities/OBD.SecureSettings.pas`) — DPAPI-encrypted INI storage. Wraps `CryptProtectData` / `CryptUnprotectData` (current-user scope). Plaintext never touches disk; failed decryption falls back to caller-supplied default rather than raising. Standalone `DPAPIEncrypt` / `DPAPIDecrypt` exported for ad-hoc byte-level use. -- `TOBDAuditRecorder` (`src/Utilities/OBD.Audit.pas`) — structured audit events routed through the configured `TOBDLogger` with `SourceTag = "audit"` and JSON-serialised payload (actor / action / resource / outcome / detail). Outcomes map onto log levels: success → Info, failure → Error, denied → Warning. -- `TOBDAttemptCounter` (`src/Utilities/OBD.Security.AttemptCounter.pas`) — per-identity exponential back-off lockout. `BaseLockoutSeconds` doubles per failure beyond `FreeAttempts`, capped at `MaxLockoutSeconds`. Thread-safe; identities don't interfere. -- 32 new tests across `Tests.ECU.Signature`, `Tests.ECU.Flashing`, `Tests.SecureSettings`, `Tests.Audit`, `Tests.Security.AttemptCounter` exercising real DPAPI round-trips, golden SHA-256 vectors, every flashing-failure path with rollback verification, lockout math, and JSON audit shape. - -## [2.4.0] - 2026-05-06 — Distribution & Docs - -### Added -- `Packages/getit.json` — GetIt package manifest (name, version, runtime + design-time paths, examples, doc references). Submission to Embarcadero is the maintainer-side follow-up. -- `.github/workflows/docs.yml` — automated PasDoc API-reference build + GitHub Pages deploy on every push to main and every tag. `docs/pasdoc.cfg` carries the PasDoc configuration. -- `docs/ARCHITECTURE.md` — full architectural overview with Mermaid diagrams (layered model, per-PID sequence diagram, connection / adapter / protocol / service / async / logging / UI maps). -- `docs/PROTOCOLS.md` — protocol-stack reference: OBD-II transports, ISO-TP framing, SAE J1979 services, UDS/KWP2000/DoIP/J1939/FlexRay/LIN/MOST/tachograph, adapter dialect notes. -- `docs/TROUBLESHOOTING.md` — symptom-keyed FAQ across connection, protocol, components, ECU flashing, async, logging, build/packaging. -- `docs/PERFORMANCE.md` — headline numbers, per-component tuning levers, anti-patterns, profiling tooling, regression-reporting guidance. - -## [2.3.0] - 2026-05-06 — Async & Logging - -### Added -- `IOBDFuture` / `IOBDPromise` / `IOBDCancellationToken` async primitives in `src/Utilities/OBD.Async.pas`. `TEvent`-backed `Await` with timeout, `OnComplete` handlers (synchronous when already settled), idempotent settlement. -- `TOBDConnectionAsync` (`src/Connection/OBD.Connection.Async.pas`) — wraps `IOBDConnection` with `SendAsync` / `ATAsync` / `OBDAsync` returning `IOBDFuture`. Resolves on the configured terminator (default '>' ELM327 prompt). Per-request timeout + shared cancellation tokens. -- `TOBDProtocolAsync` (`src/Protocol/OBD.Protocol.Async.pas`) — `RequestAsync(Service, PID)` / `RequestRawAsync(HexCommand)` return parsed `TArray`; `PollAsync(PIDs)` chains Service-01 polls sequentially. -- `IOBDLogSink` + bundled sinks (`TFileRotationSink`, `TDailyRotationSink`, `TJsonLineSink`, `TConsoleSink`, `TInMemorySink`) in `src/Utilities/OBD.Logger.Sinks.pas`. `TOBDLogger` gains `RegisterSink` / `UnregisterSink` / `SinkCount` / `SourceTag`; legacy `OnLog` event and existing file-write are unchanged. -- `TOBDLogViewer` (`src/Components/OBD.LogViewer.pas`) — `TOBDTerminal` subclass that implements `IOBDLogSink`, so any logger can render directly into the in-app conversation viewer with severity-coloured rows. -- `TOBDRecorder` + `TOBDReplayer` (`src/Services/OBD.Service.Recorder.pas`) — capture and replay `.obdlog` files with elapsed-millisecond timing + direction tagging. `examples/replay/` ships a console replayer with configurable speed multiplier; documented in `examples/replay/README.md`. - -## [2.2.0] - 2026-05-06 — Components - -### Added -- `TOBDLinearGauge` (`src/Components/OBD.LinearGauge.pas`) — horizontal/vertical bar gauge with gradient fill, normal/reversed direction, optional caption + units + value text, eased `Value` transitions. Registered on the IDE palette and exercised by `Tests.Components.Smoke`. -- `TOBDTachometer` (`src/Components/OBD.Tachometer.pas`) — analog RPM gauge with redline arc, shift light, configurable tick intervals, eased Value transitions. Public `ShiftLightActive` for driving external indicators. -- `TOBDTrendGraph` (`src/Components/OBD.TrendGraph.pas`) — multi-series live trend graph, per-series ring buffer with overwrite-oldest semantics, per-series Min/Max range so unlike-unit series share a single plot, optional grid + legend + border. `AddSeries` / `PushValue` / `ClearSamples` / `RemoveSeries` API; `MaxSamples` resizes preserving the most recent samples. -- `TOBDDtcList` (`src/Components/OBD.DtcList.pas`) — virtualised diagnostic-code list. Severity stripes (info/warning/critical), status badges (active/pending/permanent/history), alternate-row striping, mouse-wheel scrolling with thumb scroll-bar, single-click + double-click events. `EnsureVisible(Index)` for programmatic scroll. -- `TOBDTerminal` (`src/Components/OBD.Terminal.pas`) — live monospace conversation viewer for ELM327 / protocol traffic. Four entry points (`LogSent`, `LogReceived`, `LogInfo`, `LogError`), per-direction colours, optional timestamps, follow-tail auto-scroll, MaxLines eviction. -- `TOBDKnob` (`src/Components/OBD.Knob.pas`) — rotary input. Drag-to-rotate, mouse-wheel stepping, snap-to-step, configurable arc start + sweep, `OnChange` event. -- `TOBDSegmentedSwitch` (`src/Components/OBD.SegmentedSwitch.pas`) — iOS-style multi-state toggle backed by a `TStringList` of segments. Click to select, `OnChange` event, SelectedIndex clamping on segment changes. -- `TOBDTheme` (`src/CustomControls/OBD.Theme.pas`) — central palette with role-named slots (chrome / plot / accent / severity / selection). Explicit `Apply(component)` overloads for every shipped v2.2 component plus an `ApplyToTree(Form)` helper that walks the entire control tree. Two factory themes ship out of the box: `TOBDTheme.Dark` and `TOBDTheme.Light`. -- `docs/COMPONENT_AUTHORING.md` — canonical pattern for adding a new visual component, covering base class, file/unit naming, skeleton, property-setter rules, animation pattern, Skia drawing helpers, theme integration, package registration, smoke-test minimum, anti-patterns, and a copy-paste PR checklist. - -### Changed -- Removed `OBD.CustomControl.AnimationManager.pas` and the `IOBDAnimatable` interface contract on `TOBDCircularGauge` / `TOBDMatrixDisplay`. Each component now interpolates animated state directly inside `PaintSkia` using its existing `TStopwatch`; the inherited `TOBDCustomControl` timer keeps firing `Invalidate` at `FramesPerSecond` Hz so every paint observes the current state. - -## [2.1.0] - 2026-05-06 — Foundation - -### Added -- DUnitX test harness (`tests/Tests.dpr`) with TestInsight and console-mode runners. -- Smoke fixture (`Tests.Smoke.pas`) that proves the rig is alive. -- VIN decoder golden tests (`Tests.VIN.Decoder.pas`): SAE J853 + Honda goldens, ISO-3779 check-digit calculation, validate-acceptance/rejection cases, round-trip property test, WMI/VDS/VIS extraction, model-year decoding. -- Radio code universal smoke tests (`Tests.RadioCode.Smoke.pas`): all 42 calculators are instantiated and asserted to have non-empty descriptions, deterministic `Calculate`, and empty-input rejection. -- Becker 4-digit golden tests (`Tests.RadioCode.Becker4.pas`): hard goldens drawn from the published lookup table, plus determinism, invalid-input rejection, whitespace trimming. -- Service 01–0A encoder tests (`Tests.Service.Encoders.pas`): every service produces the expected hex frame with and without trailing data bytes. -- Service response decoder tests (`Tests.Service.Decoders.pas`): positive / negative / too-short / Service-03 dispatching, plus PID decoders for percentage, temperature, fuel trim, fuel pressure, RPM, timing advance, and MAF — asserted against SAE J1979 formulas. -- GitHub Actions CI workflow (`.github/workflows/ci.yml`) with a static-checks job (mangled signatures, stray `end.`, leftover `Redraw;` / back-buffer fields, line endings) and a self-hosted-runner build/test job (currently gated off; flip `if: false` once a Delphi runner is registered). -- `docs/ROADMAP.md` — staged improvement plan (v2.1 → v3.0). -- `CHANGELOG.md` — this file. -- ELM327 adapter tests (`Tests.Adapter.ELM327.pas`): `FormatATCommand` for no-param, single-string, and parameterised commands; param-count mismatch raises `TATCommandException`; `TELM327Detector.GetChipTypeDescription` non-empty + expected substring per chip type. -- ISO-TP framing tests (`Tests.Protocol.IsoTp.pas`): SF/FF/CF parsing, flow-control rejection, odd-length / too-short rejection, multi-frame VIN reassembly via `TISO_15765_4_11BIT_500K_OBDProtocol`, out-of-order CF sorting. -- **BLE transport** (`src/Connection/OBD.Connection.BLE.pas`): GATT-over-BLE OBD-II support targeting the FFE0/FFE1 ELM327 BLE clone family (Vgate iCar Pro BLE, Veepeak BLE+, OBDLink CX) with override hooks for Nordic UART or vendor-specific service/characteristic UUIDs. New `ctBluetoothLE` connection type plugs into `TOBDConnectionComponent` via published `BluetoothLEManager` / `BluetoothLEAddress` / `BluetoothLEServiceUUID` / `BluetoothLEWriteCharUUID` / `BluetoothLENotifyCharUUID` properties. - -### Changed -- `src/CustomControls/OBD.CustomControl.pas` — restored to baseline (no double-buffer, no `InvalidateBackBuffer`, simple `Draw` → `PaintSkia`). -- `src/CustomControls/OBD.CustomControl.AnimationManager.pas` — default and cap lowered from 60 FPS to 30 FPS. -- `src/Components/OBD.CircularGauge.pas`, `OBD.MatrixDisplay.pas`, `OBD.Touch.Header.pas`, `OBD.Touch.Subheader.pas`, `OBD.Touch.Statusbar.pas`, `OBD.LED.pas` — all `Redraw;` calls replaced with `Invalidate;`; `Redraw` methods deleted; `InvalidateBackground` design-time guards removed; baseline `class constructor`/`class destructor` and `Repaint` overrides restored where the spiral had stripped them. -- LED component reverted to baseline (the `InvalidateColors` lazy-load tier was over-engineered). - -### Fixed -- 42 corrupted `Parse` signatures in `OBD.Response.Decoders.pas`. Every `TOBD*Decoder.Parse` had a botched search/replace that produced lines like `function TOBDfunction TOBDErrorDecoder.Parse(Data: TBytes;Decoder.Parse(...)`. All repaired to match the `IOBD*Decoder` interface declarations. -- Stray mid-file `end.` terminators in `OBD.MatrixDisplay.pas` and `OBD.Touch.Header.pas`. -- Component back-buffer dimension check in `OBD.CustomControl.pas` — was guarding `FBackBuffer.Width` access without first checking `Assigned(FBackBuffer)` (since removed entirely as part of the back-buffer revert). - -[Unreleased]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v3.3.0...HEAD -[3.3.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v3.2.0...v3.3.0 -[3.2.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v3.1.0...v3.2.0 -[3.1.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v3.0.0...v3.1.0 -[3.0.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v2.5.0...v3.0.0 -[2.5.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v2.4.0...v2.5.0 -[2.4.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v2.3.0...v2.4.0 -[2.3.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v2.2.0...v2.3.0 -[2.2.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v2.1.0...v2.2.0 -[2.1.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v2.0.0...v2.1.0 +See [CHANGELOG/v3.md](CHANGELOG/v3.md) for the full v3.79 entry. diff --git a/CHANGELOG/v2.md b/CHANGELOG/v2.md new file mode 100644 index 00000000..86413db6 --- /dev/null +++ b/CHANGELOG/v2.md @@ -0,0 +1,93 @@ +# Changelog — v2.x + +Releases from the v2.1 Foundation milestone through v2.5 Hardening & ECU. +Newer entries are in [v3.md](v3.md). The top-level +[../CHANGELOG.md](../CHANGELOG.md) indexes both. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [2.5.0] - 2026-05-06 — Hardening & ECU + +### Added +- `TOBDECUFlashing` (`src/Services/OBD.ECU.Flashing.pas`) — first-class flashing coordinator. Runs the strict pre-check → signature → snapshot → erase → write → finalise → verify pipeline; OEM-specific I/O plugs in via `OnHealthCheck` / `OnSnapshot` / `OnWriteChunk` / `OnFinalise` / `OnVerifyEcu`. Snapshot persists to `BackupPath`; `BlockSize` chunks the stream; `RequestCancel` honoured at every stage boundary; automatic rollback re-writes the snapshot on write/finalise/verify failure. Stage / progress / completed / failed events expose UI hooks. +- `IFirmwareSignatureVerifier` + `TOBDSha256SignatureVerifier` (constant-time hash compare) + `TOBDPermissiveSignatureVerifier` (development only) in `src/Services/OBD.ECU.Signature.pas`. `ComputeSha256` helper for one-liners. +- `TOBDSecureSettings` (`src/Utilities/OBD.SecureSettings.pas`) — DPAPI-encrypted INI storage. Wraps `CryptProtectData` / `CryptUnprotectData` (current-user scope). Plaintext never touches disk; failed decryption falls back to caller-supplied default rather than raising. Standalone `DPAPIEncrypt` / `DPAPIDecrypt` exported for ad-hoc byte-level use. +- `TOBDAuditRecorder` (`src/Utilities/OBD.Audit.pas`) — structured audit events routed through the configured `TOBDLogger` with `SourceTag = "audit"` and JSON-serialised payload (actor / action / resource / outcome / detail). Outcomes map onto log levels: success → Info, failure → Error, denied → Warning. +- `TOBDAttemptCounter` (`src/Utilities/OBD.Security.AttemptCounter.pas`) — per-identity exponential back-off lockout. `BaseLockoutSeconds` doubles per failure beyond `FreeAttempts`, capped at `MaxLockoutSeconds`. Thread-safe; identities don't interfere. +- 32 new tests across `Tests.ECU.Signature`, `Tests.ECU.Flashing`, `Tests.SecureSettings`, `Tests.Audit`, `Tests.Security.AttemptCounter` exercising real DPAPI round-trips, golden SHA-256 vectors, every flashing-failure path with rollback verification, lockout math, and JSON audit shape. + +## [2.4.0] - 2026-05-06 — Distribution & Docs + +### Added +- `Packages/getit.json` — GetIt package manifest (name, version, runtime + design-time paths, examples, doc references). Submission to Embarcadero is the maintainer-side follow-up. +- `.github/workflows/docs.yml` — automated PasDoc API-reference build + GitHub Pages deploy on every push to main and every tag. `docs/pasdoc.cfg` carries the PasDoc configuration. +- `docs/ARCHITECTURE.md` — full architectural overview with Mermaid diagrams (layered model, per-PID sequence diagram, connection / adapter / protocol / service / async / logging / UI maps). +- `docs/PROTOCOLS.md` — protocol-stack reference: OBD-II transports, ISO-TP framing, SAE J1979 services, UDS/KWP2000/DoIP/J1939/FlexRay/LIN/MOST/tachograph, adapter dialect notes. +- `docs/TROUBLESHOOTING.md` — symptom-keyed FAQ across connection, protocol, components, ECU flashing, async, logging, build/packaging. +- `docs/PERFORMANCE.md` — headline numbers, per-component tuning levers, anti-patterns, profiling tooling, regression-reporting guidance. + +## [2.3.0] - 2026-05-06 — Async & Logging + +### Added +- `IOBDFuture` / `IOBDPromise` / `IOBDCancellationToken` async primitives in `src/Utilities/OBD.Async.pas`. `TEvent`-backed `Await` with timeout, `OnComplete` handlers (synchronous when already settled), idempotent settlement. +- `TOBDConnectionAsync` (`src/Connection/OBD.Connection.Async.pas`) — wraps `IOBDConnection` with `SendAsync` / `ATAsync` / `OBDAsync` returning `IOBDFuture`. Resolves on the configured terminator (default '>' ELM327 prompt). Per-request timeout + shared cancellation tokens. +- `TOBDProtocolAsync` (`src/Protocol/OBD.Protocol.Async.pas`) — `RequestAsync(Service, PID)` / `RequestRawAsync(HexCommand)` return parsed `TArray`; `PollAsync(PIDs)` chains Service-01 polls sequentially. +- `IOBDLogSink` + bundled sinks (`TFileRotationSink`, `TDailyRotationSink`, `TJsonLineSink`, `TConsoleSink`, `TInMemorySink`) in `src/Utilities/OBD.Logger.Sinks.pas`. `TOBDLogger` gains `RegisterSink` / `UnregisterSink` / `SinkCount` / `SourceTag`; legacy `OnLog` event and existing file-write are unchanged. +- `TOBDLogViewer` (`src/Components/OBD.LogViewer.pas`) — `TOBDTerminal` subclass that implements `IOBDLogSink`, so any logger can render directly into the in-app conversation viewer with severity-coloured rows. +- `TOBDRecorder` + `TOBDReplayer` (`src/Services/OBD.Service.Recorder.pas`) — capture and replay `.obdlog` files with elapsed-millisecond timing + direction tagging. `examples/replay/` ships a console replayer with configurable speed multiplier; documented in `examples/replay/README.md`. + +## [2.2.0] - 2026-05-06 — Components + +### Added +- `TOBDLinearGauge` (`src/Components/OBD.LinearGauge.pas`) — horizontal/vertical bar gauge with gradient fill, normal/reversed direction, optional caption + units + value text, eased `Value` transitions. Registered on the IDE palette and exercised by `Tests.Components.Smoke`. +- `TOBDTachometer` (`src/Components/OBD.Tachometer.pas`) — analog RPM gauge with redline arc, shift light, configurable tick intervals, eased Value transitions. Public `ShiftLightActive` for driving external indicators. +- `TOBDTrendGraph` (`src/Components/OBD.TrendGraph.pas`) — multi-series live trend graph, per-series ring buffer with overwrite-oldest semantics, per-series Min/Max range so unlike-unit series share a single plot, optional grid + legend + border. `AddSeries` / `PushValue` / `ClearSamples` / `RemoveSeries` API; `MaxSamples` resizes preserving the most recent samples. +- `TOBDDtcList` (`src/Components/OBD.DtcList.pas`) — virtualised diagnostic-code list. Severity stripes (info/warning/critical), status badges (active/pending/permanent/history), alternate-row striping, mouse-wheel scrolling with thumb scroll-bar, single-click + double-click events. `EnsureVisible(Index)` for programmatic scroll. +- `TOBDTerminal` (`src/Components/OBD.Terminal.pas`) — live monospace conversation viewer for ELM327 / protocol traffic. Four entry points (`LogSent`, `LogReceived`, `LogInfo`, `LogError`), per-direction colours, optional timestamps, follow-tail auto-scroll, MaxLines eviction. +- `TOBDKnob` (`src/Components/OBD.Knob.pas`) — rotary input. Drag-to-rotate, mouse-wheel stepping, snap-to-step, configurable arc start + sweep, `OnChange` event. +- `TOBDSegmentedSwitch` (`src/Components/OBD.SegmentedSwitch.pas`) — iOS-style multi-state toggle backed by a `TStringList` of segments. Click to select, `OnChange` event, SelectedIndex clamping on segment changes. +- `TOBDTheme` (`src/CustomControls/OBD.Theme.pas`) — central palette with role-named slots (chrome / plot / accent / severity / selection). Explicit `Apply(component)` overloads for every shipped v2.2 component plus an `ApplyToTree(Form)` helper that walks the entire control tree. Two factory themes ship out of the box: `TOBDTheme.Dark` and `TOBDTheme.Light`. +- `docs/COMPONENT_AUTHORING.md` — canonical pattern for adding a new visual component, covering base class, file/unit naming, skeleton, property-setter rules, animation pattern, Skia drawing helpers, theme integration, package registration, smoke-test minimum, anti-patterns, and a copy-paste PR checklist. + +### Changed +- Removed `OBD.CustomControl.AnimationManager.pas` and the `IOBDAnimatable` interface contract on `TOBDCircularGauge` / `TOBDMatrixDisplay`. Each component now interpolates animated state directly inside `PaintSkia` using its existing `TStopwatch`; the inherited `TOBDCustomControl` timer keeps firing `Invalidate` at `FramesPerSecond` Hz so every paint observes the current state. + +## [2.1.0] - 2026-05-06 — Foundation + +### Added +- DUnitX test harness (`tests/Tests.dpr`) with TestInsight and console-mode runners. +- Smoke fixture (`Tests.Smoke.pas`) that proves the rig is alive. +- VIN decoder golden tests (`Tests.VIN.Decoder.pas`): SAE J853 + Honda goldens, ISO-3779 check-digit calculation, validate-acceptance/rejection cases, round-trip property test, WMI/VDS/VIS extraction, model-year decoding. +- Radio code universal smoke tests (`Tests.RadioCode.Smoke.pas`): all 42 calculators are instantiated and asserted to have non-empty descriptions, deterministic `Calculate`, and empty-input rejection. +- Becker 4-digit golden tests (`Tests.RadioCode.Becker4.pas`): hard goldens drawn from the published lookup table, plus determinism, invalid-input rejection, whitespace trimming. +- Service 01–0A encoder tests (`Tests.Service.Encoders.pas`): every service produces the expected hex frame with and without trailing data bytes. +- Service response decoder tests (`Tests.Service.Decoders.pas`): positive / negative / too-short / Service-03 dispatching, plus PID decoders for percentage, temperature, fuel trim, fuel pressure, RPM, timing advance, and MAF — asserted against SAE J1979 formulas. +- GitHub Actions CI workflow (`.github/workflows/ci.yml`) with a static-checks job (mangled signatures, stray `end.`, leftover `Redraw;` / back-buffer fields, line endings) and a self-hosted-runner build/test job (currently gated off; flip `if: false` once a Delphi runner is registered). +- `docs/ROADMAP.md` — staged improvement plan (v2.1 → v3.0). +- `CHANGELOG.md` — this file. +- ELM327 adapter tests (`Tests.Adapter.ELM327.pas`): `FormatATCommand` for no-param, single-string, and parameterised commands; param-count mismatch raises `TATCommandException`; `TELM327Detector.GetChipTypeDescription` non-empty + expected substring per chip type. +- ISO-TP framing tests (`Tests.Protocol.IsoTp.pas`): SF/FF/CF parsing, flow-control rejection, odd-length / too-short rejection, multi-frame VIN reassembly via `TISO_15765_4_11BIT_500K_OBDProtocol`, out-of-order CF sorting. +- **BLE transport** (`src/Connection/OBD.Connection.BLE.pas`): GATT-over-BLE OBD-II support targeting the FFE0/FFE1 ELM327 BLE clone family (Vgate iCar Pro BLE, Veepeak BLE+, OBDLink CX) with override hooks for Nordic UART or vendor-specific service/characteristic UUIDs. New `ctBluetoothLE` connection type plugs into `TOBDConnectionComponent` via published `BluetoothLEManager` / `BluetoothLEAddress` / `BluetoothLEServiceUUID` / `BluetoothLEWriteCharUUID` / `BluetoothLENotifyCharUUID` properties. + +### Changed +- `src/CustomControls/OBD.CustomControl.pas` — restored to baseline (no double-buffer, no `InvalidateBackBuffer`, simple `Draw` → `PaintSkia`). +- `src/CustomControls/OBD.CustomControl.AnimationManager.pas` — default and cap lowered from 60 FPS to 30 FPS. +- `src/Components/OBD.CircularGauge.pas`, `OBD.MatrixDisplay.pas`, `OBD.Touch.Header.pas`, `OBD.Touch.Subheader.pas`, `OBD.Touch.Statusbar.pas`, `OBD.LED.pas` — all `Redraw;` calls replaced with `Invalidate;`; `Redraw` methods deleted; `InvalidateBackground` design-time guards removed; baseline `class constructor`/`class destructor` and `Repaint` overrides restored where the spiral had stripped them. +- LED component reverted to baseline (the `InvalidateColors` lazy-load tier was over-engineered). + +### Fixed +- 42 corrupted `Parse` signatures in `OBD.Response.Decoders.pas`. Every `TOBD*Decoder.Parse` had a botched search/replace that produced lines like `function TOBDfunction TOBDErrorDecoder.Parse(Data: TBytes;Decoder.Parse(...)`. All repaired to match the `IOBD*Decoder` interface declarations. +- Stray mid-file `end.` terminators in `OBD.MatrixDisplay.pas` and `OBD.Touch.Header.pas`. +- Component back-buffer dimension check in `OBD.CustomControl.pas` — was guarding `FBackBuffer.Width` access without first checking `Assigned(FBackBuffer)` (since removed entirely as part of the back-buffer revert). + +[Unreleased]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v3.3.0...HEAD +[3.3.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v3.2.0...v3.3.0 +[3.2.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v3.1.0...v3.2.0 +[3.1.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v3.0.0...v3.1.0 +[3.0.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v2.5.0...v3.0.0 +[2.5.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v2.4.0...v2.5.0 +[2.4.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v2.3.0...v2.4.0 +[2.3.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v2.2.0...v2.3.0 +[2.2.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v2.1.0...v2.2.0 +[2.1.0]: https://github.com/erdesigns-eu/Delphi-OBD/compare/v2.0.0...v2.1.0 diff --git a/CHANGELOG/v3.md b/CHANGELOG/v3.md new file mode 100644 index 00000000..451b98ec --- /dev/null +++ b/CHANGELOG/v3.md @@ -0,0 +1,3206 @@ +# Changelog — v3.x + +Releases tagged 3.0.0 and later. Older entries live in +[v2.md](v2.md). The top-level [../CHANGELOG.md](../CHANGELOG.md) +indexes both. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Added — v3.83 in progress + +- **Per-OEM drive-cycle resolvers** (`OBD.DriveCycle.Resolvers`) — populates `RegisterDriveCycleResolver` for `'vw'`, `'bmw'`, `'mercedes'`, `'ford'`, `'toyota'` with factory-published cycles. VW SSP 388 + 605 (catalyst, EVAP fuel-level + ambient gates, oxygen sensor closed-loop, EGR/VVT decel cycles); BMW TIS (catalyst with mandatory partial-throttle decels, cold-start EVAP rules, secondary-air-pump post-start cycle); Mercedes WIS (top-gear catalyst, fuel-1/4–3/4 EVAP, diesel PMFilter regen window); Ford TSB (OD-locked catalyst with AC/defrost off, coolant-within-6°C-of-ambient EVAP, 4-decel EGR/VVT); Toyota Repair Manual (8-hour-soak EVAP, light-off-temperature heated catalyst, 60-second O2 heater). Per-monitor steps that the OEM doesn't override return an empty record so `BuildDriveCycle` falls back to the ISO 15031-7 generic step automatically. Tests cover each OEM's catalyst step embeds the right reference (`SSP`, `TIS`, `WIS`, `TSB`, `RM`), unknown monitor falls through to generic, unregistered OEM falls through to generic, VW EVAP cites the 25–75% fuel-level rule, Ford EVAP requires cold start, Toyota EVAP requires the 8-hour soak. +- **OEM session helper** (`OBD.OEM.SessionHelper`) — `TOBDOEMSessionHelper.RunServiceRoutine(Routine, Callbacks)` wraps a `TOBDServiceRoutine` (v3.81/A1) into one call that opens the required diagnostic session, optionally consults the voltage gate (v3.80/4.6) when `Routine.Safety = srsBatteryMin12V5`, sends the UDS 0x31 RoutineControl frame, optionally reads the result, and always closes the session. Returns `TOBDRoutineExecutionResult` with `Success`, `AbortStage` (`reseSessionOpen` / `reseVoltageGate` / `reseRoutineStart` / `reseRoutineWait` / `reseResultRead` / `reseSessionClose`), `NRC` byte, `MeasuredVolts`, `ResultBytes`. NRC failures are annotated via the v3.81/A6 catalog so error messages embed `(SAD: securityAccessDenied)`-style descriptions. Callback-driven — no hard coupling to `TOBDDiagSession`; production wires each callback to the matching method, tests inject lambdas. Tests cover the happy path, session-open NRC, routine-start NRC, result-read NRC, voltage-gate fail, voltage gate not consulted for non-battery routines, voltage required but reader missing, session always closed even on failure, callback-contract violations raise. + +### Added — v3.82 (shipped) + +- **Toyota / Lexus smart-key learning framing** (`OBD.OEM.KeyAdaptation.Toyota`) — request envelope (17-byte VIN + Mode + MasterKeyPresent flag + length-prefixed PIN) + response envelope (Mode + Success + KeyCount + 4-byte AddedKeyId). Validates that PIN is supplied when no master key is present; PIN length capped at 16. `FindToyotaPlatform` covers Auris ZRE182 + Camry ASV50 (master-key procedure), Lexus NX AGZ10 + RAV4 MXUA70 (PIN), Yaris MXPA10 (certificate-locked); unknown platforms default to `tpaCertificateRequired`. Tests cover both master-key + PIN round-trips, PIN-required-without-master-key, PIN-too-long rejection, response round-trip, bad-AddedKeyId rejection, platform lookups + unknown fallback. +- **Ford PATS framing** (`OBD.OEM.KeyAdaptation.Ford`) — initialise / add-key / status request envelope (17-byte VIN + Operation + ProgrammerPresentByte) + status envelope (KeyCount + LockoutActive + SecondsRemaining + PinCodePresent). `FindFordPlatform` covers F-150 P552 + Fusion CD391 + Focus C520 (`fpaOpen`), Ranger P702 (`fpaPinRequired`), Mustang Mach-E CD542 + F-150 Lightning P708 (`fpaGatewayLocked`); unknowns default to `fpaGatewayLocked`. Tests cover request + status round-trip, bad-VIN rejection, bad-length decode rejections, platform lookups + unknown-is-locked default. +- **HMG smart-key registration framing** (`OBD.OEM.KeyAdaptation.HMG`) — request envelope (17-byte VIN + Mode (AddKey/EraseAll/ReadCount) + length-prefixed 4–6 digit PIN + KeyIndex 0..7) + response envelope (Mode + Success + KeyCount + StatusCode). `FindHMGPlatform` covers i20 RB + Elantra LD + Sonata JF + Stonic QS (`hpaOpenWithPIN`), E-GMP (`hpaGatewayLockedPostMY2020`), Genesis G80 (`hpaCertificateRequired`). Tests cover request + response round-trip, bad-VIN / bad-PIN-length / bad-key-index rejections, decode-bad-length rejection, platform lookups + unknown is certificate-required, E-GMP gateway-locked. +- **Mercedes SCN coding flow** (`OBD.OEM.SCN.Mercedes`) — request/response envelopes for the XENTRY / Vediamo SCN (Software Calibration Number) flow: `TMBSCNVersionRequest` (17-byte VIN + uint16 ECUId), `TMBSCNVersionResponse` (current SCN + HW/SW part numbers), `TMBSCNCodingRequest` (VIN + ECUId + length-prefixed Variant + length-prefixed AccessoryList), `TMBSCNCodingResponse` (NewSCN + ServerSignature). `IMBSCNSolver` interface decouples the central-server lookup; `TMBSCNSolverNotAvailable` is the fail-closed default. Tests cover version-request round-trip + length validation, coding-request round-trip + bad-VIN rejection, coding-response round-trip + truncation rejection, default-solver fails closed for both fetch and coding. +- **VAG Component Protection framing** (`OBD.OEM.ComponentProtection.VAG`) — challenge envelope (ECUType + length-prefixed ComponentSerial + 17-byte VIN + length-prefixed Nonce) and activation envelope (length-prefixed Response + length-prefixed Signature) for the SVM-authenticated component-replacement flow used on radios, clusters, AC/HVAC modules. `IVAGCPSolver` interface plugs in the dealer-portal client; `TVAGCPSolverNotAvailable` is the fail-closed default that raises `EOBDVAGCPNoSolver`. Tests cover request + response round-trip, bad-VIN length rejection, truncated-serial / bad-VIN-length / truncated-response rejections, default-solver-fails-closed. +- **BMW key adaptation framing** (`OBD.OEM.KeyAdaptation.BMW`) — encoders/decoders for the three immobiliser generations: EWS (16-byte slot, 0..9), CAS (16-byte slot with 4-byte RemoteId + KM-thousands, 0..9), FEM-BDC (32-byte slot with PersonalSettingsBank 1..4, 7-byte DigitalKeySerial, 32-bit UsageCounter + LastKMReading, 0..7). `ValidateSlotIndex` enforces per-generation slot bounds. `IBMWKeyChallengeSolver` interface decouples the proprietary ISN derivation and EWS/CAS challenge-response encryption (tracked in DATA_GAPS). Tests cover slot validation per generation, full round-trip per generation, bad-slot rejection, bad PersonalSettingsBank rejection, wrong-length decode rejection, DigitalKeySerial length enforcement. +- **CalID / CVN sweep** (`OBD.Service09.Calibration`) — Service 09 PIDs $04 (CalibrationID, ASCII) + $06 (CVN, big-endian uint32). `EncodeCalIDRequest` / `EncodeCVNRequest` build the 2-byte requests; `DecodeCalIDResponse` / `DecodeCVNResponse` parse N×16-byte CalID blocks (trailing nulls stripped) and N×4-byte CVN blocks. `PairCalIDsAndCVNs` matches them positionally per ISO 15031-5 §8.6.6 and raises on count mismatch. `FormatCVN` produces the 8-character upper-case hex display every scan tool uses. Tests cover request layout, ASCII trailing-null stripping, multi-block decode, bad-service-id rejection, truncation rejection, big-endian CVN decode, multi-block CVN, bad-PID rejection, hex formatter, positional pairing, mismatched-length rejection. +- **Drive-cycle advisor** (`OBD.DriveCycle.Advisor`) — given a `TWWHOBDReadinessSet` and an optional OEM key, returns a `TArray` listing the workshop-friendly drive procedure for every Supported-but-not-Complete monitor. `GenericStepFor` covers the ISO 15031-7 generic cycle for all 17 monitors (Misfire, FuelSystem, Comprehensive, Catalyst, HeatedCatalyst, Evap, SecondaryAir, AC, OxygenSensor, OxygenSensorHeater, EGRorVVT, NMHCCatalyst, NOxAftertreatment, BoostPressure, ExhaustGasSensor, PMFilter, EGRSystem) with realistic durations. `RegisterDriveCycleResolver(OEMKey, Resolver)` lets apps plug in OEM-specific overrides; an empty Description from a resolver falls back to the generic step. Tests cover empty input, fully-complete input, generic catalyst step, custom-resolver override, fallback-on-empty-description, diesel monitors emit diesel-specific steps. +- **WWH-OBD readiness decoder** (`OBD.Protocol.WWHOBD.Readiness`) — turns the FD05 readiness DID payload into `TWWHOBDReadinessSet` with named (Supported, Complete) booleans for the 17 ISO-spec'd monitors: continuous (Misfire, FuelSystem, Comprehensive) + non-continuous SI (Catalyst, HeatedCatalyst, Evap, SecondaryAir, AC, OxygenSensor, OxygenSensorHeater, EGRorVVT) + ISO 27145-3 diesel/Euro 6+ extension (NMHCCatalyst, NOxAftertreatment, BoostPressure, ExhaustGasSensor, PMFilter, EGRSystem). `EncodeWWHOBDReadiness` round-trips fixtures; emits the 4-byte form for SI and the 6-byte form when any diesel monitor is supported. `AllReady` flags whether every supported monitor has reported; `PendingMonitors` returns the workshop-friendly drive-cycle target list. Tests cover too-short rejection, MIL bit, DTC count, continuous + non-continuous decode, 4-byte and 6-byte round-trip, AllReady semantics for both populated and empty sets, PendingMonitors filtering. + +### Added — v3.81 (shipped) + +- **UDS NRC catalog** (`OBD.UDS.NRC`) — ISO 14229-1:2020 §A.1 Negative Response Code lookup. `DescribeNRC(Byte)` returns `(Code, ShortName, Description, Category)` for every spec'd NRC including the 0x50–0x5D certificate / authentication codes added in the 2020 revision; reserved bytes fall back to a synthetic descriptor without raising. `FormatNRC` produces the canonical "NRC 0x33 (SAD: securityAccessDenied)" rendering used as the project-wide one-line formatter. `IsTransientNRC` flags 0x21 / 0x22 / 0x78 / 0x94 so retry layers can decide whether to back-off-and-resend. Tests cover named lookups for general / security / condition / response-pending NRCs, reserved fallback, hex+short-name formatter shape, transient flagging, and category classification across security / condition NRC sets. +- **J1939 named-PGN library** (`OBD.J1939.PGNs`) — `TJ1939PGNDescriptor` records carry PGN id, mnemonic, human name, length (bytes; 0 = variable / multi-packet), default priority, default transmission rate (ms; 0 = on-request, -1 = on-change), and the spec section it's sourced from. Initial seed of 40+ entries across SAE J1939-71 (powertrain EEC1-4, ET1, EFL/P1, LFE1, CCVS, AMB, IC1, VEP1, TRF1, DD, AAI, WFI; brakes EBC1, EBS5, AIR1, HRVD; transmission ETC1/2/3/7; body PTO, VP, TIME, VW, VI, CI, EH; after-treatment AT1*, DPFC1), J1939-73 (DM1/2/3/4/5/6/7/8/10/11/12/23/26), J1939-21 (TP.CM, TP.DT), J1939-81 (AC), J1939-75 (genset GG, GAP, GTH, GTHA). Sorted by PGN at init; `FindPGN` is binary-search; `RegisterJ1939PGN` lets apps add OEM-specific entries. Tests cover seed count, no-duplicate-IDs, every entry has mnemonic/name/citation, DM1 + EEC1 lookups match spec, unknown returns zero record, register-replaces-existing, register-adds-new, all-sorted-ascending, AC + TP.CM + TP.DT distinct. +- **WWH-OBD support** (`OBD.Protocol.WWHOBD`) — UN GTR No.5 + ISO 27145 + ISO 15031-5 §7. `TWWHDtc` carries SPN (19-bit), FMI (5-bit), OccurrenceCount (7-bit), ConversionMethod (1-bit) packed into 4 wire bytes. `PackWWHDtc` / `UnpackWWHDtc` round-trip the J1939-FMI form with full range validation; `UnpackWWHDtcStream` parses an N×4-byte response payload. The standardised WWH-OBD DID set (VIN, CalibrationID, CVN, ECUName, OBDMID list, active/permanent DTCs, readiness, freeze frame, distance/time with MIL on, distance/time since DTC clear, warm-up cycles) is enumerated as constants + a `FindWWHOBDDataIdentifier` lookup that returns name + description. Tests cover round-trip + bit-field-width range validation + bad-length + ragged-stream rejection + named DID lookup. +- **OBD-II Mode 06 on-board monitoring** (`OBD.Service06.Mode06`) — ISO 15031-5:2015 §6.5 + §B Test ID / OBDMID / Unit-and-Scaling ID tables. Request encoder (`46 OBDMID`) + response decoder producing `TArray` with TID, UCSID, TestValue, MinLimit, MaxLimit per record. `PassedTest`, `ScaleFactor`, `UnitName` helpers on the record. `FindMode06Unit` / `FindMode06TestIdName` / `FindMode06OBDMIDName` cover the standardised lookup tables (~30 UCSIDs, OBDMIDs for O2 / catalyst / EGR / VVT / EVAP / O2 heater / misfire per cylinder / PM filter / NMHC / NOx adsorber). Tests cover request layout, single + multi-record decode, bad service-id rejection, ragged payload rejection, too-short rejection, pass/fail logic, scale factor, unknown-UCSID default, named OBDMID/TID lookups. +- **Tachograph workshop operations** (`OBD.Tachograph.Workshop`) — encode/decode the workshop-card-authenticated calibration records spec'd in EU 2016/799 Annex 1C: UTC time sync (4-byte TimeReal + 16-byte card id), K/L/W speed-source factors (3 × big-endian uint16, K validated 4000–25000 pulses/km), tyre size (2 BE bytes, 1500–4500 mm), VIN (17 ASCII), VRPlate (length-prefixed + national symbol byte), pulses-per-revolution, sealed-state activation (timestamp + card id + length-prefixed UTF-8 note). `DateTimeToTimeReal` / `TimeRealToDateTime` bridge Delphi `TDateTime` and the Annex 1C uint32 epoch. Reuses the v3.80 / 8.3 cert-chain crypto for the authenticated path. Tests cover round-trip + range validation for every record + bad-length / out-of-range rejections + sealed-activation layout. +- **Service routines library** (`OBD.OEM.ServiceRoutines`) — `TOBDServiceRoutineRegistry` ships a registry of 30+ publicly documented workshop procedures across maintenance / steering & brakes / powertrain / comfort / battery & electrical / TPMS / emissions. Each entry carries the UDS RoutineControl identifier (RID), sub-function, OptionRecord, required diagnostic session, safety class (`srsEngineMustBeRunning` / `srsVehicleMayMove` / `srsBatteryMin12V5` / etc.), pre/post-conditions, and a mandatory citation. Coverage includes oil reset for VAG/BMW/Mercedes/Ford/Toyota, SAS zero, EPB open+close, DPF forced regen, throttle adapt, BMW/Mercedes/Audi battery registration, TPMS relearn, headlight aim, brake-pad-change service position, AdBlue reset. `BuildRoutineControlFrame` produces the spec-correct 0x31 SF RID-hi RID-lo [OptRec] bytes; `Find / GetByCategory / GetByOEM` give UIs the lookups they need. `Tests.OEM.ServiceRoutines` enforces the contract: count >= 25, every entry cited, RID non-zero, valid sub-function, no duplicate keys, frame builder layout, frame builder rejects bad sub-function. New `docs/SERVICE_ROUTINES.md`. + +### Added — v3.80 (shipped) + +- **Radio code brand registry** (`OBD.RadioCode.Registry`) — process-wide map of brand key → factory with thread-safe register/find, a `TRadioCodeVariantManager` per brand, and a `DataAvailable` flag distinguishing real calculators from data-pending stubs. +- **Eight new brand entries (data-pending stubs)** — Pioneer, Kenwood, JVC, Sony, Philips, Grundig, Panasonic, Continental/VDO. Each implements `IOBDRadioCode` but raises `EOBDRadioCodeDataMissing` on `Calculate` because no public algorithm or licensed DB was found. `docs/DATA_GAPS.md` describes precisely what reference data each brand needs to become live. +- **`docs/DATA_GAPS.md`** — central register of features shipped as framework + stubs. +- `Tests.RadioCode.Registry` — covers registry lookup, case-insensitive find, duplicate suppression, pending-brand refusal contract. +- **VIN-aware variant resolver** (`OBD.RadioCode.VinResolver`) — `ResolveCalculator(Ctx) -> TRadioCodeResolveResult` uses the brand registry + the variant manager + `OBD.VIN.Decoder` to return the correct calculator + variant for a given VIN, with per-field overrides (model year, region, model hint). Registers VW / Audi / Mercedes / BMW as data-available brands seeded with documented per-generation variants (Audi Concert I-III + Symphony + RNS-E + MMI 2G/3G + MIB; Mercedes Becker BE/BE-2 + Audio 50 APS + COMAND NTG2/2.5/5 + MBUX; BMW Business + Professional + DSP + CCC + CIC + NBT/EVO + iDrive 5/6/7/8). The brand-internal calculators continue to hold the algorithms; the registry-side variants give the resolver enough metadata to dispatch. +- `Tests.RadioCode.VinResolver` — covers brand registration, year-boundary variant selection, invalid-VIN fallback, region override. +- **Tachograph DDD signature verification** (`OBD.Tachograph.Signature`) — `TOBDTachographSignatureChecker.ParseBlocks` walks the EU 2016/799 Annex 1C TLV blocks (overview / activities / events / faults / vehicle unit / speeds / technical data / card chip / signature), classifying tags from the public spec table. `VerifyChain` walks the parsed blocks and asserts each data block is immediately followed by a signature block whose body verifies via the host-supplied `IFirmwareSignatureVerifier`. Card-side and VU-side verifiers are independently configurable so production code wires ECDSA-P256 / RSA-PSS through the existing OpenSSL plumbing while tests use a controllable verifier. Tests cover empty file, single TLV parse, truncated declared length raises, full chain success with both verifiers, missing-signature-block detection, verifier-returns-False propagation, no-verifier-configured guard. +- **EV battery-health helpers** (`OBD.EV.BatteryHealth`) — `ComputeCellImbalance` walks a per-cell voltage array (the data already shipped with the v3.34+ catalogs across VW MEB / Tesla / BMW i / HMG E-GMP / Polestar / Lucid / NIO / BYD / Xpeng / Rivian) and returns min / max / mean / std-dev / spread / >3-sigma outlier index. `ComputeBatterySoH(Rated, Observed, Cycles, Derating)` derives state-of-health (SoHFromCapacity = Observed / Rated, clamped at 1.0) plus an optional temperature-derating composite. `NormaliseChargingSession` validates a session record (SoC pair, duration, session type AC/DC/V2L/V2G). Pure math — production callers fetch the underlying DIDs via the existing OEM client and pass them in. Tests cover flat-pack zero-spread, mixed-spread metrics, > 3-sigma outlier detection on 100 cells, empty-input rejection, SoH at rated / half-capacity / temperature derating, charging-session round-trip, end-before-start rejection, out-of-range SoC rejection. +- **J2534-2 (2018) IOCTL constants + SET_CONFIG builder** (`OBD.Adapter.PassThrough.J2534v2`) — adds the 2018-spec parameter IDs missing from the J2534-1 surface: `CFG_CAN_MIXED_FORMAT`, `CFG_CAN_FD_DATA_RATE`, `CFG_BIT_SAMPLE_POINT_FD`, `CFG_SYNC_JUMP_WIDTH_FD`, `CFG_TX_DELAY_COMP`, `CFG_ISO15765_FD_BS`, `CFG_ISO15765_FD_STMIN`. `TJ2534ConfigList` builds the SCONFIG_LIST buffer (`uint32 NumOfParams; (uint32 Parameter, uint32 Value)*`) ready for the existing `PassThruIoctl` call. Tests cover empty-list framing, single-entry little-endian byte order, multi-entry size/order preservation, count tracking, and the 2018 parameter IDs match the public spec table. +- **ISO-TP timing audit harness** (`OBD.Protocol.IsoTp.Timing`) — `DecodeStminMicros` / `EncodeStminMicros` cover the ISO 15765-2 §6.5.5 STmin byte encoding (0x00..0x7F = 0..127 ms; 0xF1..0xF9 = 100..900 us; rest reserved → raises). `TOBDIsoTpTimingChecker` walks a recorded sequence of `TIsoTpFrameObservation` (Single / First / Consecutive / FlowControl + microsecond timestamp + direction) against declared STmin/BlockSize and reports per-frame violations (`itvIntraGapTooSmall` / `itvBlockSizeExceeded`). Configurable tolerance (default 200 us) absorbs scope-timer jitter on real adapters. Pure offline harness — production capture-replay tests can drive it from `.obdlog` fixtures, and live timestamps drop in once a CI CAN simulator is online. Tests cover the full STmin decode lookup table, encode round-trip, reserved-byte raises, compliant stream, undershoot violation, BlockSize overrun, tolerance forgiveness, FC reset. +- **AUTOSAR SecOC framing + auth** (`OBD.Protocol.SecOC`) — `TSecOCContext` covers profiles 1, 2 and 3. Profile 3 (HMAC-SHA-256) is fully implemented end-to-end via `System.Hash.THashSHA2.GetHMAC`; `SecOCComputeAuthenticator` computes the authenticator over `KeyId || FreshnessValue || Payload` with deterministic truncation to `AuthenticatorBits`, and `SecOCVerifyAuthenticator` does a constant-time compare. Profiles 1 / 2 (CMAC-AES-128) raise `EOBDSecOCAlgorithmNotAvailable` until OpenSSL EVP_MAC binding lands (gap tracked in `docs/DATA_GAPS.md`). `SecOCEncodePDU` produces the wire envelope. Tests cover round-trip verification, FV change → MAC change, payload-flip rejection, wrong-key rejection, configurable truncation length (24/32/64-bit), Profile 1 raises until binding ships, PDU layout, empty-key rejection. +- **CAN-FD adapter capability flag** (`OBD.Adapter.Capabilities`) — process-wide registry mapping adapter-key → `TOBDAdapterCapabilitySet` (CAN, CAN-FD, ISO-TP, ISO-TP-LF, DoIP, J1939, K-Line, Voltage, SecOC, J2534, J2534v2, BLE, WiFi, FTDI). Seeded with the known adapters: ELM327 (no FD), OBDLink MX (no FD), OBDLink EX (FD + 64-byte ISO-TP), DoIP gateway, J2534. `AdapterSupports(Key, Cap)` and `ResolveIsoTpFrameBytes(Key)` give callers a clean way to feature-gate CAN-FD-aware code paths and pick 7-byte vs 62-byte ISO-TP single frames. Registry is thread-safe, case-insensitive, and idempotent. Tests cover ELM327-no-FD, OBDLink-EX-has-FD, DoIP-no-K-Line, unknown-adapter-false, ISO-TP fallback to 7, ISO-TP-62 for FD adapters, case-insensitive lookup, set-to-string rendering, register-replaces-existing. +- **DoIP UDP discovery + AliveCheck** (`OBD.Protocol.DoIP.Discovery`) — ISO 13400-2 §5/§6/§8 UDP wire codec covering Vehicle Identification Request (no payload, EID-targeted, VIN-targeted), Vehicle Announcement / Identification Response, AliveCheck Request/Response, and the generic header NACK. Frame builders verify VIN length (17) and EID length (6); `ParseDoIPHeader` validates the protocol-version / inverse-NOT pairing and the declared payload length; `ParseVehicleAnnouncement` decodes the 32/33-byte payload and exposes VIN, logical address, EID, GID, further-action-required, optional sync status (mandatory in 2019, optional in 2012). Pure codec, no I/O — production code composes with the existing `OBD.Connection.*` UDP path. Tests cover header inverse, request lengths, length-mismatch raises, AliveCheck source-address echo, header rejection of bad inverse and truncation, full Vehicle Announcement round-trip, 2012 form without sync. +- **Programming-voltage gate** (`OBD.ECU.Flashing.VoltageGate`) — `TOBDProgrammingVoltageGate.RequirePass(VoltageReader, OEMKey)` reads the adapter's measured pack voltage and refuses to proceed (`EOBDProgrammingVoltageTooLow`) if it's below the resolved threshold. Default 12.5 V (ISO 22900-2 informative annex); per-OEM overrides via `SetOEMThreshold(OEMKey, Volts)` for platforms that need a different floor (e.g. EVs that need a specific LV state). Reader-side errors raise `EOBDProgrammingVoltageUnavailable` separately so the caller can distinguish "battery low" from "adapter dead". Tests cover default + above/below threshold, OEM override + case-insensitivity, nil reader, reader-raises-caught, RequirePass low-voltage / unavailable separation, non-positive rejection. +- **PQC-ready signature verifier** (`OBD.ECU.Signature.PQC`) — `TOBDPQCSignatureVerifier` plugs into the existing `IFirmwareSignatureVerifier` interface. Supports the FIPS 204 ML-DSA family (44 / 65 / 87) and FIPS 205 SLH-DSA family (SHA2-128s / SHAKE-128s) by stable algorithm tag. The envelope codec (`uint8 alg | uint8 key-id-len | bytes key-id | uint32 sig-len | bytes signature`) is fully implemented and tested. `Verify` parses + algorithm-checks the envelope and then raises `EOBDPQCNotAvailable` until an OEM publishes a wire spec and the OpenSSL 3.x EVP binding lands (tracked in `docs/DATA_GAPS.md`); failing closed is the right behaviour while no production-signed-PQC ECU exists. +- **Coding encoders for Toyota / Honda / HMG / Stellantis** — `OBD.OEM.Coding.Toyota` (CUW Customize), `OBD.OEM.Coding.Honda` (HDS option-byte), `OBD.OEM.Coding.HMG` (GDS variant-coding), `OBD.OEM.Coding.Stellantis` (Proxi). Each follows the existing VW long-coding pattern: byte / bit accessors over a fixed-length payload, with per-controller bit semantics deferred to the JSON catalog system. Stellantis Proxi includes a `ComputeChecksum` placeholder that raises until the FCA CRC polynomial is supplied (tracked in `docs/DATA_GAPS.md`); `SetChecksum(Crc, Offset)` writes a caller-supplied value for use with captured wiTECH log data. Tests cover hex round-trip, bit accessors, out-of-range guards, the placeholder-raises contract, and zero-length rejection. +- **Resumable flashing** (`OBD.ECU.Flashing.Checkpoint`) — sidecar JSON file recording (firmware SHA-256, block size, total blocks, last completed block, snapshot path, timestamp). `Initialise(SidecarPath, FirmwarePath, BlockSize, TotalBlocks, SnapshotPath)` creates it; `MarkBlockComplete(I)` updates idempotently after every block ack; `LoadAndVerify(SidecarPath, FirmwarePath)` checks the SHA before allowing resume so a swapped firmware is rejected. `Clear` deletes the sidecar on a successful flash. Tests cover initial persist + resume, progress recording, firmware-swap rejection, completed-flash-not-resumable, sidecar deletion, out-of-range guard. +- **Coding rollback log** (`OBD.OEM.Coding.AuditLog`) — append-only JSON file with HMAC-SHA256 chained signatures (`HMAC = HMAC(K, Prev || Body)` where `Prev` is the previous record's HMAC). `Verify` walks the file from the start and reports the first tamper position; insert / delete / mutate are all detected. Restarting against an existing file continues the chain from the last record's HMAC. Tests cover single-record verify, multi-record chain, byte-flip detection, mid-chain deletion, restart-continues-chain, empty-key rejection. +- **Coding diff & dry-run** (`OBD.OEM.Coding.Diff`) — `TOBDCodingPlan(Current, Target [, Schema])` builds a structured diff between two coding payloads, exposes `Diff` / `IsNoOp` / `AsText`, and only writes when `Apply(Confirmed=True, Writer)` is called. Optional `TOBDCodingSchema` lets a caller annotate bit / byte / uint16 fields with names and descriptions so the diff renders as `CornerLights [byte 1 bit 3]: 0 -> 1` instead of bare hex offsets. `Tests.OEM.Coding.Diff` covers no-op detection, byte-level diff, schema-driven naming, confirm/no-confirm semantics, length mismatch. + +## [3.79.0] - 2026-05-08 — Async UDS + cross-platform DoIP + TLS + tooling + +**Async UDS client** (`OBD.OEM.UdsClient.Async`): future-returning +facade over `IOBDUdsClient` so UI threads can fire-and-await every +diagnostic call without blocking. One serialised worker thread per +client matches the wire contract (UDS allows one outstanding +request per ECU). Cooperative cancellation through +`IOBDCancellationToken` — pre-cancelled tokens never reach the +wire; CloseSession drains pending futures as cancelled. +`Tests.OEM.UdsClient.Async` covers Await, OnComplete, +pre-cancellation, exception propagation, and serial in-order +completion. + +**Capture/replay round-trip** (`Tests.OEM.UdsClient.Replay`): +new `TCaptureReplayTransport` parses recorded `.obdlog` pairs and +replays responses by request-byte match. First end-to-end test +that exercises ResolveCatalogPath + TOBDOEMJSONCatalog + +IOBDUdsClient + DID decoder against real wire data — VW VIN +(F190 → "WVWZZZ8N8Z1234567") and VAG part number (F187 → +"04L906056AA") decode bit-exactly from the captured fixture. + +**Cross-platform DoIP** (`OBD.Protocol.DoIP.Session.Cross`): +TCP-side ISO 13400-2 §8 implementation built on +`System.Net.Socket` (RTL, all platforms — Windows, macOS, Linux, +iOS, Android), no Indy or Synapse dependency. Same Connect / +ActivateRouting / SendReceive / Disconnect surface as the WinSock +variant; same 16-frame ACK/NACK consumption cap. Self-loop +integration test (`Tests.Protocol.DoIP.Cross`) spins a +`TFakeGateway` TThread on 127.0.0.1, walks the routing-activation +handshake, and asserts a diagnostic round-trip is bit-exact — +first end-to-end DoIP wire test in the suite. + +**DoIP TLS** (`OBD.Protocol.DoIP.Session.TLS`, ISO 13400-3 §7, +TCP/3496): Indy 10 + OpenSSL TLS-secured session. TLS 1.2 +mandatory minimum (1.3 allowed) per spec. Mutual TLS via +`TDoIPTLSCredentials` (root CA, client cert+key+passphrase, +peer-verification toggle, optional cipher-list override for OEM +policies). Self-loop integration test spins +`TIdTCPServer + TIdServerIOHandlerSSLOpenSSL` with a fixture +self-signed cert pair (`tests/fixtures/tls/`); falls back to +Assert.Pass with skip message if OpenSSL is unavailable on the +dev machine. + +**Catalog Browser VCL example** (`examples/catalogbrowser`): +programmatic single-form VCL app that walks every shipped OEM +catalog and lets the user drill into ECUs / DIDs / Routines / +Coding Blocks / Adaptations / Actuator Tests / Live PIDs / DTC +Extended Data side by side. Auto-loads from `catalogs/` by +walking up from the binary; skips schema, DTC catalogs, ISO/UDS +universal files and test fixtures. + +**Coverage harness** (`tools/coverage/`): wires +`delphi-code-coverage` against the DUnitX test runner. Emits +HTML + Cobertura XML + LCOV reports. `cov-include.txt` lists the +OEM / UDS / DoIP / async / capture units to instrument; CI +post-test upload step is documented for when the Delphi runner +is provisioned (see GAPS.md G4). + +GAPS.md G12 (DoIP TLS + self-loop integration test) marked +✅ FIXED. The remaining open gap is G4 (CI Delphi runner — process +constraint, not a code gap) — coverage tooling is now ready for +it. + +## [3.78.0] - 2026-05-08 — Production-quality gap pass + Phase B vehicle classes + +Phase D (DTC content): expanded `dtc_extended_data` across 47 OEM +catalogs (1,282 entries) with the v3.77 schema fields (symptoms, +repair_guidance, monitor_type, freeze_frame_relevant, related_dids, +related_routines, oem_bulletin). Phase E (DoIP transport): unit body +guarded with `{$IFDEF MSWINDOWS}`, `SendReceive` alive-check loop +bounded to 16 frames per call. Phase F.1-F.3 (UDS client): +`OBD.OEM.UdsClient` async-friendly facade — OpenSession / ReadDID / +WriteAdaptation / ExecuteRoutine / ReadCodingBlock / WriteCodingBlock +/ RunActuatorTest / ReadDtcs / StreamLivePIDs, with ASCII-empty- +payload guard and tightened bounds enforcement (no longer skipped +when min=max=0). + +Phase A (schema/JSON Schema): `catalogs/_schema/oem-catalog-v2.json` +shipped + new `Tests.OEM.SchemaShape` walks every catalog asserting +WMI regex, decoder/field/adaptation kind enums, DTC code formats +(SAE J2012 + J1939 SPN-FMI + 22 OEM prefixes), non-empty manufacturer +keys and version 1/2 bound. Phase C (catalog integrity): +`Tests.OEM.CatalogIntegrity` covers coding-block payload bounds, +cross-section ECU references, and duplicate primary keys — replaces +the deleted Python lint. + +Phase B (vehicle classes, 33 new OEM catalogs, ~50,000 entries): +- Motorcycles (14): Ducati, Harley-Davidson, Triumph, BMW Motorrad, + KTM, Yamaha-moto, Honda-moto, Kawasaki, Suzuki-moto, Indian + Motorcycle, Royal Enfield, MV Agusta, Aprilia, Husqvarna-moto. +- Agricultural (8): John Deere, CNH, Caterpillar-Agri, Komatsu, + Kubota, AGCO, Claas, Volvo CE. +- Marine (6): Mercury Marine, Volvo Penta, Yanmar Marine, MTU, + Cummins Marine, Yamaha Marine. +- Powersports (5): Polaris, Can-Am/BRP, Arctic Cat, Yamaha + WaveRunner, Kawasaki Jet Ski. + +Each backed by `OBD.OEM.{Motorcycles,Agricultural,Marine, +Powersports}.pas`, registered at unit init, wired into RunTime.dpk ++ RunTime.dproj. `ResolveCatalogPath` probes vehicle-class subdirs +after the top level. `AllOEMCatalogsLoadFromDirectory` recurses with +`TSearchOption.soAllDirectories`; threshold raised to ≥70 catalogs. + +Cleanup: removed all 8 Python lint scripts (this is a Delphi +repository); CI lint replaced with bash one-liner + the Delphi +`Tests.OEM.CatalogIntegrity` fixture. 36 catalogs re-deduped on +normalised integer ECU addresses; 9 coding-block payloads bumped; +59 implicit ECU references promoted to explicit `ecus[]` entries. + +Total: 79 OEM catalogs / 247,279 entries / 5 vehicle classes. + +## [3.76.0] - 2026-05-08 — Isuzu Motors ~18% ODIS, ~4,800 entries + +RZ4E 1.9 diesel + DDi 3.0 Blue Power + 4HK1 5.2 + 6HK1 7.8 + 6UZ1 +9.8 + 6WG1 15.7 + 4HK1 LNG + N-Series Electric + Giga Electric + +D-Max EV announced + Giga FCEV (Honda fuel cell partnership) + D-Max +Rough Terrain Mode + Aisin 6/8AT + AMT MZW6E + MIMAMORI + IDSS. + +## [3.75.0] - 2026-05-08 — Volvo Trucks Group ~22% ODIS, ~4,900 entries + +Volvo + Mack + Renault Trucks + UD Trucks. D8/D11/D13/D16 + D13TC +turbo-compound (I-Save) + D11K/D13K LNG + D13H hydrogen ICE + FL/FE/ +FH/FM/FMX Electric (FH Electric 490 kW dual e-axle) + B8R bus EV + +Mack MD Electric + Renault D Wide ZE + Volvo FH FCEV (Cellcentric JV) ++ I-Shift 12 AMT + I-Shift Dual Clutch + Powertronic 6AT + Tech Tool. + +## [3.74.0] - 2026-05-08 — PACCAR (Kenworth/Peterbilt/DAF) ~18% ODIS, ~4,800 entries + +MX-11/MX-13 + PX-7/PX-9 + MX-11 LNG + MX-13 hydrogen ICE + Kenworth +T680E EV + Peterbilt 579EV + DAF XB-e/XF Electric + Kenworth/Peterbilt +hydrogen fuel cell EV (Toyota partnership) + PACCAR AMT 12-speed + +Eaton Endurant + TruckTech+/SmartLINQ/DAF Connect. + +## [3.73.0] - 2026-05-08 — Scania ~22% ODIS, ~4,900 entries (Traton Group) + +DC09/DC13/DC16 V8 (660/770 hp) + Super 13L next-gen + OG13 LNG/CNG + +Super 13H hydrogen ICE + Scania BEV + PHEV DC09 + Opticruise G25/G33 +12-speed AMT + Active Prediction GPS-aware + Scania One TCU + SDP3. + +## [3.72.0] - 2026-05-08 — MAN Truck & Bus ~22% ODIS, ~4,900 entries (Traton Group) + +D08/D20/D26/D38/D15 + E3876 NG + D38H hydrogen ICE + eTruck eTGX/ +eTGS + eTGM/eTGE BEV + Lion's E City bus + EfficientCruise + +EfficientRoll + Predictive Powertrain Control + EBA 2 + Lane Return ++ Side Collision Avoidance + TipMatic 12-speed AMT + MAN-cats II. + +## [3.71.0] - 2026-05-08 — Iveco Group ~18% ODIS, ~4,800 entries + +Cursor 8/9/11/13/16 + F1C/F1A/NEF + Cursor NG (LNG/CNG) + FPT XC13 +hydrogen ICE + Hi-SCR (no EGR Iveco trademark) + Hi-Cruise predictive ++ eDaily/eMoover/S-eWay battery EV + ZF TraXon + HI-TRONIX 16AMT. + +## [3.70.0] - 2026-05-08 — Detroit Diesel ~20% ODIS, ~4,900 entries + +DD13/DD15/DD16/DD5/DD8 + DT8 legacy + DD5N natural gas + eCascadia +eAxle EV + DT12 AMT + Detroit Assurance Active Brake Assist 5 + IPM ++ Detroit Connect Virtual Technician. + +## [3.69.0] - 2026-05-08 — Cummins ~22% ODIS, ~4,900 entries (J1939 engine OEM) + +17 engine variants (ISB 6.7 / ISL 8.9 / ISX 15 / X15 / X12 / X10 / +B6.7 Ram HD / R2.8 Repower / QSB/QSL/QSX industrial / ISF2.8/3.8 + +X15N natural gas + X15H/B6.7H hydrogen ICE) + Accelera BTEV battery +EV + H Drive HEV/PHEV/BEV integration + Eaton Endurant 12-speed AMT ++ Allison 3000/4000 + ZF TraXon + DPF + SCR + DEF + DOC + ASC ammonia +slip cat + cooled EGR + 7th injector + INSITE. + +## [3.68.0] - 2026-05-08 — Tata Motors ~15% ODIS, ~4,800 entries + +Revotron 1.2T iCNG / 1.5 T-GDi + Kryotec 1.5/2.0 diesel + Nexon EV +LR + Punch EV ACTI.EV + Curvv EV + Tiago/Tigor EV + Harrier/Safari EV ++ Altroz EV + Sierra EV revival + Avinya/Atlas gen-3 + iRA Connected ++ ConnectNext + ACTI.EV OS. + +## [3.67.0] - 2026-05-08 — Mahindra ~15% ODIS, ~4,800 entries + +mHawk 2.2 diesel + mStallion 2.0/1.5/1.2 turbo petrol + Thar/Scorpio-N +4XPLOR 4WD low-range + diff lock + 6 terrain modes + XUV700 Z-Wheels +AMT + INGLO BE 6 / XEV 9e 800V + e-Verito legacy + Treo/Zoom 3-wheeler +EV + AdrenoX + Alexa Built-in. + +## [3.66.0] - 2026-05-08 — Lada (AvtoVAZ) ~15% ODIS, ~4,700 entries + +VAZ 1.5/1.6/1.8 8V/16V Evo + Renault H4Mk/H4Dt/K9K diesel + Niva +Travel/Legend 4WD low-range + Vesta NG/Sport/Cross/Aura + X-Ray +CMF-B-LS Renault platform + Largus e-EV + AvtoVAZ AMT robotised + +JATCO CVT/4AT + EnjoY Pro infotainment. + +## [3.65.0] - 2026-05-08 — GWM (Haval/Wey/Tank/ORA/Poer) ~18% ODIS, ~5,000 entries + +Lemon DHT 1.5/2.0 turbo PHEV + GW4N20/4N30 V6 + Tank 500/700 ladder- +frame + Tank 300 off-road + Tank turn (700) + 4WS crab-walk + 3 diff +locks + Wey Coffee PHEV + ORA Good/Lightning/Ballet Cat + Haval H6 +DHT-PHEV + Poer/Cannon pickups + Coffee OS. + +## [3.64.0] - 2026-05-08 — Geely Holding (9 brands) ~20% ODIS, ~5,000 entries + +Geely Auto + Lynk & Co + Zeekr + Lotus + Galaxy + Proton + Livan + +Geometry + Volvo (already done) sharing SEA/SEA-S/EMA platforms. +Zeekr 001/007/009/X/Mix 800V SiC + 5C Flash-Charge + Lotus Eletre/ +Emeya/Theory 1 (Eletre R 905hp) + Galaxy L7/E8 PHEV + Lynk & Co Z10 ++ Livan + Proton e.MAS 7 + E-DHT Hi-X PHEV. + +## [3.63.0] - 2026-05-08 — smart (Mercedes×Geely JV) ~22% ODIS, ~4,800 entries + +Legacy ForTwo/ForFour W453 + new #1/#3/#5 BEV3 SEA platform + Brabus +AWD 428 hp + smart Pilot + Beats Audio + Halo panoramic + smart AI +Cockpit Snapdragon. + +## [3.62.0] - 2026-05-08 — McLaren ~15% ODIS, ~4,900 entries + +M838T/M840T 3.8/4.0 V8 BiTurbo (720S/765LT/Senna/Speedtail KERS +1036hp/750S 740hp/W1 PHEV 1258hp/P1 903hp legacy) + M630 3.0 V6 +PHEV Artura 671hp + Artura Spider 700hp + Graziano 7-DCT + Artura +8-DCT no reverse (uses motor) + Proactive Chassis Control III + Race +Active Chassis Senna + Active DRS + nose lift + Variable Drift Control ++ Iris II 5G + future all-EV post-2030. + +## [3.61.0] - 2026-05-08 — Ferrari ~15% ODIS, ~5,000 entries + +F154 3.9 V8 BiTurbo (488/F8/Roma/Pista 711hp) + F154 4.0 V8 PHEV +SF90 1000hp + SF90 XX 1030hp + F163 3.0 V6 PHEV 296 GTB + F140 6.5 V12 +NA (812 Superfast/Competizione/Purosangue/12Cilindri 830hp/Daytona +SP3 840hp) + LaFerrari HY-KERS + Manettino 8-pos + E-Manettino PHEV + +SSC Side Slip Control + Active aero + F1-style DRS + first all-EV +2026 Elettrica. + +## [3.60.0] - 2026-05-08 — Aston Martin ~18% ODIS, ~4,800 entries + +AMG 4.0 V8 BiTurbo DBX707 697hp + Vantage 665hp + DB12 671hp + 5.2 +V12 BiTurbo DBS Superleggera + V12 Speedster + Vanquish 2024 835hp + +Valhalla PHEV + Valkyrie Cosworth 6.5 V12 NA 1000hp + ZF 8HP rear- +mount transaxle (DBS) + Mercedes Comand 8 + AML in-house infotainment ++ first all-EV 2026. + +## [3.59.0] - 2026-05-08 — Rolls-Royce ~32% ODIS, ~5,000 entries (BMW Group leakage) + +N74 V12 BiTurbo + Black Badge 600 hp + Spectre BMW i7-derived dual +motor + Black Badge 650 hp + Magic Carpet Ride + Planar Suspension + +Flagbearer cam + Satellite Aided Transmission + Starlight Headliner +1568 fibres + Shooting Stars + Starlight Doors (Phantom Tranquillity) ++ Spirit of Ecstasy retractable + 13-bit Bespoke (crystal door +handles, Droptail program, Bespoke Collective) + 18-channel Bespoke +Audio + Whispers app + iDrive 8 bespoke. + +## [3.58.0] - 2026-05-08 — Bentley ~32% ODIS, ~5,000 entries (VW Group leakage) + +W12 + V8 BiTurbo (incl. Continental GT Speed PHEV) + Bentley Dynamic +Ride 48V active anti-roll + Rotating Display + Breitling rotating +clock + 15-bit Mulliner bespoke (Naim for Bentley, Akrapovič, crystal +glass, Linley overmats, diamond knurling, Battue Pack) + Bentley +Smart Cabin Snapdragon + first all-EV PPE 2026. + +## [3.57.0] - 2026-05-08 — Dacia ~22% ODIS, ~4,800 entries + +Shared CLIP/Renolink with Renault. Y-light signature + StarklePack + +YouClip modular accessories + Extreme Pack 6-mode + Spring CN-platform +EV + E-TECH 140 hybrid Jogger/Bigster. + +## [3.56.0] - 2026-05-08 — Suzuki / Maruti pushed to ~22% ODIS, ~4,900 entries + +SDT / Suzuki Diagnostic Tool community. + +ALLGRIP 4-mode (Auto/Sport/Snow/Lock + Jimny 4L/4H) + SHVS Smart +Hybrid + Strong Hybrid + Jimny part-time 4WD low-range + 13 engine +variants (K14D Boosterjet + K14C SHVS + K10C 3-cyl + K15B/K15C SHVS + +Z14EET Strong + K12C Dualjet + DDiS diesel + eVX dual motor + e Vitara +27PL + **Across/Swace Toyota-badge hybrids + RAV4-based PHEV**). + +## [3.55.0] - 2026-05-08 — Mini (BMW Group) pushed to public-source ceiling (~30% ODIS, ~5,000 entries) + +BMW ISTA + Mini Connected community sources. Mini is a BMW sub-brand +sharing UKL2/FAAR/Spotlight platforms. + +### catalogs/mini.json +~5,000 entries. Built via shared library. + +#### Brand-specific captures +- **Go-Kart Mode** + 8 Mini Experiences (Green/Sport/Timeless Classic/ + Core/Vivid/Balance/Personal) +- **Mini Yours / Spotlight** 6-bit (Door LED projector + Union Jack + taillights + Piano Black + Chrome delete + Multitone Roof + LED + ambient patterns) +- 12 engine variants: B38 1.5T 3-cyl + B48 2.0T + JCW 306 hp + B58 3.0 + I6 JCW GP3 + B37/B47 diesel + Cooper SE legacy (BMW i3 driveline) + + **Cooper SE new (Spotlight CN platform) + Aceman dual + Countryman + E UKL2/FAAR PHEV+EV + Countryman SE ALL4 + JCW Electric** +- 7 transmissions incl. Aisin 8-speed Steptronic + ZF 8HP legacy + + 7-DCT + 6/7MT + ALL4 AWD coupling +- Mini Connected + **Mini OS 9 round OLED** + +Estimated ~30% ODIS — at BMW-shared/Mini-community ceiling. + +## [3.54.0] - 2026-05-08 — Renault / Alpine pushed to public-source ceiling (~25% ODIS, ~4,900 entries) + +CLIP / Renolink / Pyren / Ddt4all community sources. Covers Renault + +Alpine (now full sub-brand with A110/A290/A390 EV). + +### catalogs/renault.json +~4,900 entries (132 ECUs / 1,950 DIDs). Built via shared library. + +#### Brand-specific captures +- **MULTI-SENSE** 8-mode (Comfort/Sport/Eco/Perso/Neutral/Race + Megane RS/Snow/All-Road) +- **4CONTROL** 4-wheel steering coding (low-speed opposite phase + + high-speed same phase + sport aggressive — Megane RS/Espace/Rafale) +- **E-TECH** multi-mode clutchless hybrid 9-bit (PHEV + EV priority + + Hybrid auto + E-Save + Pure full EV + V2L on R5/R4 E-Tech) +- 19 engine variants: H5Ht 1.8 turbo Megane RS Trophy + H4Ht/H5Dt + Blue dCi + K9K + E-TECH 1.6/1.8 + **Renault 5 E-Tech AmpR Small + + Renault 4 E-Tech + Megane E-Tech CMF-EV + Scenic E-Tech + Alpine + A290 R5-based + Alpine A390 fastback SUV** + Zoe legacy + Kangoo + Z.E. + Master E-Tech +- 9 transmissions incl. EDC 7-DCT (Getrag) + EDC 6-DCT + E-TECH multi- + mode clutchless + 1/2-speed EV + X-Track 4WD +- HU gens: EASY LINK + **OpenR Link Android Automotive** + Alpine + telematics +- 5 routines incl. **4CONTROL calibrate + E-TECH multi-mode clutch + relearn + Alpine telemetry export** + +Estimated ~25% ODIS — at CLIP/Renolink-community ceiling. + +## [3.53.0] - 2026-05-08 — GM (Chevy/Buick/Cadillac/GMC) pushed to public-source ceiling (~30% ODIS, 5,254 entries) + +GDS2 / MDI / Tech2 community sources. Covers all 4 brands sharing GM +diagnostic topology: Chevrolet, Buick, Cadillac, GMC. + +### catalogs/gm.json — 28 → 5,254 entries +141 ECUs, 2,244 DIDs, 126 routines, 32 coding blocks (309 fields), 78 +adaptations, 134 actuator tests, 39 live PIDs, 2,460 DTC ext. + +#### Brand-specific captures +- **Super Cruise** 13-bit coding (hands-off lane change + hitched towing + + speed limit assist + driver attention camera + steering wheel + light bar + approved-route only + Super Cruise w/ trailer + max + speed) +- **Ultra Cruise** (Cadillac Celestiq) door-to-door +- **Hummer EV** 11-bit (CrabWalk diagonal + 4-wheel steering + Extract + Mode +6 inch lift + Watts to Freedom launch + Adaptive Air Ride + + Ultra Vision UFS underbody + off-road Super Cruise + Infinity Roof + sky panels + Power Pack outlets + V2H bidirectional + Terrain Mode) +- **C8 Corvette modes** 16-bit (Weather/Tour/Sport/Track/MyMode/Z-Mode + + 6-level PTM Performance Traction Management Wet→Race 2 + frunk + button + front-lift GPS memory + valet mode + Performance Data + Recorder) +- **DFM** Dynamic Fuel Management 17-cylinder modes +- **OnStar Connected Services** 19-bit (Automatic Crash Response + 5G + Wi-Fi + Smart Driver coaching + Google Built-in Maps/Assistant/Play + + Alexa + Phone-as-Key UWB + Trailering App + Connected Navigation + + my{Chevy/GMC/Cadillac/Buick} app + SiriusXM 360L) +- **Trailering** 12-bit (Max Trailering + Advanced Trailering System + + Transparent Trailer view + Trailer Camera 14 views + Hitch View + + Hitch Guidance + Jackknife Alert + Trailer Blind Zone + Trailer + TPMS + Super Cruise w/ trailer + max kg up to 11,500) +- **30 engine variants**: LS3 → LT2 mid-engine C8 Stingray + LT6 5.5 + flat-plane Z06 670 hp NA + LT7 5.5 twin-turbo ZR1 1064 hp + LT4 SC + Z06/ZL1 + L84/L87 DFM + LDD/LM2 3.0 Duramax I6 diesel + L5P 6.6 + Duramax V8 + L8T 6.6 V8 gas HD + Ecotec 2.0/2.7T + LF3 V6 BiTurbo + Blackwing + Ultium Lyriq/Hummer EV dual+tri/Silverado EV/Equinox EV/ + Blazer EV SS/Celestiq/Escalade IQ/BrightDrop Zevo +- **12 transmission variants**: 6L50/6L80/8L90/10L80/10L90 + + Tremec TR-9080 8-DCT (C8 Corvette) + Tremec 6/7MT manual + VT40 CVT + + Ultium 1-speed + Ultium 2-speed Hummer EV / Silverado EV + + ATC active transfer +- **HU gens**: Infotainment 3 Plus + VIP / Global B + VIP Ultium + Snapdragon + Ultifi software platform +- 19 routines incl. **Watts to Freedom launch test + Extract Mode + + CrabWalk + Corvette front-lift GPS save + PTM calibrate + DFM + cylinder mode relearn + MidGate calibrate Silverado EV + Performance + Data Recorder export + UFS underbody calibrate** +- 15 adaptations incl. **WTF default + Extract lift height + Ultium + max DC kW + V2H + V2L Power Pack** +- 11 actuator tests incl. **Watts to Freedom launch demo + CrabWalk + + Extract demo + Corvette front lift + Power Pack outlet test + + MidGate demo + Trailer 14-view demo** + +Estimated ~30% ODIS coverage — at the realistic GDS2/Tech2-community +ceiling. + +## [3.52.0] - 2026-05-08 — EV specialists (6 OEMs) pushed to public-source ceiling (~18-22%, 29,626 entries) + +Tesla, Rivian, Lucid, BYD, NIO, XPeng brought up to depth using +community sources (TeslaScan, Rivian Service Mode, Lucid community, +BYD e-Platform forums, NIO Banyan, XPILOT community). Built via +shared parameterized library. + +### catalogs/{tesla,rivian,lucid,byd,nio,xpeng}.json + +| OEM | Entries | ECUs | DIDs | Routines | Coding | Adapts | Acts | Live | DTC ext | +|---|---|---|---|---|---|---|---|---|---| +| Tesla | 4,985 | 135 | 2,005 | 114 | 30 (273 fields) | 70 | 132 | 39 | 2,460 | +| Rivian | 4,894 | 131 | 1,931 | 109 | 29 (252 fields) | 68 | 127 | 39 | 2,460 | +| Lucid | 4,902 | 134 | 1,941 | 107 | 28 (251 fields) | 67 | 126 | 39 | 2,460 | +| BYD | 4,995 | 131 | 2,030 | 111 | 29 (255 fields) | 68 | 127 | 39 | 2,460 | +| NIO | 4,934 | 132 | 1,967 | 112 | 29 (256 fields) | 68 | 127 | 39 | 2,460 | +| XPeng | 4,916 | 131 | 1,950 | 111 | 29 (253 fields) | 68 | 128 | 39 | 2,460 | + +#### Brand-specific captures + +**Tesla** — Autopilot 14-bit coding (Basic + Enhanced + FSD + FSD +Supervised + FSD Unsupervised + Navigate on Autopilot + Summon + +Smart Summon + Actually Smart Summon + Autopark + Traffic Light & +Stop Sign + Auto Lane Change + Highway Assist + Hands-off lane +change + max-speed-above-limit) + Acceleration Boost + Track Mode + +Drift Mode + Plaid Mode + Cheetah Stance + Premium Connectivity +11-bit (satellite maps + live traffic + streaming + Theater + +Caraoke + Arcade + Sentry Live View + in-car camera) + Sentry / Dog +Mode / Camp Mode / Bioweapon Defense / Hospital Mode (HEPA) + +Supercharger V3 350 kW handshake + Plaid tri-motor + Cybertruck +steer-by-wire + 8-camera autopilot calibrate + 15 engine variants +(Model S/3/X/Y/Cybertruck Cyberbeast + Roadster 2 + Semi + Robotaxi); +HU gens incl. AMD Ryzen + HW3/HW4 FSD computers + AI5 Cybercab. + +**Rivian** — Driver+ Enhanced Highway hands-off + R1 8-mode coding +(All-Purpose + Conserve + Sport + All-Terrain + Rock Crawl + Rally + +Drift + Soft Sand) + Camp Mode auto-level + V2L outlets + Gear +Tunnel coordinator + Tank Turn (legacy R1 hardware) + 12 engine +variants (R1T/R1S Quad Gen 1 Bosch / Gen 2 in-house + Tri Motor +Performance + R2/R3 + EDV); Quad-motor demo + Gear Tunnel test + +auto-level routines. + +**Lucid** — DreamDrive Pro 8-bit (Highway Assist + auto lane change ++ intelligent speed + Surround View + Smart Summon + Reverse Summon) ++ Wunderbox 8-bit (19.2 kW AC + 350 kW DC + V2L 9.6 kW + V2H + V2G + +ISO 15118 PnC + Plug & Charge + NACS adapter) + Sapphire tri-motor +1217 hp + Glasshouse canopy (Gravity) + 14-camera DreamDrive +calibrate + RacePak track telemetry + 9 engine variants (Air Pure/ +Touring/Grand Touring/Sapphire + Gravity Dual/Grand Touring/Sapphire ++ midsize Earth platform); 900V architecture metadata. + +**BYD** — Blade Battery LFP + Cell-to-Body integration + Super +e-Platform 1000V Flash Charge (10C / 1MW DC) + Yangwang quad-motor +e4 platform 9-bit (e4 quad + tank turn + floating mode + 3-wheel +drive limp home + crab-walk + jumping DiSus-A + DiSus-A/C/P +intelligent body control) + DiPilot City + 18 engine variants +(DM-i 1.5/2.0 PHEV + DM-o off-road PHEV Bao 5/8 + Atto 3/Seal/ +Dolphin/Han/Tang EV + Yangwang U7/U8/U9 quad-motor + Denza N7/D9 + +Song L + Seal 07). + +**NIO** — Power Swap 9-bit (BaaS subscription + 75 kWh LFP Power Up +Lite + 100 kWh ternary + 150 kWh semi-solid + Flexible swap any +size + Power Swap 4.0 station + V2G Charge & Discharge + lifetime +swap counter) + NAD with 33 sensors (Aquila + Adam) + NIO Pilot Plus ++ Navigate on Pilot Plus + City Pilot + NOMI in-car AI 5-bit (NOMI +Mate LLM + expressive face + voice only + English voice) + SkyRide +active suspension (ET9 900V) + Executive Class lounge seats + +Banyan 2.0 LLM-native OS + 11 engine variants (ES8/ES6/ES7/EC6/EC7/ +ET5/ET7/ET9 900V + ONVO L60 + Firefly + EVE). + +**XPeng** — XNGP / XPILOT 4.0 9-bit (XPILOT 2.5/3.0/4.0 tiers + +XNGP Highway/City/map-free + VPA Memory Park + ACC) + X9 4-wheel +steering with steer-by-wire 5-bit (crab-walk + U-turn + narrow park) ++ S5 Flash-Charge 480 kW 5C 6-bit (Robotic charging arm + 800V SiC + +silicon carbide inverter) + LeDar lidar + AeroHT eVTOL flying-car +module + Iron humanoid robot interface + XOS Tianji 5.0 + Orin XNPU +compute + 10 engine variants (P7/P5/G3i/G6/G9/X9 4WS/G7/MONA M03 + +AeroHT X2 flying car). + +Estimated ~18-22% ODIS coverage per OEM — proprietary CAN protocols +limit ceiling. NIO benefits from Power Swap public docs; BYD from +e-Platform 3.0 forums; Tesla from extensive community reverse +engineering. + +## [3.51.0] - 2026-05-08 — Tier 2 (5 OEMs) pushed to public-source ceiling (~22-25%, 24,921 entries) + +Volvo Cars, Polestar, Subaru, Mazda, Nissan/Infiniti, Mitsubishi +brought up to depth using community sources (VIDA/DiCE, SSM, M-MDS, +CONSULT III+, MUT-III). Built via shared parameterized library to +keep depth pattern consistent with the prior 10 OEMs. + +### catalogs/{volvo,polestar,subaru,mazda,nissan,mitsubishi}.json + +| OEM | Entries | ECUs | DIDs | Routines | Coding | Adapts | Acts | Live | DTC ext | +|---|---|---|---|---|---|---|---|---|---| +| Volvo | 5,034 | 127 | 2,078 | 108 | 28 (241 fields) | 68 | 126 | 39 | 2,460 | +| Polestar | 4,921 | 127 | 1,970 | 107 | 27 (242 fields) | 66 | 125 | 39 | 2,460 | +| Subaru | 4,925 | 131 | 1,961 | 111 | 29 (251 fields) | 68 | 126 | 39 | 2,460 | +| Mazda | 4,960 | 122 | 2,008 | 111 | 28 (245 fields) | 66 | 126 | 39 | 2,460 | +| Nissan | 5,015 | 125 | 2,058 | 109 | 29 (250 fields) | 68 | 127 | 39 | 2,460 | +| Mitsubishi | 4,987 | 127 | 2,030 | 109 | 28 (250 fields) | 68 | 126 | 39 | 2,460 | + +#### Brand-specific captures + +**Volvo** — Pilot Assist + Care Key max-speed limiter + Four-C +continuously controlled chassis + IntelliSafe + Connected Safety + +EX90 LiDAR + dual-chamber air suspension + integrated child boosters; +17 engine variants (B4204T turbo + Drive-E + B6304T V6 + B8444S V8 +Yamaha legacy + EV P2/P3/EX30/EX90/ES90); Pilot Assist 9-bit coding +(hands-off warn + emergency stop + oncoming lane mit + run-off road +mit), Care Key max-speed adaptation 50-210 km/h. + +**Polestar** — Performance Pack OTA unlock (+25 kW) + Öhlins DFV +manually adjustable damping + Akebono brakes + front Brembo + gold +seat belts + 50/50 dual-motor split + Track telemetry recorder; 11 +engine variants (Polestar 1 PHEV 3-motor + Polestar 2/3/4/5/6 incl. +BST 270/230 + 800V Polestar 5/6); Performance Pack 9-bit coding + +Öhlins calibrate routine. + +**Subaru** — EyeSight stereo camera (9-bit: ACC + Pre-Collision Brake ++ Lane Keep + Sway Warn + Lane Departure + Throttle Mgmt + Emergency +Lane Keep + DriverFocus) + DriverFocus distraction mitigation + DCCD +Driver Controlled Center Diff (Auto / Auto- / Auto+ / Manual modes) ++ X-MODE 4-mode coding (Snow/Dirt + Deep Snow/Mud + Hill Descent + +Normal) + Symmetrical AWD + STARLINK; 11 engine variants (EJ257 + +FA20DIT + FA24F WRX 2022 + FB20/25 + FA20 BRZ + e-Boxer + Solterra). + +**Mazda** — Skyactiv-X SPCCI Spark-Controlled Compression Ignition +calibrate + GVC Plus G-Vectoring Control + Kinematic Posture Control ++ i-Activsense (9-bit: Smart Brake Support + Distance Recognition + +Mazda Radar Cruise + Lane Dep + LKA + BSM + RCTA + DAA + Cruising +Traffic Support) + Wankel Range Extender (MX-30 R-EV); 15 engines +(Skyactiv-G/X/D + Inline-6 3.3 turbo/PHEV/diesel CX-60/70/90 + e- +Skyactiv R-EV + EZ-6 EV). + +**Nissan/Infiniti** — ProPILOT Assist 2.0 hands-off (9-bit) + Navi- +link + e-4ORCE 5-mode coding + e-Pedal Step / one-pedal drive + +Direct Adaptive Steering DAS + Intelligent Around-View; 15 engines +(VR30DDTT Q50 Red Sport + VR38DETT GT-R + VC-Turbo 2.0 variable +compression + VC-Turbo 1.5 3-cyl + e-POWER serial hybrid + Leaf Plus ++ Ariya e-4ORCE); VC-Turbo compression-ratio relearn routine. + +**Mitsubishi** — S-AWC Super All-Wheel Control 8-bit (Tarmac/Gravel/ +Snow/Mud + AYC Active Yaw Control + ASC + Sport) + Outlander PHEV +9-bit (EV priority + save + charge + V2H CHAdeMO + V2L 1500W + +twin-motor 4WD + Power Drive electric AWD + target save SOC) + Twin +Clutch SST 6-DCT (Evo X) + DCCD-style legacy; 14 engines (4B11T Evo +X + 4G63T Evo IX + 4B40T Eclipse Cross + Outlander PHEV motors + +i-MiEV legacy). + +Estimated ~22-25% ODIS coverage per OEM — at the realistic +SSM/M-MDS/CONSULT III+/MUT-III/VIDA-community ceiling. + +## [3.50.0] - 2026-05-08 — Stellantis (14 brands) pushed to public-source ceiling (~30% ODIS, 5754 entries) + +Same depth-pattern as the prior 9 OEMs, applied to Stellantis via +wiTech / Mopar / Multiecuscan + FCA-PSA forums. Covers all 14 brands +(Chrysler/Jeep/Dodge/Ram/Fiat/Alfa Romeo/Maserati/Peugeot/Citroën/ +Opel/Vauxhall/DS/Lancia + Ducati on commercial side) since they share +wiTech topology post-merger. + +### catalogs/stellantis.json — 28 → 5,754 entries + +| Section | v3.39 | v3.50 | +|---|---|---| +| ECUs | 8 | **166** | +| DIDs | 20 | **2,633** | +| Routines | 0 | **159** | +| Coding blocks | 0 | **34 (322 fields)** | +| Adaptations | 0 | **81** | +| Actuator tests | 0 | **168** | +| Live PIDs | 0 | **53** | +| DTC extended-data | 0 | **2,460** | + +#### ECUs (+158) +wiTech bus map covering ICE + 4xe PHEV + STLA Large/Medium/Frame BEV ++ Ramcharger REEV: powertrain (ECM + bank-2 V8 Hemi split + ZF 8HP/ +9HP TCM + transfer + HV battery + front+rear MCU + OBC + LDC + VCMS +STLA + front+rear motor + **SRT/Demon/Hellcat drive mode coordinator** ++ active exhaust + **Launch Control + Line Lock** + active engine +mount Quadrifoglio), chassis (ABS + SAS + EPS + EPB + TPMS + ORC + +occupancy + Active Park + **Bilstein Adaptive Damping + Active Roll +Control Quadrifoglio + rear-axle steering Maserati + Q4 torque +vectoring Alfa + Quadra-Lift air suspension Jeep/Ram + iBooster + +Trailer Sway Control**), **Jeep off-road suite** (Selec-Terrain + +Wade Sensing Wrangler/Gladiator + SelecSpeed Crawl + HDC + NV245 +transfer + **Tru-Lok rear+front diff lock Rubicon** + **electronic +sway bar disconnect Rubicon**), ADAS (master + Forward Facing Camera ++ Forward Facing Radar + rear radar L+R + 4 corner radars + Surround +View 4-cam + Driver Status Monitor + **Night Vision Wagoneer/Grand +Wagoneer** + sonar + traffic sign), body (CGW + BCM + Uconnect 5 +cluster + dual ATC + 4 doors + 4 seats Wagoneer Executive Class +24-way + 2 mirrors + steering column + 2 sliding doors Pacifica + +liftgate + pano + convertible Wrangler/Spider/124 + fuel/charge flap ++ **frunk BEV** + **power tonneau Ram/Gladiator**), **Pixel LED +headlights** + LED tails + welcome signature, **Uconnect 5 +Snapdragon** + passenger display Wagoneer + 2 rear displays + ** +McIntosh / Harman Kardon / Alpine / Sonus Faber Maserati** premium +amp + tuner + 5G TCU + eCall + SiriusXM Guardian, Passive Entry + +SKIM + alarm + tilt + glass-break, HV/EV thermal (heat pump + PTC + +HV scroll compressor + battery heater + chiller + valve block + 2× +aux pumps + **Range Extender Ramcharger / Jeep 4xe REEV**), heated +steering + 4× seat climate + HUD + ambient + Qi + 2× massage + auto- +wiper + **N95 cabin air filter Pacifica** + ionizer, trailer Tow +Package + power hitch + Defender aux battery + **center console +fridge** + **Power Running Boards Ram/Wagoneer** + **RamBox** +lockable + Trailer view camera + **multifunction tailgate Ram** + +bed lights, OTA + HSM + Ethernet + 5 domain controllers + Car2X + +**Uconnect / Mopar Owner app gateway** + DAB+/HD Radio + Face Connect ++ **UWB Phone-as-Key**. + +#### DIDs (+2,613) +- 26 generic UDS DIDs incl. Mopar part no, FCA calibration, wiTech ID, brand code (CHR/JEE/DOD/RAM/FIA/ALF/MAS/PEU/CIT/OPL/VAU/DS/LAN). +- 85 ECM engine DIDs (RPM/torque/MAP/MAF/lambda B1+B2, 2× turbo + **supercharger rpm Hellcat/Demon + supercharger boost**, 2× wastegate, **MDS state + count + active minutes Hemi 4-cyl deact**, VVT intake+exhaust B1+B2, idle, drive mode 9 modes, ISS, **Launch Control + Line Lock count Demon + Red Eye/Demon mode flag**, max-RPM/speed/oil-temp/G lifetime, **DPF soot + regen count + distance since regen + active regen**, **DEF/AdBlue + remaining km + NOx in/out**, **4xe PHEV charge mode + EV distance + Hybrid distance + eTorque 48V assist + state**, SRT chiller water temp). +- 64 per-cylinder (1-8) — Hemi V8 + V6 Quadrifoglio + 4xe V6. +- 256 engine variant DIDs — 32 Stellantis engines × 8 fields (Hemi 5.7 / 6.4 392 / 6.2 Hellcat / Hellcat Redeye / **Demon 170 V8** / Hurricane 3.0 SO+HO I6 / Pentastar 3.6 V6 + eTorque + 4xe / EcoDiesel V6 / Alfa 2.0/2.2/2.9 V6 Quadrifoglio / 1.3/1.4/2.4 MultiAir / **Maserati Nettuno V6 Twin Combustion** / V8 / PSA PureTech 1.0/1.2/1.6 + hybrid / BlueHDi 1.5/2.0 / Opel 1.4T / PHEV 1.6 PSA / **STLA Large single/dual/Banshee SRT** / **STLA Medium e-3008/e-Avenger** / **STLA Frame Wagoneer S** / STLA Smallcar e-208/Corsa-e). +- 240 transmission variants — 15 trans gens × 16 fields (ZF 8HP50/70/75/95 + 9HP48 + EAT8/EAT6 PSA + 6-DCT + 6MT + STLA front/rear reducer + **2-speed Charger Daytona Banshee** + NV245/Rock-Trac/Quadra-Drive II transfers). +- 64 Uconnect head-unit gens (Uconnect 4 + 5 + PSA NAC + Alfa/Maserati Connect) × 16 fields. +- 41 ABS + chassis DIDs (Adaptive Damping state + ARC state + rear steer angle + 4× ride height + **4× Quadra-Lift pressure + compressor + mode 5-position Aero/Normal/OR1/OR2/Park** + Q4 split + transfer split + low-range + **front+rear Tru-Lok state + sway bar disconnect state** + wade depth + max-safe wade + HDC + crawl + Selec-Terrain mode). +- 64 per-wheel ABS/TPMS (4 × 16) + sensor IDs + camber/toe. +- 33 HV battery DIDs incl. **800V architecture STLA Large** + chemistry + pyrofuse + lifetime charged/discharged. +- 192 per-cell V (STLA Large max). +- 256 per-module (32 × 8 fields). +- 38 motor/MCU DIDs (front + rear, 19 each, incl. **PowerShot active Charger Daytona + overboost remaining**). +- 31 OBC + VCMS DIDs incl. **V2L active/kW/lifetime + REEV runtime + REEV fuel burned + REEV target SOC** (Ramcharger). +- 35 ADAS (Adaptive Cruise + Stop & Go + Lane Keep + AEB + Highway Assist + **Hands-Free Active Drive Level 3 STLA AutoDrive** + Intersection Assist + DSM + Blind Spot + Rear Cross Path + Surround View + **Trail Camera Wrangler/Gladiator** + Night Vision + Active Park + remote parking + **Trailer Reverse Steering Control** + Swerve). +- 96 ADAS object stack (12 × 8). +- 38 cluster + **Performance Pages** (lap timer + best 0-60/0-100/QM + best 60-0 braking + max long/lat G + **pitch + roll + wheel articulation + altitude + max wade + off-road minutes + low-range minutes + diff-lock minutes + sway-disconnect minutes + Launch + Line Lock + PowerShot + Drift mode minutes + Track minutes**). +- 256 last-32-trip × 8. +- 80 driver coaching (20 × 4 windows incl. off-road + wade + PowerShot). +- 46 per-bulb hours (Pixel LED + welcome + race-track brake + aux off-road + bed lights). +- 32 ambient zones. +- 64 per-key (8 × 8) incl. Passive Entry + **UWB Phone-as-Key**. +- 36 per-camera (9 × 4) incl. Trail + Night Vision IR. +- 60 premium audio incl. **15-band parametric EQ × 3** (gain/freq/Q). +- 132 per-ECU programming (33 × 4). +- 52 Uconnect/SiriusXM Guardian/Mopar Owner subscription incl. **Free2move EV Route Planner + Free2move Charge + Mopar Owner app + Uconnect Market in-car commerce + SiriusXM with 360L**. +- 20 bus topology (C/B/Diag/Chassis CAN + LIN + CAN-FD + Ethernet + AVB + SOME/IP). +- 32 quad-zone HVAC + 96 user profiles + 104 service history (26 × 4 incl. ATF + Tru-Lok diff oil + supercharger oil + DPF + DEF) + 43 vehicle metadata (incl. **SRT + Quadrifoglio + Trail Rated + Rubicon + TRX + DT + Rebel + brand 14 codes + STLA platform 4 codes + 4xe PHEV + eTorque + V2L + Mopar pack**). + +#### Routines (+159) | Coding blocks (34 / 322 fields) | Adaptations (81) | Actuator tests (168) | Live PIDs (53) | DTC ext (2,460) +Engine adapt resets + **MDS relearn + DPF forced regen + DEF priming + Launch + Line Lock arm + Red Key/Demon unlock + supercharger test + eTorque init**, ZF + EAT8 + Quick Learn, transfer + Q4 adapts, per-wheel ABS + SAS + yaw zero + TPMS + EPB workshop, **Bilstein Adaptive Damping + ARC + rear-axle steer + Quadra-Lift + ride-height + iBooster calibrations**, **Jeep off-road**: Selec-Terrain init + Wade Sensing + crawl + HDC + center diff + **front+rear Tru-Lok + sway bar disconnect tests**, BCM + window + mirror + sunroof + convertible + liftgate + sliding doors L+R + frunk + tonneau + **Power Running Boards + RamBox + multifunction tailgate** calibrations, ATC basic + heat-pump self-test, headlight aim L+R + Pixel LED calibrate, front camera + radar align + 4 corner/rear radars + Surround View + Trail Camera + Night Vision + DSM + sonar, cluster + mileage align + **Performance Pages reset**, HV cell balance + capacity + isolation + contactor + pre-charge + pyrofuse, motor resolver zero + inverter self-test, OBC + LDC + VCMS + thermal + charge flap, **Range Extender self-test Ramcharger**, exhaust flap test, 23 module-replacement procedures incl. **SKIM**, key + **SKIM relearn + Phone-as-Key UWB pair**, OTA check/install/rollback, HSM provision/zeroize, 5 domain self-tests + Ethernet + Car2X, seat init + massage calibrate + Qi + HUD + **Uconnect/SiriusXM Guardian refresh + welcome animation load + Ramcharger REEV test**. + +Coding blocks: BCM + door + alarm + **Pixel LED features** (matrix HB EU + R/T runway projection Daytona + lane lighting + intersection lighting), tail signature (Daytona Charger illuminated strip), ADAS Lane (**Hands-Free Active Drive Level 3 STLA AutoDrive**), ACC (Stop & Go + predictive + curve speed), AEB (intersection + reverse + swerve), Blind Spot + Rear Cross Path, ATC (heat pump + ionizer + N95 cabin filter Pacifica), EV charge (CCS1/2 + NACS + V2L + ISO 15118 PnC + 11/22kW AC + 200/350kW DC + 800V STLA Large + Free2move EV Route Planner), **SRT/Demon/Hellcat features** (Track + Drag + Drift + Custom + Snow + Tow + Eco + Valet + Launch + **Line Lock + Torque Reserve + Trans Brake + Red Key + PowerShot Charger Daytona + Performance Pages + G-meter**), active exhaust (legal quiet mode), **Uconnect 5 features** (CarPlay + AA + Connected + McIntosh + Harman Kardon + Alpine + Sonus Faber Maserati + passenger + rear displays Wagoneer + Uconnect Market), cluster (HUD + **Performance Pages overlay + Off-Road Pages overlay Jeep + articulation + wade depth overlays**), Passive Entry + UWB Phone-as-Key, trailer + **Trailer Reverse Steering Control** + power hitch, **Jeep off-road features** (Selec-Terrain + auto + Wade Sensing + crawl + HDC + low-range + front+rear Tru-Lok + sway bar disconnect + Trail Camera + 7 modes), Quadra-Lift + chassis (rear-wheel steer Maserati + ARC Quadrifoglio + Adaptive Damping + Q4 + Dynamic Response + Predictive Terrain Response), seat climate dr (24-way memory), sliding doors Pacifica, convertible, panoramic sunroof, HUD, Qi, DSM (gaze tracking HFAD), OTA (staged rollout), Crypto/HSM, **Uconnect/SiriusXM Guardian/Mopar Owner** (Phone-as-Key + Free2move EV Route Planner + Free2move Charge + voice + Alexa + Uconnect Market + Mopar Owner app + region NA/EU/CN/SA), **Ram truck features** (RamBox + multifunction tailgate + bed lights + cargo + trailer cameras + Power Running Boards + auto air lift + TRX off-road), frunk BEV, **Ramcharger / Jeep 4xe REEV** (Auto + EV priority + Hybrid + battery charge + save modes), N95 cabin filter, aux battery (Defender/Wrangler dual-battery + auto disconnect on low SoC). + +Adaptations: Engine (incl. **MDS default + Launch max RPM + Red Key default + exhaust flap policy**) + ZF + ABS + Adaptive Damping + Quadra-Lift heights (off-road/aero/park) + rear steer + Q4 + **Selec-Terrain default + Wade warn depth + crawl + HDC default speeds** + Lane Keep + AEB + ACC + Highway Assist + Hands-Free Drive + Pixel LED HB + welcome animation + comfort + N95 default + EV (DC/AC targets + 350kW + ISO 15118 PnC + V2L + AVAS) + **Ramcharger REEV target SOC + default mode** + OTA + HSM + massage. Actuator tests incl. **CDC 4× dampers + ARC actuators + rear-steer + 4× Quadra-Lift valves + center diff + front+rear Tru-Lok + sway bar disconnect + frunk + tonneau + Power Running Boards + RamBox + tailgate step + Pixel LED anim + supercharger test + Line Lock demo + PowerShot demo + Range Extender start/stop**. Live PIDs incl. engine + DPF + DEF + MDS state + supercharger + eTorque + HV battery + front+rear motor + OBC + V2L + REEV runtime + ADAS + chassis (pitch/roll/articulation/wade depth/Selec-Terrain mode). DTC ext: broad P/B/U/C codes × 4-6 record types incl. environmental_data + freeze_frame_template. + +Estimated ~30% ODIS coverage — at the realistic wiTech/Mopar-community ceiling. Higher coverage requires wiTech 2 dealer license. + +## [3.49.0] - 2026-05-08 — JLR (Jaguar Land Rover) pushed to public-source ceiling (~30% ODIS, 5465 entries) + +Same depth-pattern as the prior 8 OEMs, applied to JLR via SDD / +Pathfinder community + JLRTechInfo. Covers Jaguar (XE/XF/F-Pace/E- +Pace/I-Pace/F-Type) + Land Rover (Defender/Discovery/Discovery +Sport/Evoque) + Range Rover (RR/Sport/Velar/Range Rover Electric). + +### catalogs/jlr.json — 28 → 5,465 entries + +| Section | v3.39 | v3.49 | +|---|---|---| +| ECUs | 8 | **154** | +| DIDs | 20 | **2,396** | +| Routines | 0 | **150** | +| Coding blocks | 0 | **32 (303 fields)** | +| Adaptations | 0 | **75** | +| Actuator tests | 0 | **149** | +| Live PIDs | 0 | **49** | +| DTC extended-data | 0 | **2,460** | + +#### ECUs (+146) +SDD bus map covering ICE + MHEV + PHEV + BEV (I-Pace, EMA, Range +Rover Electric): powertrain (ECM + bank-2 V8 split + ZF 8HP TCM + +transfer case + HV battery + front+rear MCU + OBC + LDC + **VCMS** +EMA + front+rear motor + Dynamic Response + active exhaust flap + +Launch Control + active engine mount), chassis (ABS + SAS + EPS + +EPB + TPMS + SRS + occupancy + Park Pilot + **Adaptive Dynamics CDC ++ Active Roll Control / Dynamic Response Pro + rear-wheel steer L460 ++ Active e-Diff + 4-corner cross-linked air suspension + ride height ++ Continental MK C1 iBooster + Trailer Stability**), **Land Rover +off-road** (Terrain Response 2 + Wade Sensing + ATPC + HDC + center +diff lock + rear diff lock + 2-speed low-range), ADAS (master + front +camera + radar + rear radar L+R + 4 corner radars + **ClearSight +ground-view 360 front + rear** + **ClearSight camera mirrors L+R +Range Rover** + driver attention + **ClearSight Interior Rear-View +Mirror** + sonar + traffic sign), body (CGW + BCM + 12.3" cluster + +dual FATC + 4 doors + 4 seats Executive Class 22-way + 2 mirrors + +steering column + powered tailgate + **inner tailgate Range Rover +split** + pano roof + **F-Type convertible** + fuel/charge flap), +**Pixel LED Digital headlights** + **animated taillights** + welcome +signature, **Pivi Pro** (Snapdragon) + passenger display + 2 rear +displays Range Rover + **Meridian Signature Sound 3D** + premium amp ++ tuner + 5G TCU Pivi Connect + eCall + Stolen Vehicle Locator, +**Activity Key** waterproof wristband + smart key + immobilizer + +alarm + tilt + glass-break, HV/EV thermal (heat pump + PTC + scroll +compressor + battery heater + chiller + valve block + 2× aux pumps), +heated steering + 4× seat climate + **Hot Stone massage 4 seats** + +HUD + **CAIL ambient** + Qi + auto-wiper + **Cabin Air Purification +Pro PM2.5** + ionizer, trailer (**Advanced Tow Assist**) + power +hitch + Defender aux battery + **center console fridge**, OTA + HSM ++ Ethernet + 5 domain controllers + Car2X + InControl/Pivi Connect +TCU + DAB+/HD Radio + **Activity Key inductive charger** + UWB +**Phone-as-Key**. + +#### DIDs (+2,376) +- 25 generic UDS DIDs incl. JLR part no, calibration, SDD ID, Solihull/Halewood/Castle Bromwich/Nitra factory codes. +- 75 ECM engine DIDs (RPM/torque/MAP/MAF/lambda B1+B2, 2× turbo + **supercharger rpm 5.0L SC**, 2× wastegate, VCT intake+exhaust B1+B2, idle, drive mode 4 modes, ISS, **Launch Control state+count**, max-RPM/speed/oil-temp lifetime, full-load + overboost seconds, **DPF soot + regen count + distance since regen + active regen flag**, **AdBlue level + remaining km + NOx in/out**, **MHEV 48V belt-starter assist torque + recuperation kW + state**, PHEV charge mode). +- 64 per-cylinder (1-8) — V8 5.0 SC + BMW N63 4.4 V8 + 3.0 I6. +- 176 engine variant DIDs — 22 JLR engines × 8 fields (Ingenium 2.0 P200/P250/P300 gas + D150/D180/D200 MHEV/D240 diesel + 3.0 I6 D300/D350 MHEV diesel + 3.0 I6 P360/P400 MHEV gas + 3.0 P510e/P550e PHEV, **AJ-V8 5.0L SC + 5.0L SVR**, **BMW N63 4.4L V8 BiTurbo Range Rover**, I-Pace dual + EMA single/dual + Range Rover Electric, legacy V6/V8 TDV6/SDV8). +- 176 transmission variants — 11 trans gens × 16 fields (ZF 8HP50/70/76/95, 9HP, 6MT, I-Pace front+rear reducer, EMA 2-speed, transfer 4WD + 2-speed low-range). +- 48 head-unit gens (InControl Touch Pro Duo + Pivi Pro + Pivi Connect 5G) × 16 fields. +- 41 ABS + chassis DIDs (Adaptive Dynamics state + ARC state + rear-wheel steer angle + 4× ride height + 4× air-susp pressure + compressor + **cross-link state 4-corner** + e-Diff split + e-Diff oil temp + transfer split + low-range state + center+rear diff lock + **wade depth + max-safe wade** + HDC + ATPC + Terrain Response mode). +- 64 per-wheel ABS/TPMS (4 × 16) + sensor IDs + camber/toe. +- 33 HV battery DIDs incl. **800V architecture (EMA / Range Rover EV)** + chemistry + pyrofuse + lifetime charged/discharged. +- 144 per-cell V (EMA max). +- 256 per-module (32 × 8 fields). +- 38 motor/MCU DIDs (front + rear, 19 each). +- 25 OBC + VCMS DIDs. +- 34 ADAS (Adaptive Cruise + Intelligent Cruise + Lane Keep + Highway Assist + Steering Assist + AEB + driver condition + Blind Spot + Rear Traffic + **ClearSight Ground View** + **ClearSight Interior Rear-View Mirror** + Park Pilot + remote parking + intersection + swerve + Trailer Reverse Assist). +- 96 ADAS object stack (12 × 8). +- 33 cluster + **off-road telemetry** (lap timer + best 0-60/0-100/QM + **pitch + roll + wheel articulation + altitude + max wade depth + off-road minutes + low-range minutes + diff-lock minutes + Terrain Response use distribution + ATPC distance + HDC distance**). +- 256 last-32-trip × 8. +- 80 driver coaching (20 × 4 windows incl. off-road + wade + ATPC distance). +- 44 per-bulb hours (Pixel LED + signature DRL + animated tail + welcome). +- 36 CAIL ambient zones (incl. **headliner pano + d-pillars + canopy + mood lighting**). +- 64 per-key (8 × 8) incl. **Activity Key + Phone-as-Key UWB**. +- 32 per-camera (8 × 4) incl. ClearSight mirrors + Interior Rear-View. +- 60 Meridian Signature Sound 3D incl. **15-band parametric EQ × 3** (gain/freq/Q). +- 132 per-ECU programming (33 × 4). +- 50 InControl/Pivi Connect subscription incl. **EV Route Planner + JLR Charging Service + Connected Navigation Pro + OTA feature unlock**. +- 20 bus topology (PT/Chassis/Body/Info CAN + LIN + CAN-FD + Ethernet + AVB + SOME/IP). +- 32 quad-zone HVAC + 96 user profiles + 104 service history (26 × 4 incl. ZF 8HP fluid + e-Diff + supercharger oil + DPF service + AdBlue refill) + 39 vehicle metadata (incl. **SVR + SVAutobiography + Dynamic Pack + First Edition + brand JAG/LR/RR + battery size + chemistry + DC max kW + motor count + EMA platform flag + V2X capable + Meridian Signature 3D fitted + Executive Class seats**). + +#### Routines (+150) +ECM adapt resets (idle, throttle, misfire, kat, lambda, VCT, oil-pump, +starter) + battery register + oil/inspection/brake fluid resets + +**DPF forced regen + replace reset + AdBlue priming + Launch Control +calibrate + supercharger test 5.0 SC + 48V belt-starter init**, ZF +basic + oil reset + Quick Learn, transfer + e-Diff adapt, per-wheel +ABS bleed + pump + SAS + yaw zero + TPMS relearn + EPB workshop, +**Adaptive Dynamics CDC + Active Roll Control + rear-wheel steer + +4-corner air-susp + ride-height calibrations**, iBooster, **Land +Rover off-road** (Terrain Response init + Wade Sensing calibrate + +ATPC + HDC calibrations + center diff + rear diff lock + low-range +tests), BCM + window + mirror + sunroof + **F-Type convertible top** ++ tailgate + **inner tailgate Range Rover split** calibrations, FATC +basic + heat-pump self-test, headlight aim L+R + Pixel LED Digital +calibrate + animated taillight init, front camera dynamic + static + +radar align + 4 corner/rear radars + **ClearSight 360 + camera mirrors +L+R + Interior Rear-View** calibrations + driver attention + sonar +front+rear, cluster + mileage align + **off-road telemetry reset**, +HV cell balance + capacity + isolation + contactor + pre-charge + +pyrofuse, motor resolver zero (front+rear) + inverter self-test, +OBC + LDC + VCMS + thermal + charge flap, exhaust flap test, 22 +module-replacement procedures, key + immobilizer relearn + **Activity +Key wristband pair + Phone-as-Key UWB pair**, OTA check/install/ +rollback, HSM provision/zeroize, 5 domain self-tests + Ethernet + +Car2X, seat init dr+pa + **Hot Stone calibrate** + Qi + HUD calibrate, +**InControl/Pivi Connect refresh + welcome animation load**. + +#### Coding blocks (32 / 303 fields) +BCM general (UWB approach + walk-away), door extended (**flush door +handles Range Rover + auto-extend at speed**), alarm zones, **Pixel +LED Digital features** (Digital LED HD + matrix HB + dynamic signature +DRL + lane lighting + intersection lighting + country-specific), +animated taillight, ADAS Lane (Highway + Emergency Assist), Adaptive +Cruise (Intelligent Cruise Stop&Go + predictive + curve speed + +**Connected Navigation ACC**), AEB (intersection + reverse + swerve), +Blind Spot + Rear Traffic + **ClearSight Interior Rear-View auto**, +FATC (heat pump + ionizer + **Cabin Air Purification Pro PM2.5**), +EV charge (CCS1/2 + NACS + V2G/V2L/V2H + ISO 15118 PnC + 22kW AC + +200/350kW DC + **800V EMA / Range Rover Electric** + EV Route +Planner), Sport/Dynamic mode (**Dynamic Pro SVR + Track mode F-Type ++ Predictive Speed Control**), active exhaust (legal quiet mode), +**Pivi Pro features** (CarPlay + AA + InControl + Meridian Signature +3D + Meridian Premium + passenger display + rear displays Range +Rover), cluster (HUD + sport layout F-Type + **off-road layout LR + +articulation overlay + wade depth overlay**), smart key + Activity +Key + UWB, trailer + **Advanced Tow Assist** (knob steering), +**off-road features Land Rover** (Terrain Response 2 + auto + Wade +Sensing + ATPC + HDC + low-range + center+rear diff lock + ClearSight +Ground + articulation + pitch & roll indicators + 7 modes incl. rock +crawl), air suspension + chassis (cross-linked 4-corner + rear-wheel +steer + ARC + Adaptive Dynamics + e-Diff + Dynamic Response Pro + +Predictive Terrain Response), driver seat climate + **Hot Stone +massage** (Relaxation mode), F-Type convertible, panoramic sunroof, +HUD (AR overlay), Qi, driver attention (gaze tracking), OTA (staged +rollout), Crypto/HSM (debug locked), **InControl / Pivi Connect** +(Phone-as-Key + EV Route Planner + JLR Charging Service + voice + +Alexa + region NA/EU/CN/UK), **ClearSight features** (Ground View + +Interior Rear-View + camera mirrors + 360 + trailer view + transparent +hood), tow hitch, **Cabin Air Purification Pro** (PM2.5 + ionizer + +CO2 management + auto-recirc on smog), fridge / cool box. + +#### Adaptations (75) + Actuator tests (149) + Live PIDs (49) + DTC ext (2,460) +Engine (incl. **Launch max RPM + active exhaust policy + ISS min +coolant**) + ZF + ABS + Adaptive Dynamics + air-susp (off-road/ +access/loading heights) + rear steer + e-Diff + **Terrain Response +default + Wade warn depth + ATPC default speed + HDC default speed** ++ Lane Keep + AEB + ACC + Highway Assist + Pixel LED HB + welcome +animation + comfort + Cabin Air Pro + EV (DC/AC targets + 350kW + +ISO 15118 PnC + AVAS) + OTA + HSM + Hot Stone + off-road camera auto; +full per-actuator tests incl. **CDC 4× dampers + ARC actuators + rear- +steer + 4× air-susp valves + center+rear diff lock + low-range engage ++ inner tailgate Range Rover split + Pixel LED anim L+R + Hot Stone ++ supercharger test + Activity Key charger + fridge**; live engine + +DPF + AdBlue + MHEV + supercharger + HV battery + front+rear motor + +OBC + ADAS + chassis (ride heights + **pitch + roll + articulation + +wade depth + Terrain Response mode**) PIDs; broad P/B/U/C codes × +4-6 record types incl. environmental_data + freeze_frame_template. + +Estimated ~30% ODIS coverage — at the realistic SDD/Pathfinder- +community ceiling. Higher coverage requires Pathfinder dealer license. + +## [3.48.0] - 2026-05-08 — HMG (Hyundai/Kia/Genesis) pushed to public-source ceiling (~28% ODIS, 5537 entries) + +Same depth-pattern as the prior 7 OEMs, applied to HMG via GDS / KDS +community + E-GMP forums. Covers all three brands (Hyundai, Kia, +Genesis) since they share GDS topology and most ECUs. + +### catalogs/hmg.json — 30 → 5,537 entries + +| Section | v3.39 | v3.48 | +|---|---|---| +| ECUs | 8 | **153** | +| DIDs | 22 | **2,481** | +| Routines | 0 | **145** | +| Coding blocks | 0 | **32 (295 fields)** | +| Adaptations | 0 | **76** | +| Actuator tests | 0 | **145** | +| Live PIDs | 0 | **45** | +| DTC extended-data | 0 | **2,460** | + +#### ECUs (+145) +GDS bus map covering ICE + HEV + PHEV + E-GMP EV + N Performance: +powertrain (EMS + TCM + HTRAC AWD + HCU + HV battery + front+rear +MCU + OBC + LDC + **VCMS** E-GMP 800V + front+rear motor + Sport +mode + N exhaust flap + N Launch + active engine mount), chassis +(ESC + SAS + MDPS + EPB + TPMS + SRS + occupancy + RSPA + ECS + ARC +Genesis + rear-axle steer G90/EV9 + e-LSD + air suspension G90 + ride +height + Mando iBooster), ADAS (HDA/HDA2 master + front camera + radar ++ rear radar L+R + 4 corner radars + 4-cam SVM + DAW + BVM L+R ++ front+rear sonar + traffic sign), body (CGW + IPM + LCD/OLED 12.3" +cluster + dual FATC + 4 doors + 4 seats Genesis + 2 mirrors + steering +column + 2 sliding doors Carnival + tailgate + pano sunroof + fuel/ +charge flap + **frunk** EV9/Ioniq), **HD Matrix LED** Genesis IMA + +**pixelated taillights** + welcome pixel, ccNC head unit + passenger +display + 2 rear displays + B&O/Lexicon/Meridian/Krell amp + +Bluelink/Kia Connect/GCS TCU + 5G TCU + eCall + SVR, SMK + immobilizer ++ alarm + tilt + glass-break, HV/EV (ITMS thermal + heat pump + PTC ++ HV scroll compressor + battery heater + chiller + valve block + +2× aux pumps + **motor disconnector**), aux comfort (heated steering ++ 4× seat climate + AR-HUD Genesis + 64-color ambient + Qi + 2× Ergo +Motion massage + auto-wiper + fragrance + ionizer/fine dust filter), +trailer + power tow hitch, **V2L converter + V2X bidirectional +inverter**, OTA + HSM + Ethernet + 5 domain controllers + Car2X + +**Bluelink TCU app gateway + DAB+/HD Radio + fingerprint + Face +Connect GV60 + Digital Key 2.0 UWB**. + +#### DIDs (+2,459) +- 26 generic UDS DIDs incl. HMG part no, calibration, GDS ID, KDM/USA/EUR/GEN region. +- 74 EMS engine DIDs (RPM/torque/MAP/MAF/lambda B1+B2, 2× turbo speed, 2× wastegate, **CVVT intake+exhaust B1+B2 + CVVD state Smartstream**, idle, drive mode 5 modes incl. Smart, **ISG (Idle Stop&Go) count**, **N Launch + NGS / N Power Shift counters**, max-RPM/speed/oil-temp lifetime, **DPF soot + regen count + distance since regen**, **SCR urea + remaining km + NOx in/out**). +- 64 per-cylinder (1-8) — Tau V8 G80/G90. +- 224 engine variant DIDs — 28 HMG engines × 8 fields (Kappa 1.0T, Gamma 1.2/1.4T/1.6T/1.6T-N, Nu 2.0/2.0T/2.0T-N, Theta3 2.5T, Smartstream 2.5T/2.5T-N, Smartstream R 2.2 + U3 1.6 diesel, Lambda V6 3.0/3.3T/3.5T-N/3.8 NA, Tau V8 4.6/5.0, HEV Smartstream 1.6/1.8 + 1.5 PHEV, **E-GMP RWD/AWD/N/Long Range/Standard Range**). +- 176 transmission variants — 11 trans gens × 16 fields (6/8/10AT, 7-DCT dry/wet, 8-DCT wet, IVT CVT, 6MT, **E-GMP 1-speed reducer + 2-speed Ioniq 5 N/EV6 GT**, HTRAC transfer). +- 64 head-unit gens (AVN 5/ccIC/**ccNC Snapdragon**/Genesis) × 16 fields. +- 29 ESC chassis DIDs (ECS state + ARC state + rear steer angle + 4× ride height + 4× air-susp pressure + e-LSD split + e-LSD oil temp + HTRAC torque split). +- 64 per-wheel ABS/TPMS (4 × 16) + sensor IDs + camber/toe. +- 33 HV battery DIDs incl. **800V architecture flag + chemistry NMC/LFP + pyrofuse + lifetime charged/discharged kWh**. +- 192 per-cell V (E-GMP max 192 cells). +- 256 per-module (32 × 8 fields). +- 38 motor/MCU DIDs (front + rear, 19 each, incl. **disconnector state + N Grin Boost active + overboost remaining**). +- 32 OBC + VCMS DIDs incl. **400V boost** (E-GMP) + **V2L active/kW/session/lifetime + V2G active**. +- 36 ADAS + HDA2 (HDA / HDA2 hands-on lane change / curve speed + ISLA + DAW drowsy warnings + BCA + RCCA + RSPA + Safe Exit + Junction Turning Assist + Navi-based ACC + highway auto lane change). +- 96 ADAS object stack (12 × 8). +- 32 cluster + **N-mode telemetry** (lap timer, best 0-60/0-100/QM, total launches, **N Grin Boost + Drift Optimizer counters + N e-shift + N Active Sound+ mode**). +- 256 last-32-trip × 8. +- 80 driver coaching (20 × 4 windows incl. grin boost + drift optimizer). +- 43 per-bulb hours (matrix + parametric + pixel tail + welcome pattern). +- 32 ambient zones (incl. **sound-mood lighting**). +- 64 per-key (8 × 8) incl. SMK + **Digital Key 2.0 UWB**. +- 36 per-camera shading (9 × 4) incl. BVM L+R. +- 60 premium audio (B&O / Lexicon / Meridian / Krell) incl. **15-band parametric EQ × 3** (gain/freq/Q). +- 132 per-ECU programming (33 × 4). +- 48 Connect (Bluelink + Kia Connect + GCS) subscription incl. **EV Route Planner + Charge myHyundai/Kia Charge + Genesis Lounge**. +- 20 bus topology (B/C/P/M-CAN + LIN + **CAN-FD** + Ethernet + AVB + SOME/IP). +- 32 quad-zone HVAC + 96 user profiles (6 × 16) + 96 service history (24 × 4 incl. DCT clutch + e-LSD + alignment) + 38 vehicle metadata (incl. **N Performance + N Line + Genesis Designs + Genesis Lounge member + battery size + chemistry + DC max kW + motor count + E-GMP platform flag + V2L/V2G capable + brand HYU/KIA/GEN**). + +#### Routines (+145) +EMS adapt resets (idle, throttle, misfire, kat, lambda, **CVVT, CVVD +Smartstream**) + oil/inspection/brake fluid resets + **DPF forced regen ++ DPF replace reset + SCR/DEF priming** + **N Launch calibrate + N +track init**, TCM + DCT clutch + kiss-point, HTRAC + e-LSD adapt, per- +wheel ABS bleed + pump + SAS + yaw zero + TPMS relearn + EPB workshop, +**ECS + ARC + rear-axle steer + air-suspension calibrations**, IPM + +window/mirror init + sunroof + sliding doors L+R + tailgate + frunk +calibrate, FATC basic + heat-pump self-test, headlight aim L+R + HD +Matrix LED + pixelated taillight init, front camera dynamic + static ++ radar align + 4 corner/rear radars + SVM + BVM L+R + DAW + sonar +front+rear, cluster + mileage align + N telemetry reset, HV cell +balance + capacity + isolation + contactor + pre-charge + pyrofuse, +motor resolver zero (front+rear) + inverter self-test (front+rear), +OBC + LDC + **VCMS** + thermal loop + charge flap, **disconnector + +V2L + V2X self-tests**, N exhaust flap test, 21 module-replacement +procedures, key + immobilizer relearn + **Digital Key 2.0 pair + +fingerprint enroll + Face Connect enroll**, OTA check/install/rollback, +HSM provision/zeroize, 5 domain self-tests + Ethernet + Car2X, seat +init dr+pa + Ergo Motion calibrate + Qi + HUD calibrate, **Bluelink/ +Kia Connect refresh + welcome pixel animation load**. + +#### Coding blocks (32 / 295 fields) +IPM general (UWB approach unlock + walk-away lock), door extended +(flush handles Genesis), alarm zones, **HD Matrix LED features** +(IFS + IMA HD matrix + parametric DRL pixels + lane lighting + +intersection lighting + country-specific patterns), pixelated +taillight (parametric pixels Ioniq 5), ADAS Lane (LFA + LKA + LDW + +HDA + HDA2 hands-on), SCC + Navi ACC (predictive + curve speed + ISLA), +FCA (pedestrian + cyclist + JTS intersection + reverse PCA-R + evasive +steering), BCA + RCCA + BVM + Safe Exit Assist, FATC features (heat +pump + ionizer + fine dust filter + fragrance), EV charge features +(CCS1/2 + NACS + V2G/V2L/V2H + ISO 15118 PnC + 800V E-GMP + 22kW AC + +240/350kW DC + E-Route Planner), **N Performance features** (Grin +Boost + e-shift + Drift Optimizer + Active Sound+ + N Pedal + Track +SENSE Shift + Battery pre-condition + Launch Control + Power Shift + +lap timer + G-meter + track data export), N exhaust (legal quiet +mode), **ccNC head unit features** (CarPlay + AA + Bluelink/Kia +Connect + Bang & Olufsen + Lexicon Genesis + Meridian Kia + Krell + +passenger display + rear displays G90/EV9), cluster (HUD + AR-HUD +Genesis + OLED Genesis + N track mode + G-meter), SMK + UWB Digital +Key 2.0 (relay attack protection), trailer (power hitch), HTRAC + +chassis (HTRAC AWD + e-LSD + ECS + ARC + rear-axle steer + air susp +G90 + sport/snow/mud/sand/terrain modes), Ergo Motion (relaxation +mode Genesis), sliding doors (Carnival), pano sunroof, HUD (AR +Genesis), Qi, DAW (gaze tracking HDA2), OTA (staged rollout), Crypto/ +HSM, **Bluelink/Kia Connect/GCS** (EV Route Planner + Charge myHyundai/ +Kia Charge + voice assistant + region NA/EU/CN/KDM), frunk (Ioniq/ +EV9), tow hitch, fragrance Genesis, **fingerprint + Face Connect** +(GV60), **V2L/V2G features** (interior outlet + charge port adapter ++ V2H home backup + auto start on load detect). + +#### Adaptations (76) + Actuator tests (145) + Live PIDs (45) + DTC ext (2,460) +Engine (incl. **N Launch max RPM + N exhaust flap policy + ISG min +coolant**) + TCM + ESC + ECS + air-susp + rear steer + e-LSD + HTRAC ++ HDA2 + SCC + ISLA + matrix HB + pixel tail + comfort + EV (DC/AC +targets + 350kW DC + ISO 15118 PnC + V2L default) + OTA + HSM + +**N drive mode default + N torque distribution + N Active Sound + +fingerprint default + Face Connect default + Ergo Motion default + +fragrance + ionizer auto**; full per-actuator tests incl. **ECS 4× +dampers + ARC actuators + rear-steer + 4× air-susp valves + N Launch +demo + V2L outlet test + fingerprint + Face Connect tests + DPF +forced regen 60s**; live engine + DPF + SCR + HV battery + front+rear +motor + OBC + V2L + ADAS + chassis (ride heights, G long+lat) PIDs; +broad P/B/U/C codes × 4-6 record types incl. environmental_data + +freeze_frame_template. + +Estimated ~28% ODIS coverage — at the realistic GDS/KDS-community +ceiling. Higher coverage requires GDS Mobile or KDS Tester license. + +## [3.47.0] - 2026-05-08 — Porsche pushed to public-source ceiling (~38% ODIS, 5446 entries) + +Same depth-pattern as the prior 6 OEMs, applied to Porsche via PIWIS- +community + 911uk + Rennlist + shared VW Group ODX (Porsche shares +PPE/J1/MEB platforms with Audi/VW). Higher ceiling than Toyota/Honda +because Porsche shares platforms with VW Group (which we already +mapped at ~50% — Group ODX leakage benefits Porsche). + +### catalogs/porsche.json — 41 → 5,446 entries + +| Section | v3.39 | v3.47 | +|---|---|---| +| ECUs | 8 | **138** | +| DIDs | 33 | **2,423** | +| Routines | 0 | **141** | +| Coding blocks | 0 | **31 (278 fields)** | +| Adaptations | 0 | **74** | +| Actuator tests | 0 | **139** | +| Live PIDs | 0 | **40** | +| DTC extended-data | 0 | **2,460** | + +#### ECUs (+130) +PIWIS topology spanning ICE + hybrid + EV variants: powertrain +(DME + bank-2 split for V8, PDK/Tiptronic, AWD PTU, HV battery, +front+rear PCU, OBC, DC-DC, 800V DC charger, front+rear e-motor, +Sport Chrono, Sport exhaust flap, Launch Control coordinator, active +engine mount), chassis (ABS+PSM, SAS, Servotronic EPS, EPB, TPMS, +SRS, occupancy, ParkAssist, PASM, PDCC, rear-axle steer, PTV+, air +suspension, ride height, PCCB ceramic brake, iBooster), ADAS +(coordinator + InnoDrive, front camera + radar, rear radar L+R, +4× corner radar, 360-camera × 4, driver monitor, Night Vision Assist, +front+rear sonar, traffic sign), body (CGW + BCM + cluster + dual +HVAC + 4 doors + 4 seats Panamera + 2 mirrors + steering column + +convertible top + active rear spoiler + active front lip + tailgate ++ pano roof + fuel/charge flaps L+R), Matrix LED HD headlights L+R + +OLED taillights L+R, IVI (PCM + passenger display + 2 rear displays ++ Burmester 3D amp + premium amp + tuner + 5G TCU + eCall + Porsche +Vehicle Tracking), Kessy + immobilizer + alarm + interior motion + +tilt + glass-break, HV/EV (thermal + heat pump + PTC + HV refrigerant +compressor + battery heater + chiller + valve block + 2× aux pumps), +aux comfort (heated steering + seat climate dr+pa + HUD + ambient + +Qi + 2× massage + auto-wiper + fragrance + ionizer), trailer + power +hitch, zonal (OTA + HSM + Ethernet switch + 5 domain controllers + +Car2X + My Porsche TCU app gateway). + +#### DIDs (+2,390) +- 27 generic UDS DIDs incl. Porsche part no, calibration, PIWIS ID, factory. +- 72 DME engine DIDs (RPM/torque/MAP/MAF/lambda B1+B2, 2× turbo speed, 2× wastegate, VVT intake+exhaust B1+B2, idle, drive mode 4 modes, Launch Control state+count, **6 overrev band counters** (911 trademark), max-RPM/speed/oil-temp lifetime, full-load + overboost seconds). +- 64 per-cylinder (1-8) — V8 Cayenne/Panamera Turbo S. +- 192 engine variant DIDs — 24 Porsche engines × 8 fields (MA1/MA2 flat-6, 9A1/9A2 NA GT3 flat-6, MA2 flat-4 718, V6 BiTurbo, V6 turbo Cayenne/Macan, V8 BiTurbo Panamera/Cayenne, V6+V8 TDI legacy, PHEV V6+V8, T-Hybrid GTS, Taycan dual-motor, Taycan Turbo GT S Plate, Macan EV PPE, Macan EV Turbo). +- 160 transmission variants — 10 trans gens × 16 fields (7+8 PDK, Tiptronic, 6+7MT, 2-speed Taycan rear, 1-speed Taycan front, 2-speed Macan EV, transfer 4S, PTV+). +- 48 PCM head-unit gens (PCM 5/6/7) × 16 fields. +- 29 chassis DIDs (ABS+PSM + PASM state + PDCC state + rear steer angle + 4× ride height + 4× air suspension pressure + compressor + PTV+ split). +- 64 per-wheel ABS/TPMS (4 × 16 incl. PCCB disc temp + caliper temp + lifetime + camber + toe). +- 30 HV battery DIDs incl. 800V architecture flag + chemistry + pyrofuse + coolant in/out. +- 216 per-cell (108 × 2) for Taycan/Macan EV. +- 264 per-module (33 × 8 fields). +- 32 motor/PCU DIDs (front + rear, 16 each). +- 30 OBC DIDs incl. 800V booster + max session kW + charge curve. +- 35 ADAS + InnoDrive (predictive cruise + Night Vision + Emergency Assist + Lane Change + Side Assist L+R + intersection + swerve). +- 96 ADAS object stack (12 × 8). +- 28 cluster + Sport Chrono (lap timer, best 0-60/0-100/QM, total laps, total track minutes, launches). +- 256 last-32-trip × 8. +- 80 driver coaching (20 × 4 windows). +- 40 per-bulb hours (matrix + OLED + ambient). +- 31 ambient zones. +- 64 per-key (8 × 8) incl. UWB + Digital Key. +- 32 per-camera (8 × 4) incl. Night Vision IR. +- 60 Burmester 3D audio incl. **15-band parametric EQ × 3 (gain/freq/Q)**. +- 120 per-ECU programming (30 × 4). +- 48 Connect Plus subscription incl. **Function on Demand** (rear steer, PASM, PDCC, InnoDrive, matrix HB). +- 20 bus topology (Comfort/PT/Chassis/Info CAN + LIN + FlexRay + Ethernet + AVB + SOME/IP). +- 32 quad-zone HVAC + 96 user profiles (6 × 16) + 96 service history (24 × 4 incl. PDK clutch + timing chain + alignment) + 39 vehicle metadata (incl. **Weissach + lightweight + aero kit + carbon roof + PTS Exclusive Manufaktur paint** + battery size + chemistry + max DC kW + motor count + overboost). + +#### Routines (+141) +DME idle/throttle/misfire/kat/lambda/VVT/oil-pump/starter adapts + +**overrev counter clear** + **Track Mode init** + **Launch Control +calibrate**, PDK basic + clutch adapt + kiss-point, AWD + PTV+ adapt, +per-wheel ABS bleed + pump test + SAS + yaw zero + TPMS relearn + +EPB workshop, **PASM + PDCC + rear-axle steer + air-suspension + +ride-height calibrations**, PCCB warm-up + iBooster, BCM + window + +mirror + sunroof + convertible top + tailgate + active rear spoiler + +active front lip calibrations, A/C basic + heat-pump self-test, matrix +LED HD calibrate + OLED taillight init, front camera dynamic + static ++ radar align + 4 corner/rear radars + 360-camera + driver monitor + +Night Vision calibrations, cluster + mileage align + Sport Chrono +reset, HV cell balance + capacity + isolation + contactor + pre-charge ++ pyrofuse, motor resolver zero (front+rear) + inverter self-test +(front+rear), OBC + DC-DC + 800V DC charger + thermal loop + charge +flaps L+R, exhaust flap test, 21 module-replacement procedures, key +pairing + immobilizer relearn, OTA check/install/rollback, HSM +provision/zeroize, 5 domain self-tests + Ethernet + Car2X, seat init +dr+pa + massage calibrate + Qi + HUD calibrate, **My Porsche refresh ++ Function on Demand activate/deactivate**. + +#### Coding blocks (31 / 278 fields) +BCM general (UWB approach unlock + walk-away lock), door extended, +alarm zones (transport + garage modes), Matrix LED HD features +(**HD matrix 84k px + lane lighting + intersection lighting + pothole +warning projection**), OLED taillight, ADAS Lane (Emergency Assist), +ACC + InnoDrive (predictive ACC + speed limit assist), AEB +(intersection + reverse + swerve), Side Assist (Lane Change + +exit warning), A/C (heat pump + ionizer + fragrance), EV charge +(CCS1/2 + NACS + V2G/V2L/V2H + ISO 15118 PnC + 800V architecture + +22kW AC + 270/350kW DC), **Sport Chrono** (steering dial + Track +Precision app + Launch Control + PSM Sport + lap timer + video +overlay), Sport exhaust (legal quiet mode), PCM (CarPlay + AA + +Connect Plus + Burmester 3D + passenger display + rear displays), +cluster (HUD + Sport Chrono overlay + G-meter overlay + Track mode), +Kessy (UWB + Digital Key + relay attack protection), trailer (power +hitch), AWD/PASM/PDCC, driver seat climate (heat + vent + massage + +memory), convertible, panoramic roof, HUD (AR overlay), Qi, driver +monitor, OTA (staged rollout), Crypto/HSM (debug locked), **My +Porsche / Connect Plus + Function on Demand** (rear steer, PASM, +PDCC, InnoDrive, matrix HB unlocks + region NA/EU/CN), aero/spoilers +(active rear + front lip + diffuser), tow hitch, fragrance, massage. + +#### Adaptations (74) + Actuator tests (139) + Live PIDs (40) + DTC ext (2,460) +Engine (incl. **overrev clear + Launch Control max RPM + Track Mode +default + exhaust flap policy**) + PDK + ABS + PASM modes + air-susp +heights + rear steer angle + PTV+ + InnoDrive + ACC + matrix HB + +OLED + comfort + EV (DC/AC targets + 270kW DC + ISO 15118 PnC + +overboost + AVAS) + OTA + HSM + Sport Chrono + Launch + massage + +fragrance defaults; full per-actuator tests incl. **PASM 4× dampers ++ PDCC actuators + rear-steer + 4× air-susp valves + PCCB warm-up + +Launch Control demo**; live engine + HV battery + front+rear motor + +OBC + ADAS + chassis (ride heights, G long+lat) PIDs; broad +P/B/U/C codes × 4-6 record types incl. environmental_data + +freeze_frame_template. + +Estimated ~38% ODIS coverage — at the realistic PIWIS-community +ceiling. Higher coverage requires a PIWIS Tester license (Porsche +proprietary). + +## [3.46.0] - 2026-05-08 — Honda pushed to public-source ceiling (~26% ODIS, 4108 entries) + +Same depth-pattern as VW/BMW/Ford/Mercedes/Toyota, applied to Honda +via HDS-public + Honda-Tech community sources. Public ceiling is +moderate because HDS is closed but the community has decent coverage +of Honda SENSING + i-MMD hybrid + IMA legacy + Acura platforms. + +### catalogs/honda.json — 36 → 4,108 entries + +| Section | v3.39 | v3.46 | +|---|---|---| +| ECUs | 8 | **98** | +| DIDs | 26 | **1,782** | +| Routines | 10 | **110** | +| Coding blocks | 0 | **28 (207 fields)** | +| Adaptations | 0 | **64** | +| Actuator tests | 0 | **93** | +| Live PIDs | 0 | **33** | +| DTC extended-data | 0 | **1,900** | + +#### ECUs (+90) +HDS bus map: powertrain (ECM, TCM, IMA/IPU, MOT i-MMD, Battery ECU, +PCU, OBC, DC-DC), chassis (ABS+VSA, SAS, EPS, EPB, TPMS, SRS, OPDS +occupant), Honda SENSING (CMBS coordinator, FCW camera, FCW radar, +BLIS L+R, CTA, LKAS/parking, Multi-View Camera, driver attention), +body (BCM, CGW, multiplex, IC, A/C dual + aux, 2 seats + memory, +4 doors, 2 mirrors, tilt+telescope, 2 sliding doors Odyssey, power +tailgate, sunroof + pan roof), lighting (adaptive headlights L+R + +aim), IVI (Display Audio + nav + HondaLink TCU + amp + ELS Studio +Acura + TV + sat radio), Smart Entry + immobilizer + alarm, hybrid/EV +(brake booster HEV + regen brake + thermal + heat pump + DC/AC charge +inlets + LV battery), aux (auto wiper, mirror, HomeLink, Qi, heated +steering, seat climate dr+pa, HUD, gesture Acura), AWD (Real-Time / +SH-AWD), trailer + tow hitch + running boards, zonal (OTA, HSM, +Ethernet switch, 5 domain controllers, Car2X). + +#### DIDs (+1,756) +- 21 generic UDS DIDs incl. Honda part number, calibration, HDS ID. +- 50 ECM engine DIDs (RPM, torque, MAP/MAF/lambda, fuel rail, VTEC state, VTC intake+exhaust B1+B2, knock retard, EGR, wastegate, intercooler, cat efficiency B1+B2, idle, ECON mode, brake booster, drive mode 3 modes). +- 48 per-cylinder (cyl 1-6 × 8) — V6 J35. +- 144 engine variant DIDs — 18 Honda engines × 8 fields (K20C1 Type R, L15B7/BZ turbo, K24W/Z, J35Y/Z/A, J32A, K20A2/Z, LFA1, LFB1, L13B Fit hybrid, K20A9 Si, 3.0L V6 i-MMD, K20C5, EV motor Honda 0/Prologue). +- 160 transmission variants — 10 trans × 16 fields (5AT/6AT/9AT/10AT, CVT X-1/L4, i-MMD eCVT, 9DCT NSX/TLX, 6MT Type R/Si, transfer SH-AWD). +- 64 head-unit gens — Display Audio 8/9, Honda Connect, Acura Premium × 16 fields. +- 13 ABS+VSA DIDs + 32 per-wheel ABS/TPMS. +- 25 HV battery DIDs + 192 per-cell (96 × 2) + 72 per-module (12 × 6). +- 14 Motor/PCU (i-MMD) DIDs. +- 21 OBC charging DIDs incl. ISO 15118 + Plug & Charge. +- 24 Honda SENSING ADAS (camera + radar + ACC + LKAS + CMBS + RDM + AHB + Traffic Jam + Low-Speed Follow). +- 64 ADAS object stack (8 × 8). +- 15 cluster + iMMD power-flow + 256 last-32-trip + 64 driver-coaching. +- 31 per-bulb hours-on. +- 24 ambient zones. +- 64 per-key data (8 × 8) incl. Smart Entry + Digital Key. +- 24 per-camera lens shading (6 × 4). +- 19 ELS Studio audio + 7-band EQ. +- 96 per-ECU programming (24 ECUs × 4). +- 30 HondaLink subscription metadata (15 × 2). +- 11 bus topology (B-CAN + F-CAN + chassis CAN + LIN + Ethernet). +- 24 per-zone HVAC (3 × 8) + 64 user profiles + 64 service history (incl. valve adjust + timing chain) + 26 vehicle metadata (incl. Si/Type R + Acura A-Spec/Advance). + +#### Routines (+100) +ECM adapt resets (idle, throttle, misfire, kat aging, lambda trim, VTC), +oil pump + starter + battery register, oil/inspection/brake fluid/air +filter resets, alternator + compression + valve adjust + timing chain +inspection, TCM basic + Quick Learn, AWD adapt, per-wheel ABS bleed + +pump test + SAS + yaw zero, TPMS relearn, EPB workshop mode, BCM init, +window/mirror init, sunroof, power tailgate + sliding doors L+R, A/C +basic + compressor + heat-pump tests, headlight aim L+R + AFS, Sensing +camera dynamic + static + radar alignment, BLIS L+R + Multi-View + +driver camera + park assist calibrations, IC service reset + mileage +align, HV cell balance + capacity + isolation + contactor + pre-charge, +Motor resolver zero + inverter self-test, OBC + DC-DC + thermal loop + +charge door, hybrid brake booster, 13 module-replacement procedures, +key pairing/deletion + immobilizer relearn, OTA check/install/rollback, +HSM provision/zeroize, 5 domain self-tests + Ethernet + Car2X tests, +seat init dr+pa, wireless charging + HUD calibrate, HondaLink refresh. + +#### Coding blocks (28 / 207 fields) +BCM general, door extended, alarm zones, lighting features (Full LED + +adaptive + welcome/leaving choreography), Honda SENSING Lane (LKAS + +LDW + RDM + Traffic Jam Assist), ACC (Low-Speed Follow + curve speed), +CMBS (pedestrian + cyclist + intersection + reverse), A/C features +(plasmacluster + heat pump + auto demist), EV charge features (CCS1/2 ++ NACS + V2G/V2L/V2H + ISO 15118), Hybrid features (i-MMD: EV/ECON/ +Normal/Sport + regen paddle + predictive EV), head-unit (CarPlay + +AA + HondaLink + Alexa Built-in + ELS Studio), cluster features (IMA +power-flow + driver coaching), Smart Entry (Digital Key + walk-away), +trailer module, AWD (Real-Time + SH-AWD + Intelligent Traction +Management), driver seat climate, sliding doors (Odyssey), panoramic +roof, HUD, wireless charging, BLIS, driver attention, OTA, Crypto/HSM, +HondaLink (Security + Remote + Driver Score + Honda Driver + AcuraLink +Premium), power tailgate, tow hitch, power running boards. + +#### Adaptations (64) + Actuator tests (93) + Live PIDs (33) + DTC ext (1,900) +Engine + trans + ABS+VSA + TPMS + Honda SENSING + lighting + climate + +EV/hybrid + comfort + OTA + HSM + HondaLink defaults; full per-actuator +tests; live engine + HV battery + motor + OBC + Sensing PIDs; broad +P/B/U/C codes × 4-6 record types incl. environmental_data + +freeze_frame_template. + +Estimated ~26% ODIS coverage — at the realistic HDS-community ceiling +for Honda. Higher coverage requires commercial HDS / J2534 ODX-D +licenses. + +## [3.45.0] - 2026-05-08 — Toyota pushed to public-source ceiling (~30% ODIS, 5157 entries) + +Same depth-pattern as VW/BMW/Ford/Mercedes, applied to Toyota via +Techstream-public + HSD community sources. Public ceiling is moderate +because Techstream is closed but the HSD (Hybrid Synergy Drive) + +TSS (Toyota Safety Sense) communities have decent coverage. + +### catalogs/toyota.json — 21 → 5,157 entries + +| Section | v3.39 | v3.45 | +|---|---|---| +| ECUs | 8 | **117** | +| DIDs | 16 | **1,860** | +| Routines | 5 | **124** | +| Coding blocks | 0 | **31 (254 fields)** | +| Adaptations | 0 | **79** | +| Actuator tests | 0 | **104** | +| Live PIDs | 0 | **38** | +| DTC extended-data | 0 | **2,804** | + +#### ECUs (+109) +Full Techstream bus map: powertrain (ECM, TCM, HV ECU, MG ECU, +Battery ECU, Inverter ECU, OBC, DC-DC), chassis (ABS+VSC, SAS, EPS, +EPB, TPMS, SRS, occupant, pre-collision/TSS coordinator), TSS sensors +(front camera, front radar, BSM L+R, RCTA, ICS Park Assist, Panoramic +View Monitor, driver attention), body (BCM, CGW, multiplex, IC, +A/C amp + aux, 2 power seats + memory, 4 doors, mirrors, tilt+telescope, +2 sliding doors for Sienna/Alphard, power back door, sunroof + pan +roof), lighting (AFS L+R, headlight aim), IVI (Display Audio + nav ++ DCM telematics + amp JBL/Mark Levinson + TV + sat radio + AR navi +Lexus), Smart Key + immobilizer + keyless entry + alarm, hybrid/EV +(Power Management + EV charging + plug-in + brake booster HEV + regen +brake + thermal + heat pump + DC/AC charge inlets + charge door + LV +battery + wallbox iface), aux/convenience (auto wiper, mirror compass, +auto-dim, HomeLink, Qi wireless, heated steering, seat climate dr+pa, +massage Lexus, HUD, AR HUD, gesture, executive rear, kinetic seat), +4WD/off-road (AWD, A-TRAC/Crawl Control, KDSS, AHC, AHCS, trailer, +tow hitch, running boards, cargo, power back window), zonal (OTA, +HSM, Ethernet switch, 5 domain controllers, Car2X, V2X bZ4X). + +#### DIDs (+1,844) +- 21 generic UDS DIDs incl. Toyota part number, calibration, Techstream ID. +- 60 ECM engine DIDs (RPM, torque, coolant/oil, MAP/MAF/lambda, fuel rail, VVT-i intake+exhaust B1+B2, knock retard, EGR, wastegate, DPF, SCR NOx + DEF/AdBlue, turbo, intercooler, cat efficiency B1+B2, idle, immobilizer auth, brake booster active, eco score, drive mode 5 modes, e-boost). +- 64 per-cylinder (cyl 1-8 × 8) — V8. +- 184 engine variant DIDs — 23 Toyota engine families × 8 fields (2GR-FE/FXS/FKS/8GR-FXS, 2AR-FE/FXE, 8AR-FTS, A25A-FKS/FXS, M20A-FKS/FXS, 1NR-FKE, 1NZ-FXE, 3ZR-FAE, 1UR-FE, 3UR-FE, 1VD-FTV, 1GD-FTV, 2GD-FTV, V35A-FTS, T24A-FTS, 1ZR-FE, e-TNGA EV). +- 128 transmission variants — 8 trans × 16 fields (ECT 8AT, UC60E/AA80F/UC70 10AT, AA10F 10AT Tundra, eCVT THSII, eCVT DLSII, transfer case). +- 96 head-unit gens — Entune/Entune2/Entune3/Toyota Audio Multimedia/Lexus Interface/Display Audio × 16 fields. +- 14 ABS DIDs + 32 per-wheel ABS/TPMS. +- 30 HV battery DIDs + 192 per-cell (96 × 2) + 72 per-module (12 × 6). +- 22 motor/inverter DIDs (MG1 + MG2). +- 22 OBC charging DIDs incl. ISO 15118 + CHAdeMO + Plug & Charge. +- 23 TSS ADAS DIDs (camera + radar + DRCC + LTA + PCS + AHB + RSA + Toyota Teammate). +- 64 ADAS object stack (8 × 8). +- 16 cluster + ASSYST + 256 last-32-trip + 64 driver-coaching. +- 31 per-bulb hours-on (BiBeam segments). +- 24 per-zone ambient lighting. +- 64 per-key data (8 × 8) incl. Smart Key UWB + Digital Key. +- 24 per-camera lens shading (6 × 4). +- 19 premium audio (JBL/Mark Levinson) + 7-band EQ. +- 96 per-ECU programming history (24 ECUs × 4). +- 30 Toyota Connected subscription metadata (15 × 2). +- 16 bus topology (V-bus + body + powertrain + chassis CAN + LIN + AVC-LAN + MOST + Ethernet). +- 24 per-zone HVAC (3 zones × 8). +- 64 user profiles + 64 service history + 26 vehicle metadata (incl. TRD + Lexus F SPORT). + +#### Routines (+119) +ECM adapt resets, DPF + DEF + glow plug, TCM basic + Quick Learn, AWD +adapt, per-wheel ABS bleed + pump test + sensor zeros, TPMS relearn + +sensor replacement, EPB workshop mode, BCM init + window/mirror init + +sunroof/pan roof/power back door/sliding doors L+R/tow hitch/running +boards calibrations, A/C basic + compressor + heat-pump tests, headlight +aim L+R + AFS + BiBeam pixel test, TSS camera dynamic + static + radar +alignment, BSM L+R + park assist + Panoramic View + driver camera +calibrations, IC service reset all + mileage align, HV cell balance + +capacity remeasure + isolation + pyro + contactor + pre-charge, MG1+MG2 +resolver zero + offset + inverter self-test, OBC + DC-DC + thermal loop +bleed + EV charge door, hybrid brake booster + regen brake calibrate, +13 module-replacement procedures, CIG coding program, key pairing + +immobilizer + Smart Key relearn, OTA check/install/rollback, HSM +provision/zeroize, 5 domain self-tests + Ethernet switch + Car2X tests, +massage calibrate + seat init dr+pa, wireless charging + HUD calibrate, +Toyota Connected subscription refresh. + +#### Coding blocks (31 / 254 fields) +BCM general (CIG customisation), door extended (Smart Door auto-open), +alarm zones, lighting features (BiBeam LED + Triple Beam + BladeScan +AHS Lexus), TSS Lane Tracing (LTA + LDA + Emergency Steering + curve +speed reduction), DRCC extended (Stop & Go FSR + curve speed adapt + +lane change), Pre-Collision (pedestrian day+night + cyclist + intersection ++ reverse + Emergency Steering), A/C zone (S-Flow + nanoe X + heat pump), +EV charge features (CCS1/2/CHAdeMO/NACS/V2G/V2L/V2H/ISO15118), Hybrid +features (5 modes + regen paddle B-mode + predictive EV), head-unit +(Hey Toyota + Cloud Nav + Intelligent Assistant + Alexa), cluster (HSI +Hybrid System Indicator + driver coaching), Smart Key (UWB + Digital +Key + foot-open + walk-away), trailer module, Crawl Control + Multi- +Terrain Select (snow/dirt/sand/rock/mud/auto), driver seat climate +(kinetic seat Lexus), panoramic roof, OTA features, Crypto/HSM, Toyota +Connected (Safety Connect + Service Connect + Destination Assist + +Cloud Nav + Intelligent Assistant + Digital Key + Driver Score + Teen +Driver Tech), Toyota Teammate L2 (highway hands-free + max 60 urban), +HUD, wireless charging, Blind Spot Monitor, driver attention, Advanced +Park (memory + remote + trailer backup), power back door, AWD features +(Active Torque + E-Four hybrid AWD + DAC + HAC), power sliding doors +(Sienna/Alphard), tow hitch, power running boards. + +#### Adaptations (79) + Actuator tests (104) + Live PIDs (38) + DTC ext (2,804) +Engine + trans + ABS+VSC + TPMS + TSS + lighting + climate + EV/hybrid ++ Smart Key + OTA + HSM defaults; full per-actuator tests; live engine ++ HV battery + MG1/MG2 + OBC + TSS + cluster PIDs; broad P/B/U/C codes +× 4-6 record types incl. environmental_data + freeze_frame_template. + +Estimated ~30% ODIS coverage — at the realistic Techstream/HSD community +ceiling for Toyota. Higher coverage requires commercial Techstream +ODX-D licenses. + +## [3.44.0] - 2026-05-08 — Mercedes-Benz pushed to public-source ceiling (~35% ODIS, 6134 entries) + +Same depth-pattern proven on VW + BMW + Ford, applied to Mercedes-Benz. +Public ceiling is lower than VW/BMW/Ford because XENTRY/DAS is more +closed than VCDS/E-Sys/FORScan, but Vediamo/Carly community sources + +SCN coding documentation give a solid ~30-35% baseline. + +### catalogs/mercedes.json — 60 → 6,134 entries + +| Section | v3.39 | v3.44 | +|---|---|---| +| ECUs | 16 | **148** | +| DIDs | 32 | **2,101** | +| Routines | 10 | **164** | +| Coding blocks | 0 | **40 (327 fields)** | +| Adaptations | 0 | **94** | +| Actuator tests | 0 | **130** | +| Live PIDs | 0 | **41** | +| DTC extended-data | 0 | **3,416** | + +#### ECUs (+132) +Full XENTRY/DAS bus map: powertrain (ME engine, ETS 722.x trans, +secondary ME for V12, EMM front+rear motor for EQS/EQE/EQA/EQB/EQC, +VRM HV battery, OBC, DC-DC), chassis (ESP, SBC, ABC, AIRMATIC, AMG +Ride Control+, EHPS, LWS, PARKTRONIC, EPB, TPM, ETD, ABA Active +Brake Assist, ATC, Dynamic Select, rear-axle steering, sport diff), +body (SAM-F + SAM-R, CGW, IC, EZS, DAS, KG, OFV, BUA, AHW, AHE, +KLIMA, IHKU rear climate, 4 doors, dr+pa+rear seats + memory, +mirrors, FFL/Multibeam/DIGITAL LIGHT L+R, RFL, ILS), audio/IVI +(COMAND, MBUX, Burmester, TV, sat radio, TCU, GPS, MHI, RSE, +2 rear screens, MBUX Hyperscreen passenger), ADAS (camera main + +4 surround, driver attention, gesture control, front radar, +4 corner radars, ultrasonic F+R, Night View Plus, Speed Limit +Assist, Traffic Sign Assist, Cross-Traffic Alert, PRE-SAFE), EV +(SOBDMC rear, EMM rear, thermal, battery junction, charge inlets, +heat-pump compressor, wallbox iface, charge door, LV battery +monitor + management, charging planner, V2X, Car2X), zonal (OTA, +HSM, Ethernet switch TSN, 5 domain controllers), comfort premium +(wireless charging, HUD, AR HUD, 4 massage modules, Air-Balance +perfume, PURIFY ionizer, panoramic roof, Magic Sky electrochromic, +convertible top, AIRCAP, AIRSCARF, Maybach Executive Rear, exhaust +flap, sound actuator, trailer, electric tow hitch, power running +boards, cargo management, power trunk, power glovebox, chilled +cup-holder, fragrance pump). + +#### DIDs (+2,069) +- 23 generic UDS DIDs incl. Mercedes-specific SCN coding fields, EZS state, DAS lock state. +- 64 ME engine DIDs (RPM, torque, coolant/oil/MAP/MAF/lambda, HP+LP fuel rail, camshaft B1+B2, knock retard, EGR, wastegate, DPF deep, SCR NOx + AdBlue, turbo, intercooler, cat efficiency B1+B2, alternator, idle, drive authorisation, DAS, eco factor, e-boost). +- 96 per-cylinder DIDs (cyl 1-12 × 8) — supports M275/M279/M285 V12. +- 200 engine variant DIDs — 25 Mercedes engine families × 8 fields (M139/M177/M178/M256/M254/M260/M264/M266/M270/M271/M272/M273/M274/M275/M276/M277/M278/M279/M285, OM642/OM651/OM654/OM656/OM629/OM606). +- 128 transmission variants — 8 trans × 16 fields (722.6/722.9/725.0 9G-Tronic/724.0 DCT/AMG MCT/AMG DCT/EV single-speed/4MATIC transfer). +- 96 head-unit gens — NTG 4/5/5.5/6/7 + Hyperscreen × 16 fields. +- 14 ESP DIDs + 32 per-wheel + 32 per-corner suspension (AIRMATIC + ABC + AMG Ride). +- 28 VRM DIDs + 216 per-cell (108 cells × 2: V + T) + 72 per-module (12 × 6) — EQS/EQE pack depth. +- 21 EMM front motor + 9 EMM rear motor. +- 22 OBC DIDs incl. ISO 15118 + Plug & Charge. +- 24 ADAS Distronic/Active Distance + Active Steering + Emergency Stop + PRE-SAFE + Night View + Speed Limit + Traffic Sign + Active Brake. +- 64 ADAS object stack (8 × 8). +- 15 IC + ASSYST PLUS index + 256 last-32-trip + 64 driver-coaching. +- 32 per-bulb hours-on incl. multibeam segments + AIRSCARF. +- 30 per-zone Active Ambient (64-color premium) + trim strips. +- 64 per-key extended (8 × 8) incl. Keyless Go UWB + digital key. +- 24 per-camera lens shading (6 × 4). +- 40 per-radar waveform (5 × 8). +- 22 Burmester audio fine-grained + 4D resonator + 7-band EQ. +- 96 per-ECU programming + signature (24 ECUs × 4). +- 36 Mercedes me Connect subscription metadata (18 features × 2). +- 16 bus topology (CAN-B/C/D/E + LIN + FlexRay + MOST + Ethernet). +- 64 per-bank engine deep (2 banks × 32). +- 32 per-zone HVAC (4 zones × 8). +- 64 user profiles (4 × 16). +- 64 service history per-item (16 items × 4). +- 27 vehicle metadata (FA-style + AMG package + designo + Maybach). + +#### Routines (+154) +Engine adaptation resets, DPF + SCR + AdBlue + grid heater + secondary +air, ETS basic setting + clutch adapt + Quick Learn (9G-Tronic) + +oil filling, 4MATIC transfer adapt, per-wheel ESP bleed + pump test + +basic setting + sensor zeros, AIRMATIC height calibration + lift/lower +sets, ABC + AMG Ride calibration, EPB workshop mode, rear-axle steering +calibration, SAM init + window/mirror init + sunroof/panoramic/Magic Sky +calibrations, KLIMA basic + compressor + heat-pump + aux heater + +fragrance pump priming + perfume + ionizer tests, multibeam pixel calib +L+R, DIGITAL LIGHT calib L+R, ILS/AFS, ADAS dynamic + static camera +calib, FRR alignment zero, side radar L+R calib, surround camera + +driver attention + gesture + Night View Plus + AR HUD calib, IC service +reset all + ASSYST PLUS relearn, VRM cell balance + capacity + isolation ++ pyro + contactor + pre-charge tests, EMM resolver zero + offset + +inverter self-test, OBC + DC-DC + heat-pump self-tests, EV charge door +calibrate, 16 module-replacement procedures, SCN coding program + +offline, key pairing + DAS/EZS relearn + Keyless Go relearn, OTA +check/install/rollback, HSM provision/zeroize/log export, 5 domain +self-tests, ethernet switch + Car2X self-tests, wireless charging +calibrate, me Connect subscription refresh, AMG (Track Pace reset, +Drift Mode setup, sport diff calibrate, exhaust flap calibrate, launch +relearn), comfort (massage calibrate dr+pa, seat init, AIRSCARF + AIRCAP ++ tow hitch + running boards + glovebox + trunk tests, chilled cup-holder), +network topology rediscover. + +#### Coding blocks (40 / 327 fields) +SAM general, door extended, alarm zones, Multibeam/DIGITAL LIGHT +features, camera Lane Keep extended, Distronic Plus extended, Active +Brake Assist extended, KLIMA zones (4-zone + ionizer + perfume + AIRSCARF ++ heat pump + auto demist + solar comp), EV charge features (CCS1/CCS2/ +NACS/CHAdeMO/V2G/V2L/V2H/ISO15118/smart grid/scheduled/solar/Acceleration +Increase), EV thermal strategy, MBUX features (Hyperscreen + AR Nav + Hey +Mercedes + CarPlay/AA wired+wireless + 5GHz + video + zero layer + rear +screens + passenger screen), IC features, AMG Drive (8 modes + drift), +sport diff, exhaust flap, driver massage (vitalisation/relaxation/warming ++ kinetic seat + hot stone), driver seat climate (multicontour + dynamic +bolsters), panoramic roof + Magic Sky, tow hitch, Keyless Go (UWB + +digital key + kick-to-open + walk-away + approach), OTA, Crypto/HSM, me +Connect features bitmap, AIRMATIC (E-Active Body Control + lift + +loading + kneel), trailer module, HUD, Maybach Executive Rear (recline + +footrest + table + displays + audio + massage), convertible top +(AIRCAP + AIRSCARF), wireless charging, gesture control, driver attention, +blind spot (Active + exit warning + trailer extended), running boards, +power glovebox, power trunk, AR HUD, premium comfort bundle, AIRSCARF, +AIRCAP, Drive Pilot L3 (HW + activated + country + highway + max 60). + +#### Adaptations (94) + Actuator tests (130) + Live PIDs (41) + DTC ext (3,416) +Engine + trans + ESP + AIRMATIC/ABC + TPMS + ADAS + lighting + KLIMA + +EV + comfort + OTA + HSM defaults; full per-zone + per-actuator tests; +broad P/B/U/C codes × 4-6 record types incl. environmental_data + +freeze_frame_template. + +Estimated ~35% ODIS coverage — at the realistic Vediamo/Carly community +ceiling for Mercedes-Benz. Higher coverage requires commercial XENTRY +ODX-D licenses. + +## [3.43.0] - 2026-05-08 — Ford pushed to public-source ceiling (~40% ODIS, 7397 entries) + +Two combined passes lift Ford from 47 entries (post-v3.39 baseline) to +7,397 — same depth-pattern proven on VW + BMW. + +### v3.42 first pass (47 → 4,388 entries) + +| Section | v3.39 | v3.42 | +|---|---|---| +| ECUs | 18 | 115 | +| DIDs | 35 | 1,733 | +| Routines | 12 | 132 | +| Coding blocks | 0 | 37 (318 fields) | +| Adaptations | 0 | 88 | +| Actuator tests | 0 | 111 | +| Live PIDs | 0 | 40 | +| DTC extended-data | 0 | 2,132 | + +#### ECUs (+97) +Full Ford FDRS bus map: powertrain (PCM, TCM, secondary PCM, SOBDMC +electric powertrain front+rear, BECM HV battery, OBC, DC-DC), ABS + +abs pump + EPB, RCM (restraints), IPC + cluster secondary, BCM, GWM ++ NGWM next-gen gateway, CGEA gateway legacy, APIM (SYNC HU), FCIM, +FDIM, 4 door modules (DDM/PDM/RDM/RPDM), SCCM + PSCM steering, ACM + +amplifier B&O, TBM + GPSM + CMR telematics, OCS occupant, DSM driver +status monitor, DASCM driver assist, HCM L+R headlamps, AHM aux +heater, FEPS+REPS parking sensors, SOBD-APS active park steering, +TPMS, OFCM object fusion, SRM side radar L+R, FDM front radar, OFM +object fusion master, IPSM image processing surround, SODL+SODR side +object detection, ODLM diagnostic lighting, FCLM/RCLM/CLMU climate +loop, HTM heated tailgate, HTW heated trailer wiring, aerodynamic +shutter, EV charger door, APCM accessory power, CDCM convertible, +SGSM smart glass, R-DRLM rear DRL, DRCM door receiver UWB, RSEM rear +seat entertainment, headrest motor, massage modules dr+pa, seat +climate dr+pa, sunroof + panoramic roof, cargo management, power +running boards, Tow Tech package, Pro Power Onboard inverter, frunk, +tailgate step, mega console, Co-Pilot360, BlueCruise hands-free, BLIS, +CTA, PLC powerline charger, V2G Intelligent Backup Power, OTA +controller, cybersecurity HSM, Ethernet switch, 5 domain controllers, +wireless charging, HUD. + +#### DIDs (+1,698) +- 23 generic UDS DIDs (F1xx) — ECU serial, part #, HW/SW/boot version, calibration ID + CVN, supplier, prod date+plant, name, strategy + calibration parts, tear tag, OASIS + Ford diagnostic IDs, reset count, operating hours, supply voltage, internal temp, CPU load, RAM/Flash free. +- 60 PCM (engine) — Ford-specific telemetry incl. RPM/torque/coolant/oil/MAP/MAF/lambda, HP+LP fuel rail, VCT intake+exhaust B1+B2, knock retard, EGR position, wastegate duty, DPF deep, SCR NOx + DEF, turbo speed/inlet/outlet, intercooler, cat efficiency B1+B2, alternator load, IMRC, eco score, drive mode (10 Ford modes incl. Tow/Slippery/Sand/Mud/Trail/RockCrawl/Baja). +- 80 per-cylinder (cyl 1-10 × 8 fields) — supports V8/V10 (Godzilla 7.3, PowerStroke 6.7). +- 112 engine variant DIDs — 14 Ford engine families × 8 fields (1.5/2.0/2.3/2.7/3.0/3.5 EcoBoost + 3.5 H.O., Coyote 5.0L, Predator 5.2L, Godzilla 7.3L, PowerStroke 3.0L+6.7L, hybrids). +- 128 transmission variants — 8 trans × 16 fields (10R80, 10R140 diesel, 8F35, 8F57, 6F35, eCVT hybrid, 6DCT250, transfer case). +- 64 SYNC head-unit gens — SYNC 2/3/4/5 × 16 fields each (HW/SW, map, SSD, RAM, SoC temp, OTA, voice/nav/media engine versions). +- 14 ABS DIDs + 32 per-wheel ABS/TPMS (4 wheels × 8: speed, pad wear, disc thickness, pad temp, tire pressure+temp+target, offset). +- 29 BECM (HV battery) — pack V/A, SOC/SOH, max/min/avg cell V, delta, max/min/avg cell temp, isolation, capacity, charge cycle counts, thermal events, pyro+contactors, module + cells per module count. +- 192 per-cell battery (96 cells × 2: voltage + temperature). +- 72 per-module battery (12 modules × 6: V, A, max+min temp, SOC, SOH). +- 21 SOBDMC electric powertrain (front + rear motor: torque target/actual, rpm, stator/rotor temp, inverter temp, input V/A, phase current, efficiency, resolver offset). +- 22 OBC (charging) DIDs incl. ISO 15118 state + Plug & Charge. +- 9 Pro Power Onboard inverter (V2L for Lightning) DIDs. +- 24 Co-Pilot360 + BlueCruise ADAS. +- 64 ADAS object stack (8 objects × 8). +- 14 IPC + 256 last-32-trip extended history. +- 64 driver-coaching (16 metrics × 4 windows = lifetime / 30d / 7d / last_trip). +- 32 per-bulb hours-on counters. +- 24 per-zone ambient lighting RGB. +- 64 per-key data (8 keys × 8 fields incl. MyKey active). +- 24 per-camera lens shading (6 cameras × 4). +- 40 per-radar waveform (5 radars × 8). +- 21 premium audio (B&O) per-channel + 7-band EQ. +- 96 per-ECU programming history + signature (24 ECUs × 4). +- 13 bus topology (HS/MS/FD-CAN + LIN + Ethernet + load + errors). +- 16 per-corner suspension extended (4 corners × 4 fields). +- 64 per-bank engine deep (2 banks × 32 fields). +- 24 per-zone HVAC (3 zones × 8 fields). + +#### Routines (+120) +KAM reset, throttle/misfire/lambda/cam VCT/IMRC adapts, DPF force regen, DEF dosing, oil pump, starter, battery registration, oil/inspection/brake fluid/fuel filter/air filter resets, grid heater + secondary air tests, alternator + compression tests, TCM basic setting + clutch adapt + Quick Learn (10R80) + xDrive transfer-case adapt, per-wheel ABS bleed, ABS pump test, SAS + yaw + brake pressure zeros, TPMS relearn, EPB workshop mode, BCM init + window/mirror init, sunroof/panoramic/tailgate/frunk/tailgate step/running boards calibrations, HVAC basic + compressor + aux heater + heat-pump self-tests, headlight aim L+R, AFS, matrix pixel L+R, IPMA dynamic + static, FDM radar zero, IPSM surround, DSM, BLIS L+R, IPC service reset + mileage align + MyKey setup, BECM cell balance + capacity remeasure + isolation + pyro + contactor + pre-charge tests, SOBDMC resolver zero + offset + inverter self-test, OBC + DC-DC + Pro Power self-tests, EV thermal loop bleed, V2G self-test, 14 module-replacement procedures, As-Built/CCC programming, key pairing/deletion, PATS immobilizer relearn, MyKey admin, OTA check/install/rollback, HSM provision/zeroize/log export, 5 domain self-tests, ethernet switch self-test, BlueCruise + Co-Pilot360 calibrations. + +#### Coding blocks (37 / 318 fields) +BCM general, door extended, alarm zones, lighting extended, Co-Pilot Lane, FRR ACC, AEB, BlueCruise, IPMA camera, TDM trailer, EV charge features (CCS1/CCS2/NACS/CHAdeMO/V2G/V2L/V2H/Pro Power/ISO15118/smart grid/home integration/Intelligent Backup), MyKey, SYNC features, IPC features, massage dr, seat climate dr, panoramic roof, frunk, tailgate, running boards, BLIS, driver status, HVAC zones, OTA, crypto/HSM, Pro Power, FoD bitmap (12 features), APIM audio (AM/FM/HD/DAB+/SiriusXM/BT/USB/podcast/Spotify/Amazon/Apple Music/RSA/ANC), Co-Pilot360, Tow Tech, frunk features, mega console, AHM aux heater, aerodynamic shutter, HUD, wireless charging, trailer brake. + +#### Adaptations (88) + Actuator tests (111) + Live PIDs (40) + DTC ext (2,132) +Engine + trans + ABS + TPMS + ADAS + lighting + climate + EV + body + MyKey + trailer + OTA + HSM defaults; full per-zone + per-actuator tests; live engine + battery + motor + charge + ADAS + cluster PIDs; broad P/B/U/C codes × 4 record types. + +### v3.43 second pass (4,388 → 7,397 entries) + +| Section | v3.42 | v3.43 | +|---|---|---| +| DIDs | 1,733 | 1,945 | +| Routines | 132 | 187 | +| Adaptations | 88 | 142 | +| DTC extended-data | 2,132 | 4,820 | + +- 4 user profiles × 16 fields = 64 driver-stats DIDs. +- 16 per-corner air-spring chamber pressures + 12 onboard scales DIDs. +- 15 Tow Tech / Smart Hitch detail DIDs. +- 16 power running boards / tailgate step / frunk / tailgate position + lifetime cycles. +- 64 service-history per-item DIDs (16 items × 4: last miles + epoch + workshop + due-in). +- 25 vehicle metadata DIDs (FA-style: model year, plant, paint, interior, market, country, trim, options, kerb/GVW/payload, warranty start + first registered + production, engine + trans serials, axle ratio, tire + wheel size, color, trim). +- 55 routines incl. ZF deep + PowerStroke deep (glow plug, water separator drain, DEF tank drain, DPF burn-off, EGR clean), pinch-relearn (window FL/FR + sunroof + pan), seat init, SYNC factory reset + voice recog calibrate, ADAS deep recals, trailer pair + calibrate + brake burnish, Smart Hitch zero, BECM module-specific balance, V2G self-test, gateway routing reset, profile create/delete/export/import, aerodynamic shutter calibrate. +- 54 adaptations (cruise buffers, lighting, park assist, driver attention, off-road incl. Trail Control + Hill Descent + Crawl Control, EV deep incl. route-aware unlocks + curves + AVAS, FordPass remote services, massage/seat-climate defaults, comfort, power running boards, tailgate). +- 2,688 DTC ext-data records — long-tail P-codes round 2 + round 3 with 6 record types (incl. environmental_data + freeze_frame_template). + +Estimated ~40% ODIS coverage — at the realistic public-source ceiling for Ford (FORScan community is among the most open of any OEM). + +## [3.41.0] - 2026-05-08 — BMW final pre-commercial-ceiling pass (~42% ODIS, 7932 entries) + +Final BMW push toward public-source ceiling (~40-45% non-commercial). +Adds 2,337 entries focused on the coding-blocks gap and final long-tail. + +| Section | v3.40 | v3.41 | Change | +|---|---|---|---| +| ECUs | 138 | **138** | — | +| DIDs | 2,070 | **2,070** | — | +| Routines | 203 | **203** | — | +| Coding blocks | 8 (77 fields) | **43 (414 fields)** | +35 / +337 | +| Adaptations | 156 | **198** | +42 | +| Actuator tests | 151 | **151** | — | +| Live PIDs | 69 | **69** | — | +| DTC extended-data | 2,800 | **5,060** | +2,260 | + +### Coding blocks (+35 / +337 fields) +BDC general (auto-lock/unlock policy, comfort open/close, dome +behaviour), FEM window extended (4-door one-touch + anti-pinch + +rain close + travel/kid lock + comfort speed), FEM central lock + +alarm (full alarm zone bitmap + dynamic blink + transport mode), +FRM lighting extended (welcome/leaving choreography + dynamic turn ++ matrix high-beam + glare-free + adaptive cornering + highway/city/ +weather/intersection light + travel-mode swap + laser + OLED rear + +dynamic DRL), KAFAS Lane Assist extended (haptic/audio/visual warning ++ active steering + Emergency Assist + trained parking + highway +assist + min/max speed), FRR ACC extended (Stop&Go + curve + speed- +limit + predictive + lane-change + default distance + speed), KAFAS +AEB extended (pedestrian + cyclist + intersection + reverse + evasive ++ warn lead), IHKA zones extended (4 zones + sync + auto-recirc + +residual heat + aux heater + heat pump + ionizer + perfume + solar + +auto-demist + blower max), EV charge features extended (ISO 15118 + +DIN 70121 + CCS Combo 1+2 + CHAdeMO + NACS + V2G/V2L/V2H + smart +grid + solar + max DC), EV thermal strategy (preheat per route + DC +charge preheat + aggressive cooling + battery-friendly + heat-pump +priority + max charge temp), cluster features (HUD + AR HUD + digital ++ curved + OLED + track-mode + driver-coaching + speed-limit/eco/ +charge/regen/nav/phone/media overlays + brightness), iDrive features +(CarPlay + AA wired/wireless + 5GHz hotspot + voice wake-word + +local DNN + cloud + profile sync + app store + video + browser + +games + rear-pax apps + split screen + widget layout), sport diff +(torque vectoring + track-mode aggressive + dyn traction + default +lockup), active steering (velocity-dependent + track-mode direct + +default ratio), exhaust flap (track always open + sport open + manual +button + open threshold rpm), launch control (count limit + max rpm), +surround view (top-down + 3D + transparent hood + side obstacle + +rear cross traffic + collision warn + default view), driver attention +(phone-use + drowsiness + hands-off + gaze tracking + seatbelt check ++ warn threshold), gesture control (volume + call + nav zoom + 3 +custom gestures), AR HUD (nav/speed/ACC/lane/warning overlays + +brightness), tow hitch (electric + recognition + brake assist + sway ++ park assist + max trailer kg), driver massage (vitalisation + +relaxation + warming + Active Well-Being + default intensity), driver +seat climate (heat + vent + chill + auto-with-climate + zones + +default level), panoramic roof (Sky Lounge LED + electrochromic + +anti-pinch + auto rain + comfort + comfort speed), alarm extended +(full per-door switch bitmap + transport + garage + convertible mode + +duration), Comfort Access keyless (UWB + digital key + kick-to-open + +walk-away + approach + driver-only + walk-away distance), EHC air +suspension (self-levelling + aero drop + lift + loading + kneel + aero +drop speed), AR glasses (nav/call/media overlay), V2X / Car2X (V2V/ +V2I/V2P/V2G/V2L/V2H + DSRC/C-V2X radio + warn distance), OTA features +(auto-install + Wi-Fi only + metered + signed-only + rollback + min +battery), HSM features (secure boot + sec log + intrusion + anti-theft ++ seedkey + flash protection + level), FoD bitmap (15 features), +matrix headlight pixels, IHKU aux rear climate (zones + blower + +displays integration), Executive Lounge (rear recline + footrest + +table + console + displays + audio + default recline %). + +### Adaptations (+42) +Cruise overspeed/underspeed buffers + speed-limiter default, auto +high-beam threshold + min speed, cornering max speed + min angle, +welcome/leaving light proximity, park-assist (max speed + warn dist ++ volume + freq), reverse audio attenuation, trailer (brake gain + +sway sensitivity + max kg + tongue kg), fuel + EV range/charge +warnings, auto-wipe + auto-light sensitivity, memory parking slots ++ trained parking max, blind-spot lead + haptic, night-vision +threshold + max speed, driver-attention warn threshold + max +session min, Car2X warn distance, massage program/intensity defaults +(driver + passenger), seat climate defaults, perfume intensity + +pulse interval, ionizer default, chilled cup-holder target. + +### DTC ext-data (+2,260) — third long-tail sweep +P-codes round 3 with 6 record types (occurrence + aging + miles_since_cleared + oem_status_byte + environmental_data + freeze_frame_template), B+C codes round 2. + +## [3.40.0] - 2026-05-08 — BMW second-pass deep push (~37% ODIS, 5595 entries) + +Continuing the BMW catalog push toward ~40-45% ceiling. Adds 2,744 entries: + +| Section | v3.39 | v3.40 | Change | +|---|---|---|---| +| ECUs | 78 | **138** | +60 | +| DIDs | 1,254 | **2,070** | +816 | +| Routines | 107 | **203** | +96 | +| Coding blocks | 8 | **8** | — | +| Adaptations | 79 | **156** | +77 | +| Actuator tests | 87 | **151** | +64 | +| Live PIDs | 46 | **69** | +23 | +| DTC extended-data | 1,192 | **2,800** | +1,608 | + +### New ECUs (+60) +KAS (Comfort Access), RICOM, AMK (Active Anti-Roll), EHPS, FZD, +gestik (gesture), ICTM, gestik-3D (iX), 9 M-specific ECUs (sport +diff, ARS active steer, exhaust flap, launch coord, drift analyzer, +laptimer, data recorder, M3/M4/M5/M8 specific), top-tier amplifiers +(B&W Diamond, HK Logic7), zonal architecture (5 domain controllers, +ethernet switch TSN, OTA master, crypto/HSM, Car2X radio, V2X +controller), wireless charging (Qi), AR HUD, AR glasses interface, +GPU module, passenger + rear screens, massage modules, seat-climate +modules, sky lounge, executive lounge, perfume dispenser, ionizer, +power glovebox, power trunk, tow hitch, sliding doors L+R, chilled +cup-holder, curved-screen controller. + +### Engine variant DIDs (+112) +14 engine families × 8 fields each: B38, B47, B48, B58, B57, N20, +N55, S55, S58, S63, S68, N63, N74, B68 — displacement, max power + +torque, redline, compression ratio, bore, stroke, weight. + +### Transmission variants (+128) +8 variants × 16 fields: ZF 8HP45/50/70/75/76/90, M DCT 7-spd, +xDrive transfer case — oil type code, capacity, supplier, HW +revision, SW train, oil age + temp extremes, lifetime shifts + +clutch engagements + TCC lockup + manual-mode distance + kickdown ++ launch + over-torque + limp-home counts. + +### iDrive head-unit generations (+128) +8 generations × 16 fields: CIC, NBT, NBT EVO, ID4-8 — HW part no, +SW train, map version + region, SSD total + free, RAM, SoC temp, +uptime, boot count, OTA status + progress, voice/nav/media/ +connectivity-box engine versions. + +### Per-corner suspension extended (+32) +4 corners × 8 fields: velocity, compressor branch runtime, damper +actuator temp, air-spring chamber 1-4 pressures, levelling offset. + +### Per-bank engine deep (+64) +2 banks × 32 fields: turbo speed/inlet/outlet temp, intercooler +in/out, MAP, boost, wastegate duty + pos, VGT, EGR, throttle, +intake runner, HP+LP fuel rail, fuel pump duty, injector duration, +ignition advance, knock corr, O2 short+long trim, cat efficiency, +DPF soot+ash+pressure-drop+temp in/out, SCR NOx in/out + efficiency, +EGR cooler temp, oxidation cat temp. + +### Bank-2 O2 sensors (+16) — V8/V12 + +### IHKA deep (+14) +Solar intensity L+R + position, evap pressure, compressor +displacement + clutch state, aux heater glow plug + fuel +consumption + lifetime runtime, pollen filter age, air quality +CO + NOx, cabin pressure delta. + +### KAFAS lane stack (+32) +4 lanes × 8 fields: detection quality, curvature, offset, width, +marking type, color, age, confidence. + +### Traffic-sign recognition (+32) +8 detected signs × 4 fields: class, value, distance, confidence. + +### Per-camera lens shading (+24) +6 cameras × 4 fields: shading correction, white balance R|G|B, +lens temp, blockage. + +### Per-radar waveform (+40) +5 radars × 8 fields: chirp bandwidth, chirp duration, TX power, +blockage, h-align, v-align, temp, supply. + +### Premium audio (+21) +Per-channel gains (FL/FR/RL/RR/center/sub/surround/height L+R), +amp temp/supply/total power, DSP load, 7-band EQ. + +### Per-key UWB extended (+32) +8 keys × 4 fields: UWB ranging distance, AoA, RSSI, pairing count. + +### Per-ECU programming + signature (+96) +24 ECUs × 4 fields: programming attempts, SW checksum SHA1, +signature status, last successful programming epoch. + +### BMW Function-on-Demand (+30) +15 features × 2 DIDs: adaptive lights, CarPlay, AA wireless, +hotspot, TSR, Driving Assistant Pro, Parking Plus, remote engine +start, real-time traffic, nav premium, ConnectedDrive app, +teleservices, premium audio unlock, heated seats unlock, heated +steering wheel unlock — active flag + expiry epoch. + +### Bus topology (+15) +PT-CAN/K-CAN/D-CAN/LIN/FlexRay/MOST/Ethernet node bitmaps + bus +load + bus-off errors + Ethernet link speed + packet drops. + +### Routines (+96) +Per-wheel ABS bleed, ZF deep (5 clutches + TC + basic + oil-fill), +xDrive transfer-case clutch adapt, sensor static calibrations +(SAS, yaw, long+lat G, brake pressure), ride-height calib + low/high, +matrix pixel calib L+R, laser alignment L+R, OLED tail calib, +welcome/leaving choreography setup, KAFAS extended dyn+static, +surround camera + driver camera + gesture + AR HUD + blind-spot +calibrations, ultrasonic + night-vision, KESSY UWB recalibrate + +digital-key phone pair + valet + track-mode PIN reset, aux heater +burn-off + fuel-quality recal, heat-pump efficiency, evap drain, +EV deep (module-specific balance, capacity relearn, pyro continuity, +ISO 15118 + DIN 70121 handshake test, three-phase + resolver), +network/programming (FA, I-Stufe, SGBM, CAFD, FDL diff apply, OTA +check/install/rollback, HSM provision/zeroize, secure log export), +domain self-tests (PT, chassis, body, ADAS, infotainment), ethernet +switch self-test, Car2X self-test, wireless charging calibrate, FoD +subscription refresh, M-specific (Drift Analyser reset, Laptimer +reset, data recorder export, sport-diff calibrate, exhaust flap +calibrate, launch-control relearn), body/comfort (panoramic roof, +convertible top open/close, glovebox, power trunk, tow hitch, +sliding doors L+R, massage demos L+R, seat-climate tests, perfume, +ionizer). + +### Adaptations (+77) +Engine deep (torque curve, throttle response, dynamic overboost, +overrun fuel-cut delay, cold-start strategy, warmup target, lambda +target full load, e-boost target, alternator idle load, A/C +compressor max load), transmission (sport aggression, DPF avoidance, +manual hold, paddle priority), DSC (traction threshold, dynamic +traction unlock), EHC (self-levelling, lift speed, aero drop), ARS +default ratio, sport-diff lockup, exhaust flap rpm, launch max rpm, +EHPS (assist curve, return force, velocity-dependent), KAFAS +extended (lane-centring offset, speed-limit assist, construction +zone, no-overtake), FRR extended (hands-off lead, Emergency Assist, +TJA, Extended TJA Level 2, Highway Assistant, lane-change), matrix +anti-glare, OLED rear default, welcome+leaving anim, ambient color + +speed-dependent + dynamic mode, IHKA (blower curve, solar, humidity, +recirc CO+NOx thresholds, aux heater enable temp), EV charging +(pre-condition unlocks, max DC+AC limits, charge curves), recuperation +defaults, B-mode, EV creep, comfort (CKM profiles, per-key, auto +recalls), OTA (check interval, min battery, install windows, metered, +signed-only, rollback), HSM (secure boot, intrusion detect, seed-key +level). + +### Actuator tests (+64) +M sport (exhaust flap open/close, sport diff engage, active-steer, +launch-control), body/comfort (pan roof, conv top, glovebox, trunk, +tow hitch, sliding doors L+R, massage demos, seat-climate demos, +perfume, ionizer, chilled cup-holder), lighting deep (matrix sweep, +laser pulse, OLED choreography, welcome+leaving demos, ambient +red/green/blue/chase), ADAS deep (KAFAS dyn, FRR blockage, side +radar L+R, surround sweep, driver camera, gesture, AR HUD test, +blind-spot indicators, ultrasonic sweep), domain controllers + zonal +(5 domain tests, ethernet switch loopback, Car2X test, wireless +charging, AR glasses, AR HUD full calib, passenger+rear screens), +OTA (install force, rollback force), HSM self-test. + +### DTC ext-data (+1,608) — long-tail +P-codes broad sweep across P05xx-P0Fxx + P18xx-P2Dxx, B-codes round 2 (1400-2800), U-codes round 2 (0300-3200) — each × 4 record types. + +## [3.39.0] - 2026-05-08 — JSON-only architecture for all 45 OEMs + BMW deep-push (~28% ODIS) + +Two coordinated changes: + +### 1. JSON-only architecture across all OEM extensions +The VW JSON-only pattern (introduced in v3.31) is now applied to +every other OEM. Refactored 43 OEM Pascal files to remove their +hardcoded `ECU($xx, ...)` / `DID($xx, ...)` / `Routine($xx, ...)` +arrays. Every entry that was previously hardcoded has been merged +into the corresponding catalog JSON. Each OEM extension now: + +- `ApplicableToVIN` returns `VINMatchesCatalog('.json', VIN)` + (no hardcoded WMI lists in Pascal). +- `BuildCatalog` does only `MergeCatalogJSON('.json', ...)` + + `MergeCatalogJSON('uds-standard.json', ...)`. +- `BuildExtendedCatalog` (newly added) does + `MergeExtendedCatalogJSON('.json', ...)` for coding blocks, + adaptations, actuator tests, live PIDs and DTC extended-data. + +This means catalog updates ship as JSON edits (no recompile) and +porting to other languages only needs a JSON parser — same as VW. + +OEMs refactored: Aston Martin, BMW, BYD, Bentley, Cummins, Dacia, +Detroit Diesel, Ferrari, Ford, GM, Geely, Great Wall, Honda, +Hyundai/Kia (HMG), Isuzu, Iveco, JLR, Lada, Lucid, MAN, MINI, +Mahindra, Mazda, McLaren, Mercedes-Benz, Mitsubishi, NIO, Nissan, +PACCAR, Polestar, Porsche, Renault, Rivian, Rolls-Royce, Scania, +Smart, Stellantis, Subaru, Suzuki, Tata, Tesla, Toyota, Volvo, +Volvo Trucks, Xpeng (45 total — VW + 44 others). + +### 2. BMW catalog pushed to ~28% ODIS +First tier-1 OEM lift. catalogs/bmw.json grew from 43 to 2,851 +entries — applying the same depth-pattern proven on VW. + +| Section | Before | After | +|---|---|---| +| ECUs | 7 | **78** | +| DIDs | 34 | **1,254** | +| Routines | 9 | **107** | +| Coding blocks | 0 | **8 (77 fields)** | +| Adaptations | 0 | **79** | +| Actuator tests | 0 | **87** | +| Live PIDs | 0 | **46** | +| DTC extended-data | 0 | **1,192** | + +Coverage includes: +- Full F-series + G-series ECU map (DME, EGS, DSC, FEM, BDC, FRM, + KOMBI, KAFAS, ICM, IHKA, lighting modules, doors, audio, telematics, + HUD), plus i-series EV (SME, EMC front+rear, KLE, DC-DC, OBC, + chiller, HV heater, ISO 15118 wallbox interface), plus G-series + UDS-style addresses (0x6E0-0x6F9 + 0x600-0x618). +- DME deep telemetry (engine RPM/torque/coolant/oil/MAP/MAF/lambda, + HP+LP fuel rail, VANOS intake+exhaust, Valvetronic, knock retard, + DPF soot+ash+pressure-drop+temp+regen, SCR NOx in/out + efficiency, + AdBlue, turbo speed/inlet/outlet temp, intercooler, cat efficiency + bank 1+2, immobilizer/EWS state). +- Per-cylinder DIDs (cyl 1-12 × 8 fields = 96 DIDs) — supports V8/V12. +- EGS DIDs (oil temp/age/pressure, gear, TCC lockup, input/output rpm, + shift counts, clutch temp DCT, torque in/out, adapt status). +- DSC DIDs + per-wheel pad wear/disc thickness/temp/tire pressure+temp+target. +- EHC + VDC per-corner suspension actuators + ARS torque per corner. +- SME (HV battery) — pack V/A, SOC/SOH, max/min/avg cell V, cell delta, + isolation, capacity remaining + total, charge cycle counts, thermal + events, pyro fuse + contactors, module count + cells/module. +- 96 per-cell voltages + 96 per-cell temperatures. +- 12 modules × 6 fields (V, A, max/min temp, SOC, SOH). +- EMC front + rear motor (torque target/actual, rpm, stator/rotor + temp, inverter temp + input V/A, phase current, efficiency, + resolver offset). +- KLE charging (DC + AC voltage/current/power, phases active, + efficiency, ISO 15118 state, Plug & Charge, charge port + temp + lock state, lifetime DC + AC kWh, session counts). +- KAFAS + FRR + ACC ADAS (camera blockage, lane offset+curvature, + speed limit detect + confidence, dynamic-calibration state, radar + target distance + relative speed + class, blockage, alignment H+V, + chirp bandwidth, TX power, AEB lifetime interventions). +- ADAS tracked-object stack (8 simultaneous objects × 8 fields). +- IHKA per-zone (4 zones × 8 fields) + heat pump + aux heater. +- Cluster KOMBI (speed, odometer, trip A/B, range, fuel, service + intervals, drive cycle count) + last-32-trip extended history + (32 × 8 = 256 DIDs). +- Driver-coaching (16 metrics × 4 windows = 64 DIDs). +- Per-bulb hours-on (32 lighting circuits). +- Per-zone ambient lighting (24 zones). +- Per-key data (8 keys × 8 fields). +- 8 coding blocks (77 fields total): FEM door extended, + FRM lighting, matrix headlight features, ACC extended, AEB, + IHKA zones, alarm zones, EV charge features. +- 79 adaptations covering DME idle/torque/start-stop envelope/cyl-deact + /DPF/EGR/SCR/grid-heater, EGS shift speed + kickdown + creep, DSC + default mode + traction + auto-hold + trailer brake, TPMS per-axle + summer/winter/loaded, KAFAS Lane Assist + AEB sensitivity, ACC + default distance + speed + Stop&Go, lighting (auto high-beam, welcome, + ambient), IHKA defaults, EV charge limits + regen + AVAS, comfort + (auto-lock speed, mirror dip, window remote, auto-wipe). +- 87 actuator tests (DME throttle/EGR/wastegate/intake-runner/fuel-pumps + /secondary-air/DPF/SCR/starter/alternator/grid-heater/exhaust-flap/ + oil+coolant pumps, DSC pump + per-wheel inlet/outlet valves, EPB + motors L+R, EHC compressor + relief valve, VDC dampers, FEM + windows/mirrors/sunroof/tailgate/central-lock, lighting per-bulb + + matrix sweep + laser + OLED, IHKA compressor + heat pump + aux + heater, audio per-channel sweeps, ADAS tests, EV contactors + + pre-charge + pyro continuity + motor demos + chiller + radiator fan + + battery heater). +- 1,192 DTC ext-data records (P + B + U + C codes × 4 record types). + +## [3.38.0] - 2026-05-08 — VW final pre-commercial-ceiling pass (~50% ODIS) + +Final non-commercial pass — adds 993 entries across the last +practical gaps before public/community sources are exhausted. +Adds 36 new ECUs (rear-axle steer, EV thermal secondary, HV +junction box, charge inlet electronics, heat-pump compressor, +combustion + electric aux heaters, LV battery management, charging +planner, V2X controller, Car2X radio, OTA master, crypto/HSM, +ethernet switch + 5 domain controllers in the new zonal +architecture, AR headlight, digital OLED + matrix headlights, +ultrasonic clusters, blind-spot modules, night-vision, driver- +attention monitoring, EV rear motor + disconnect clutch), +per-bank engine deep telemetry (32 fields × 2 banks), per-zone +HVAC fine-grained, brake-fluid + iBooster + pedal-feel, ADAS +tracked-object stack (8 objects × 8 fields), premium audio +fine-grained (24 DIDs), HV battery per-module (12 modules × 6 +fields), 8 new coding blocks (lane-assist, ACC, AEB, V2X, OTA, +HSM, trailer, sound synthesis), 76 module-replacement and zonal +self-test routines, 42 actuator tests for the new domain, 500 +more long-tail VAG P-codes. + +### catalogs/vw.json — 7,498 → 8,491 entries + +| Section | v3.37 | v3.38 | Change | +|---|---|---|---| +| ECUs | 75 | **111** | +36 | +| DIDs | 2,774 | **3,042** | +268 | +| Routines | 460 | **536** | +76 | +| Coding blocks | 133 (915 fields) | **141 (973 fields)** | +8 / +58 | +| Adaptations | 565 | **604** | +39 | +| Actuator tests | 298 | **340** | +42 | +| Live PIDs | 421 | **445** | +24 | +| DTC extended-data | 2,772 | **3,272** | +500 | + +### New ECUs (36) — covers zonal-architecture + niche subsystems +Domain controllers (powertrain, chassis, body, ADAS, infotainment), +secondary central gateway, ethernet switch (TSN), crypto/HSM, +OTA master, central computer (zonal), Car2X DSRC/C-V2X radio, +V2X (V2G/V2L/V2H) controller, charging planner, AC + DC charge +inlet electronics, HV battery junction box, EV thermal secondary, +heat-pump compressor, combustion + electric aux heaters, LV aux +battery monitor + 12V starter battery sensor, panoramic roof, +convertible top, exhaust flap L+R, soundaktor L+R, rear-axle steer, +wireless charging (Qi), HUD projector, interior camera, gesture +control, massage modules dr+pa, passenger screen, rear screens L+R, +premium amplifier, DSP processor, AR headlight, digital OLED +taillights L+R, digital matrix headlights L+R, side radars FL+FR, +ultrasonic clusters front+rear, blind-spot modules L+R, night-vision, +driver-attention, EV rear motor inverter, EV rear-axle disconnect +clutch, secondary AWD coupling, trailer module. + +### Per-bank engine deep (64 DIDs) +2 banks × 32 fields each: turbo speed + inlet/outlet temp, +intercooler in/out temp, manifold absolute + boost pressure, +wastegate duty + position, VGT vane, EGR position, throttle position, +intake runner position, HP + LP fuel rail pressure, fuel pump duty, +injector duration, ignition advance, knock correction, O2 short + +long trim, cat efficiency, DPF soot + ash + pressure-drop + temp +in/out, SCR NOx in/out + efficiency, EGR cooler temp, oxidation cat +temp. + +### Per-zone HVAC (32 DIDs) +4 zones × 8 fields each: setpoint, actual, blower duty, temp flap, +defrost flap, face flap, foot flap, ambient sensor. + +### Brake-fluid + iBooster + pedal-feel (12 DIDs) +Reservoir level, fluid temp, age, water content, master cylinder +pressure, iBooster motor current + position + temperature, pedal +force + travel, park-brake L+R motor currents. + +### ADAS tracked-object stack (64 DIDs) +8 simultaneous tracked objects × 8 fields each: object ID, class +(car/truck/motorcycle/bicycle/pedestrian/animal/unknown), distance, +lateral offset, relative speed, confidence, track age, sensor-fusion +source bitmap. + +### Premium audio fine-grained (24 DIDs) +Per-channel gains (FL/FR/RL/RR/center/sub/surround L+R), amp temp + +supply + total power, DSP load, 7-band EQ, ANC active + attenuation, +road-noise level, speaker short + open bitmaps. + +### HV battery per-module (72 DIDs) +12 modules × 6 fields each: voltage, current, max temp, min temp, +SOC, SOH. + +### Coding blocks (+8 / +58 fields) +Lane Assist extended, ACC extended, AEB extended, V2X / Car2X, +OTA features, HSM / security, trailer, AVAS + soundaktor synthesis. + +### Routines (+76) +14 module-replacement procedures (engine, trans, ABS, steering, +airbag, cluster, BCM, gateway, radar, camera, EVCC, HV battery, EV +motor, OBC, DC-DC), KESSY + immobilizer + SSP relearns, key pairing ++ deletion, mileage + odometer + speedo calibration, central crash- +data clear, fuel-level + EV-range relearn, gateway component +protection, OTA install/rollback/verify, HSM key provision/zeroize/ +log export, 5 domain self-tests + ethernet switch + Car2X self-tests, +trailer module pair + calibrate, panoramic roof + convertible top +calibrations, exhaust flap + soundaktor calibrations, heat-pump + +aux heaters self-tests, LV aux battery test, wireless charging +calibrate, HUD test, interior camera + gesture calibrations, massage ++ passenger/rear screen tests, premium amp + DSP self-tests, AR +headlight + OLED tail + digital matrix calibrations, ultrasonic + +blind-spot + night-vision + driver-attention calibrations, EV rear +motor + disconnect clutch + battery junction + charge inlet tests. + +### DTC ext-data (+500) — long-tail P-codes (round 2) +Sparse second-pass through P14xx-P23xx + P2Cxx-P2Dxx with +additional offsets × 3 records (occurrence + miles_since_cleared ++ oem_status_byte). + +## [3.37.0] - 2026-05-08 — VW platform/MY splits + deep service routines (~45% ODIS) + +Fourth pass — adds 1,319 entries focused on the platform/MY axis +plus the service-routine and deep actuator-test gaps. Covers MQB / +MEB / MLB-evo / PPE / NSF / Modular CE platform metadata, MIB1-4 +head-unit generation specifics, DSG generation specifics +(DQ200/250/381/500/501), per-corner suspension actuator currents + +ride heights + air-spring pressures, V6/V8/V10/W12 per-cylinder +telemetry (cyl 5-12), bank-2 O2-sensor stack, broad door/alarm/ +TPMS/seat-memory/climate-zones coding blocks, 77 deep service +routines (ABS bleed per-wheel, DSG basic-setting, EPB pad change, +camshaft / timing chain / SCR / AdBlue / oil pump / coolant pump +/ headlight aim / matrix pixel / camera dynamic / radar zero / EV +inverter self-test / EV motor resolver zero / HV contactor + +pyro-fuse + IMD self-tests), 50 deep actuator tests, 775 long-tail +VAG P-codes. + +### catalogs/vw.json — 6,179 → 7,498 entries + +| Section | v3.36 | v3.37 | Change | +|---|---|---|---| +| ECUs | 75 | **75** | — | +| DIDs | 2,438 | **2,774** | +336 | +| Routines | 383 | **460** | +77 | +| Coding blocks | 128 (857 fields) | **133 (915 fields)** | +5 / +58 | +| Adaptations | 517 | **565** | +48 | +| Actuator tests | 248 | **298** | +50 | +| Live PIDs | 393 | **421** | +28 | +| DTC extended-data | 1,997 | **2,772** | +775 | + +### Platform / MY splits (272 DIDs) +- **Platforms (8 × 8 DIDs = 64):** MQB, MQB-evo, MEB, MEB+, MLB-evo, PPE, NSF, Modular-CE — variant ID, body style, wheelbase, front+rear track, kerb / GVW / payload weights. +- **MIB head-unit (5 × 16 DIDs = 80):** MIB1, MIB2, MIB2-High, MIB3, MIB4 — HW part no, SW train, map version + region, SSD total + free, RAM, SoC die temp, uptime, boot count, OTA status + progress, voice / nav / media / connectivity-box engine versions. +- **DSG (5 × 16 DIDs = 80):** DQ200, DQ250, DQ381, DQ500, DQ501 — K1+K2 clutch temp + wear + engagement count, oil temp + pressure + age + quality, mechatronic temp + supply V, lifetime shift counts (total, kickdown, manual, launch). + +### Per-corner suspension (32 DIDs) +4 corners × 8 fields: DCC actuator current, DCC setpoint + actual response, air-suspension ride height, air pressure, valve current, strut temp, compressor branch runtime. + +### V6/V8/V10/W12 per-cyl + bank-2 (80 DIDs) +- Cylinders 5-12 × 8 fields each (64 DIDs): drive-cycle + lifetime misfire counts, knock retard, injection correction, relative compression, EGT, individual lambda, coil resistance. +- Bank-2 O2 sensors 1-4 × 4 fields (16 DIDs): voltage, current, temp, heater current. + +### Coding blocks (+5 / +58 fields) +Door FL extended (windows + mirrors + door unlock strategy), +alarm sensor zones, TPMS per-axle thresholds (summer/winter/loaded), +driver-seat memory extended (3 slots × 4 axes), climate zones. + +### Deep service routines (+77) +Per-wheel ABS bleed, DSG K1/K2 clutch adapt-reset + basic setting + +oil-fill, Haldex priming + clutch adapt, EPB workshop mode + +basic setting, steering / yaw / ESP zeros, throttle / idle / +misfire / catalyst / AFM / MAP / fuel-trim adapt-resets, intake +runner + camshaft + timing chain learns, SCR + AdBlue routines, +oil / coolant pump + thermostat + fan-clutch tests, wiper park +position, sunroof / convertible / tailgate / trunk / window / +mirror calibrations, seat init, headlight aim + AFS + matrix + +laser + OLED calibrations, radar + camera dynamic calibrations, +sensor calibrations (rain, humidity, interior+outside temp), ESP +self-test, airbag crash-data clear, seatbelt pretensioner replace, +battery replacement (BEM relearn) + capacity test, alternator load +test, starter test, EV inverter self-test + motor resolver zero + +charge-door calib + thermal loop bleed + battery isolation + +pyro-fuse + contactor + IMD self-tests. + +### Deep actuator tests (+50) +ABS pump + per-wheel inlet+outlet valves, EPB motor extend/retract +L+R, air-suspension compressor + relief valve, steering assist / +column-lock, wiper / washer (front+rear+headlight), horn low/high, +starter test, alternator field, fuel pumps (LP+HP), throttle motor, +intake runner, wastegate, VGT, EGR + EGR cooler bypass, SCR + +AdBlue pump, exhaust flap, AVAS, EV front+rear motor demos, EV A/C +compressor, HV chiller valve, EV radiator fan, HV battery heater, +EV charge port lock + indicator LED, HV pyro-fuse continuity, +HV positive + negative + pre-charge contactors. + +### DTC ext-data (+775) — long-tail VAG P-codes +Sparse sweep across P14xx-P17xx, P18xx-P19xx, P20xx-P23xx, P2Cxx-P30xx ranges × 4 record types. + +## [3.36.0] - 2026-05-08 — VW catalog third deeper-niche pass (~40% ODIS) + +Continuing the same parity pass — adds 1,435 more entries focused +on driver-coaching telemetry, last-32-trip computer history, broad +per-component supplier/HW-rev/plant/manufacturing-date sweep, +CO2 + emission strategy adaptations (start-stop thresholds, cyl +deactivation envelope, coast/sailing parameters, SCR/DPF/GPF/EGR, +cold-start, EV regen/thermal envelopes), broad B/C/U-code DTC +ext-data sweep, per-key extended (8 keys × 8 fields), live +coaching telemetry. + +### catalogs/vw.json — 4,744 → 6,179 entries + +| Section | v3.35 | v3.36 | Change | +|---|---|---|---| +| ECUs | 75 | **75** | — | +| DIDs | 1,910 | **2,438** | +528 | +| Routines | 368 | **383** | +15 | +| Coding blocks | 128 | **128** | — | +| Adaptations | 484 | **517** | +33 | +| Actuator tests | 238 | **248** | +10 | +| Live PIDs | 377 | **393** | +16 | +| DTC extended-data | 1,164 | **1,997** | +833 | + +### Driver-coaching telemetry (80 DIDs) +20 metrics × 4 windows (lifetime / 30d / 7d / last-trip): harsh +brake/accel/corner counts, over-speed minor/major, idle time, eco ++ anticipation + smoothness + attention scores, phone-use events, +hands-off/drowsiness/lane-departure warnings, AEB interventions, +ACC manual disengagements, regen efficiency, coast/eco/sport +distance. + +### Trip-computer extended (256 DIDs — last 32 trips × 8 fields) +Per-trip: distance, avg/max speed, avg consumption, duration, +start epoch, regen kWh, idle seconds. + +### Per-component supplier sweep (128 DIDs across 32 ECUs) +Each ECU: supplier code (4-byte BCD), HW revision (4-char ASCII), +manufacturing plant (3-char ASCII), manufacturing date (YYWWD BCD). + +### Per-key extended (64 DIDs across 8 keys) +Per stored key: unique ID, battery mV, lifetime button-press count, +last-used epoch, profile index, UWB-ranging-active flag, digital-key +phone-paired flag, valet-mode flag. + +### CO2 / emission strategy adaptations (33 channels) +Start-stop envelope (min coolant temp, min battery V, min SOC, max ++ min ambient), cyl-deact min/max speed + load envelope, coast +disengage speed + decel, predictive-efficiency look-ahead, SCR +dosing factor, DPF/GPF regen distance + max temp, EGR steady + +transient max-open %, cold-start idle target rpm + duration, grid +heater max current, cat light-off target, secondary-air duration, +fuel-cut min rpm, EV regen aggressiveness D/B-mode, EV one-pedal +creep, EV motor temp target, EV battery thermal pre-charge + DC +charge envelopes. + +### DTC ext-data (+833) — broad B/C/U-code sweep +B-codes 96 codes × 4 records = 384 (BCM/airbag/lighting/seat +motor/window-motor/door/sunroof/tailgate/heater zone niche faults), +U-codes 70 codes × 4 records = 280 (lost-comm + invalid-data on +network nodes covering all major buses), C-codes 45 codes × 4 +records = 180 (per-wheel ABS + EPS + EPB + air suspension + iBooster ++ ADAS chassis faults). + +### Live PIDs (+16) +Live eco/smoothness/attention/anticipation scores, live load factor, +throttle change rate, brake pressure, steering rate, yaw rate, +long+lat accel, ACC target distance + relative speed, lane offset, +phone-detected flag, driver drowsiness level. + +### Routines (+15) +Driver-coaching reset, trip-history clear, supplier-code re-learn +from neighbours, CO2 strategy re-learn, SCR dosing recal, force GPF +regen, grid-heater test, secondary-air test, digital-key phone +re-pair, UWB key recalibrate, valet/teen-driver PIN reset, +predictive efficiency re-learn, EV regen + one-pedal pedal-feel +recalibrate. + +### Actuator tests (+10) +Driver-coaching audio cue test, steering-wheel haptic cue test, +cluster + HUD coaching-overlay tests, predictive-efficiency demo, +grid heater + secondary air pulse tests, start-stop force-disable, +cylinder-deactivation demo, sport-diff lock-up. + +## [3.35.0] - 2026-05-08 — VW catalog deeper-niche pass (~37% ODIS) + +Continuing the VW push to set the parity bar before applying the +template to other OEMs. Adds 969 more entries focused on niche +subsystems that previous passes only sampled: per-cell EV battery +telemetry, per-bulb hours-on counters, per-zone ambient lighting +RGB, Audi Function-on-Demand subscription metadata, per-ECU +programming history + signature/checksum, bus topology / network +discovery, per-camera lens shading + per-radar waveform parameters, +matrix-headlight per-pixel state, niche adaptations (seat massage +zones, Webasto fuel calibration deep, HUD/CarPlay/AA fine-grained, +ambient lighting calibration), and 13 new VAG-specific P-code ext +ranges (1Axx-1Fxx + 25xx-2Bxx). + +### catalogs/vw.json — 3,775 → 4,744 entries + +| Section | v3.34 | v3.35 | Change | +|---|---|---|---| +| ECUs | 75 | **75** | — | +| DIDs | 1,417 | **1,910** | +493 | +| Routines | 348 | **368** | +20 | +| Coding blocks | 124 (782 fields) | **128 (857 fields)** | +4 / +75 fields | +| Adaptations | 412 | **484** | +72 | +| Actuator tests | 206 | **238** | +32 | +| Live PIDs | 341 | **377** | +36 | +| DTC extended-data | 852 | **1,164** | +312 | + +### Per-cell EV battery telemetry (192 new DIDs) +0x3C00-0x3C5F: per-cell voltage in mV (96 cells); 0x3C60-0x3CBF: +per-cell temperature (96 sensors). Covers MEB 8-pack / 12-pack + +e-tron 36-cell modules with cell-level granularity. + +### Per-bulb hours-on counters (32 DIDs) +0x3400-0x341F: lifetime hours-on for every individual lighting +circuit — low/high beam L+R, DRL L+R, all turn signals (incl. +mirrors), brake L+R+CHMSL, reverse L+R, fog F+R, license, interior +(dome/map L+R/trunk/glovebox), puddle (4 corners), position lamps. + +### Per-zone ambient lighting (62 DIDs + 62 adaptations) +31 ambient zones (dash, doors, footwell, console, headliner, +cup holders, speaker rings, A/B/C-pillar strips, dash strip, +door strips) — each with RGB+brightness DID for live read-back +plus calibration adaptation for static color/intensity. + +### Audi Function-on-Demand metadata (34 DIDs) +17 FoD features × 2 DIDs each (active flag + subscription expiry +epoch): Matrix high-beam, DAB+, navigation premium, smartphone +interface, wireless CarPlay/AA, TSR, ACC upgrade, Park Assist Plus, +Remote Park Pilot, voice premium, connected nav, live traffic, +hotspot, Audi connect remote, Car2X, predictive efficiency. + +### Per-ECU programming history (72 DIDs across 18 ECUs) +For 18 high-traffic ECUs: programming attempt count, last +successful programming epoch, software checksum (truncated SHA1), +software signature verification status (not_signed/valid/invalid). + +### Bus topology / network discovery (16 DIDs) +Per-bus node bitmaps (CAN powertrain/extended/infotainment, LIN1+2, +FlexRay A+B, MOST150, Ethernet), bus load %, bus-off error counts, +Ethernet link speed + packet drop count. + +### Per-camera lens shading (21 DIDs across 7 cameras) +Front main + wide, rear, mirrors L+R, front grille (top-down), +interior driver-attention — each with shading correction matrix, +white-balance gain (R|G|B), and lens temperature. + +### Per-radar waveform parameters (30 DIDs across 5 radars) +Front + 4 corners — chirp bandwidth, chirp duration, TX power, +antenna blockage estimate, horizontal + vertical alignment. + +### Matrix-headlight per-pixel state (64 DIDs) +0x3B00-0x3B3F (left) and 0x3B40-0x3B7F (right) — per-pixel PWM +state for 32-pixel matrix-LED arrays. + +### New niche adaptations (40 channels) +Seat massage program/intensity (driver + passenger), seat lumbar ++ bolster + cushion firmness, ambient global brightness + dynamic +mode + welcome/leaving/coming-home duration + speed dependence, +Webasto deep calibration (fuel priming pulses, glow-plug preheat, +combustion-air min/max PWM, fuel-pump min/max Hz, target CO2), +trip auto-reset thresholds, cluster + HUD day/night brightness, +HUD geometry offsets, CarPlay/Android Auto audio priority + 5GHz +preference, voice wake-word sensitivity + local recognition, +Car2X warning distance. + +### New live PIDs (36) +On-board charger telemetry (input V/A, efficiency, temp), DC-DC +(HV in, LV out, current, efficiency, temp), front+rear inverter +IGBT temps, front+rear motor stator temps + torque + rpm, HV +battery (SOC, SOH, pack V/A, max/min/avg cell V, max/min cell +temp, isolation kΩ), DC + AC charge actual power, thermal loop +(coolant temp, pump rpm, chiller state, PTC heater W, heat-pump +COP, compressor rpm). + +### New routines (20) +Programming history clear, SW signature recheck, bus topology +rediscover, camera shading recalibrate, radar alignment self-check, +matrix headlight pixel sweep, ambient zone color sweep, FoD +subscription refresh, EV cell balance force, capacity remeasure, +OBC/DC-DC self-test, seat position end-stop + massage zone calib, +Webasto burn-off + fuel-quality recal, HUD geometry recal, cluster +TFT pixel test, MMI touchpad self-test, microphone array test. + +### New coding blocks (4 blocks, 75 fields) +Ambient lighting per-zone enable bitmap (31 fields), FoD features +enabled bitmap (17 fields), matrix headlight features (14 fields), +MMI features (12 fields). + +### New actuator tests (32) +Matrix headlight L+R pixel sweep, OLED rear-light choreography, +ambient zone tests (red/green/blue/white/chase + welcome/leaving +choreography), seat massage + climate demos, Webasto burn-off + +glow plug check, HUD + cluster TFT test patterns, MMI touch grid, +microphone array loopback, speaker sweeps (FL/FR/RL/RR/center/sub), +EV OBC + DC-DC self-tests, HV chiller + heat-pump self-tests, +HV thermal pump priming, camera shading capture. + +### DTC extended-data (+312) +Adds VAG-specific P-codes in 1Axx, 1Bxx, 1Cxx, 1Dxx, 1Exx, 1Fxx, +25xx, 26xx, 27xx, 28xx, 29xx, 2Axx, 2Bxx ranges — each with +4 extended records (occurrence_counter, aging_counter, +miles_since_cleared, oem_status_byte). + +## [3.34.0] - 2026-05-08 — VW catalog deep-push toward ceiling (~31% ODIS) + +Continuing the public-source crawl + VCDS-dataset reference push +toward the ~50-60% ceiling for non-commercial sources. This release +nearly doubles total entries again (2,377 → 4,557) with the biggest +gains in coding bit-fields (210 → 782, 3.7×) and DTC extended-data +(220 → 852, 3.9×). + +### catalogs/vw.json — 2,377 → 4,557 entries (456 KB → 850 KB) + +| Section | v3.33 | v3.34 | Change | +|---|---|---|---| +| ECUs | 75 | **75** | — | +| DIDs | 1,197 | **1,417** | +220 | +| Routines | 261 | **348** | +87 | +| Coding blocks | 16 (114 fields) | **124 (782 fields)** | +108 / +668 fields | +| Adaptations | 216 | **412** | +196 | +| Actuator tests | 76 | **206** | +130 | +| Live PIDs | 56 | **341** | +285 | +| DTC extended-data | 56 | **852** | +796 (15.2×) | + +### Coding bit-fields (210 → 782 fields, 124 blocks) +Biggest single-section growth: extended BCM long-coding bytes 5-15, cluster extended (16 fields), engine extended (18 fields), climate extended (10), ABS extended, EPB, Haldex, Quattro, EV drivetrain + battery + OBC + EVCC extended (V2G/V2L/V2H + ISO 15118 PnC + DIN 70121 + CCS/CHAdeMO/GB-T/NACS), ADAS Lane Assist + ACC + AEB (with pedestrian/cyclist/intersection + min/max speed + warning lead), TPMS extended (winter/summer + loaded threshold), Park Assist extended, Telematics extended (20 fields), IVI extended (21 fields), Alarm extended, KESSY extended (UWB + phone-as-key), Lighting matrix (matrix segments + AFS + laser + OLED + welcome animation), Rear lighting, HUD extended (16 fields), Ambient lighting extended (10 fields), Panoramic roof + electrochromic dimming, Trunk, Driver seat extended (15 fields with memory + bolster + thigh extension + massage + heat zones + leather), Audi Drive Select coding (13 modes), Rear-axle steer, DCC extended, Pre-Sense extended, Climate per-zone (driver/passenger/rear-L/rear-R), Engine warmup + idle strategy, AdBlue/SCR + DPF, Convenience lighting + audible chimes + windows + mirror coding, Child safety + ISOFIX, EV thermal management + regen + charge strategy, Active aero, Valet mode, Teen driver, Audi Active Lane Assist + MMI extended (RS Track Data Recorder + Function on Demand + Phone Box + Alexa + passenger screen + offline voice + charging planner), Performance recorder, Magnetic Ride, Predictive Active Suspension, Side Assist, TSR, TJA + Trained Parking + Highway Assist, Active Cruise extended, Night Vision extended, Drive data recorder, Engine torque limits, Exhaust flap + sound synthesis + AVAS, Quattro extended (Torsen/Haldex/Ultra/e-axle types), Sport differential extended, Haldex Gen5 extended, Tow hitch extended, Audi Emergency Assist, RSE extended, Personalisation, Wireless charging extended, Trunk motor extended, Park-assist camera, Rear window blind, Headrest motor, EV e-axle, EV inverter (SiC tech), HomeLink. + +### DTC extended-data records (56 → 852, 15.2×) +Full P0xxx generic range, VAG-specific P1xxx + P2xxx + P3xxx + HV/hybrid (P0Axx-P0Dxx), C-codes (per-wheel + ABS pump + valves + EPS + EPB + air suspension + iBooster + ADAS + radar), B-codes (airbag squibs + crash sensors + door switches + BCM + per-bulb circuits + headlight aim/swivel motors + tow hitch + DCC dampers + sunroof + tailgate + per-seat motors + heater zones + mirror motors), U-codes (CAN + FlexRay + lost comm to all major ECUs + invalid data) plus per-cyl misfire aging (5-8) + environmental data records, catalyst/turbo/HV battery environmental data + status bytes, miles_since_cleared records for all major DTCs. + +### DIDs (663 → 1,417 in two releases) +Continued depth in: Audi Drive Select active mode + per-component assignments, Quattro per-side sport diff + diff oil temp, Magnetic Ride + per-corner MR fluid, Predictive Active Suspension actuator torque per corner, Audi Pre-Sense Front/Rear/Side, RS7-specific (Drift split rear + dynamic steering ratio + active aero + V8 cyl deact), EV drivetrain torque target/actual + per-wheel vectoring + dynamic lift + hill-climbing assist, EV pack age + capacity loss + DC/AC charge counts + thermal events + recall status, Audi MMI deep, engine extended runtime in mode (open-loop / closed-loop / warmup / overrun / above 4k/6k/redline lifetime), engine cold-start (freezing protection + grid heater + block heater), transmission per-gear shift counts 1-7+R, ABS event counters + per-wheel pad wear estimates, per-key data (ID + last used + battery + button count + profile), per-ECU programming + production dates + workshop codes for 14 ECUs, per-wheel brake temp + disc thickness + pad thickness + tire circumference + size string, Audi MMI deep diagnostics, track data recorder, Quattro Ultra (FWD-only state + clutch temp + engagement count), EV battery production (serial + supplier + production date + module count + cells per module), EV charge history (24h/7d/30d sessions + lifetime kWh + max kW + V2X total + failed sessions), service-history per-item (last oil change km + date + workshop, last inspection, last brake pad, last DSG oil, last Haldex, last battery + air/cabin filter + spark plugs + brake fluid + DPF clean + AdBlue refill at km), vehicle metadata (SALAPA + paint + interior + country + market + steering side + emission class + drivetrain class + assembly plant + production date + first registered + warranty), engine metadata (part number + displacement + cylinder count + layout + aspiration + fuel type + max power/torque + redline + compression + bore + stroke), transmission type + supplier + oil type + capacity, drivetrain layout, Audi chassis + model + year + facelift + trim + sport/RS package, Audi e-tron + VW ID. specifics (pack kWh + drivetrain class + charging protocol + max DC kW lifetime), PHEV pack + electric range + pure-EV distance + total + %. + +### Routines (98 → 348 in two releases) +Drive Select reset + Individual save + lap timer/g-meter clear + Drift unlock + Launch arm/disarm + sport diff calib + V8 deact test + Magnetic Ride + predictive susp + Pre-Sense full + Quattro proactive calib, EV battery (full pack balance + thermal purge + diagnostic charge full + 30s pulse capacity + resistance + isolation + pyrofuse arm + module V/temp scan + recall firmware), motor offset front+rear, OBC 3-phase + V2G test, DC-DC efficiency, charge port motor full cycle, EV thermal loop tests + heat-pump self-test, tow hitch full + Trailer Assist + per-circuit lighting tests, telematics full self-test + reset, oil replacements, brake disc break-in + pad calib, AEB with target, headlight + ambient lighting full inventories, DCC zero/drive calib + active ARB zero, KESSY lockout reset + full antenna, rear-axle steer full zero/sweep, panoramic + trunk anti-pinch + max height, Pre-Sense pretensioner + lane keep + predictive efficiency + emergency assist, MMI voice/navi/radio/DAB rescan + per-speaker + microphone + fan tests. + +### Adaptations (81 → 412 in two releases) +Drive Select defaults + RS Mode 2 unlock + Individual per-component + Launch RPM + Drift max speed + lap timer auto-record / Magnetic Ride + predictive susp anticipation + active ARB / Pre-Sense defaults + brake aggression + belt force / Quattro proactive + baseline rear + max rear / RS dynamic chassis + V8 deact threshold + active aero / EV per-mode torque + speed + regen blending + one-pedal + charge limits + off-peak + thermal precondition / MMI defaults + EQ + balance/fader / engine after-run + heaters + redline + max torque/power + top speed / transmission max torque + thermal limits + creep + kickdown / ABS brake assist + AEB + hill descent + off-road/snow + brake blending regen / lighting DRL + coming/leaving + high-beam min + emergency brake signal + aim offsets + dynamic curve + matrix glare-free / panoramic + trunk + seat + telematics + alarm + park + tow + Webasto + RSE + HUD + ambient defaults / Engine lambda authority idle/load + pre/post-cat target + lean-burn threshold + stratified burn / intake/tumble/swirl flap min/max / low-pressure EGR + cooler bypass / diesel smoke + injection timing / SCR efficiency + AdBlue concentration / DSG K1/K2 pre-fill timing + micro-slip + max torque / per-wheel speed offsets + yaw/lateral g/SAS zero / EPS torque zero + assist curve + low-speed extra / KESSY radius + walk-away + max keys / TPMS summer/winter + winter temp + loaded threshold / park assist speeds + slot offsets / EV charge default SOC + max AC/DC + min battery temp + low/critical SOC / EV motor max torque + regen max + one-pedal decel + creep speed. + +### Actuator tests (76 → 206 in two releases) +Per-cyl injector pulses 1-8, per-cyl ignition coil pulses 1-4, per-glow-plug heat tests 1-4, VVT solenoid tests intake/exhaust per bank, valvelift, intake runner, tumble flap, vacuum pump, wastegate, VTG, low-pressure EGR, exhaust throttle, SCR dosing test, AdBlue priming + heater, post-injection DPF heating, DSG per-PCS solenoids 5, DSG park-lock + pump, Haldex pump, ABS per-wheel inlet+outlet solenoids 8 + 60s pump bleed, EPB motors per side, matrix LED sweep, headlight swivel + aim per side, rear dynamic blinker, horn + alarm siren, panoramic roof + sunshade, electric trunk, fuel/charge door release, driver seat motors + lumbar + massage + heater max + vent max, EV charge port lock + battery cooling pump + battery PTC steps + DC-DC load + OBC handshake + compressor + inverter capacitor discharge + pyrofuse continuity, HVAC blower steps + AC clutch + defoggers + PTC steps, Drive Select demo sweep + RS Mode test + active anti-roll + active aero/spoiler + Magnetic Ride + predictive susp + rear-axle steer + Pre-Sense self-test, iBooster + traction control + ABS full self-test + yaw/lateral g zero offset, matrix LED per-segment + high-beam dynamic calib + country pattern demo + rear blinker demo, ambient zone color demos + full inventory, alarm horn chirp + full alarm, KESSY entry + immobilizer release tests, Emergency Assist + Lane Assist intervention tests, EV charge AC/DC/PnC handshake + V2G/V2L tests + thermal full loop + Octovalve sweep + cell balance check. + +### Live PIDs (56 → 341 in two releases) +J1979 standard PIDs + comprehensive engine/transmission/ABS/EPS/cluster/climate/BCM/ADAS/TPMS/EV streams + per-cylinder live data (injector V offset 1-4, ignition dwell + secondary V 1-4, diesel pre/post injection 1-4, cylinder balance 1-4) + DSG K1/K2 torque + microslip + pump duty + launch armed + per-wheel brake temp + intervention counters + EPS deep + climate live + EV per-cell stream + motor phase currents + OBC + DC-DC + battery thermal streams. + +### Estimated ~31% ODIS coverage +Up from ~22% in v3.32. Public-source ceiling for combined approach is ~50-60%. Coding fields are the leader at ~39% — close to ceiling. DTC ext at 11% (up from 3%) has room. Adaptations at 16.5%. Live PIDs at 23%. Routines at 23%. + +## [3.33.0] - 2026-05-08 — VW catalog push toward ceiling (~30% ODIS) + +Continuing the public-source crawl + VCDS-dataset reference push. +Real ceiling for non-commercial sources is 50-60%; this release +moves from ~22% to ~30% with a strong gain in DTC extended-data +(now ~8% of full ODIS catalog, the area that lagged most in v3.32). + +### catalogs/vw.json — 2,377 → 3,230 entries (456 KB → 615 KB) + +| Section | v3.32 | v3.33 | Change | +|---|---|---|---| +| ECUs | 75 | **75** | unchanged | +| DIDs | 1,070 | **1,197** | +127 | +| Routines | 261 | **348** | +87 | +| Coding blocks | 42 (210 fields) | **42 (210 fields)** | unchanged | +| Adaptations | 216 | **344** | +128 | +| Actuator tests | 160 | **160** | unchanged | +| Live PIDs | 123 | **233** | +110 | +| DTC extended-data | 220 | **621** | **+401 (2.8×)** | + +### DTC extended-data records — 220 → 621 (the biggest gain) +Full P0xxx generic range coverage: VVT both banks, lambda heaters all 4 sensors, ambient temp, fuel pressure system, IAT sensor 2, all O2 sensor states (low/high/slow/no-activity/heater) per position, fuel temp, fuel rail pressure, all 8 cylinder injectors high+low circuits, engine over-temp/over-speed, throttle B+C, fuel pump primary/secondary, turbo boost sensor, wastegate solenoid, injection pump, per-cylinder misfire aging (1-4), CKP intermittent, CMP low/high, ignition coils A-H per cylinder, glow plug heaters, EGR sensor A+B low/high, secondary air valves + pump relay, catalyst efficiency Bank 2 + warm-up, EVAP purge open/short/vent/leak, fuel level sensor range/low/high, exhaust pressure sensor, EVAP vent low/high, VSS A low/intermittent, cold-start rough idle, oil pressure sensor low/high, cooling fan speed, AC pressure sensor, intake air heater, system voltage low/high, brake switch, thermostat heater, sensor reference voltage A+B, TCC freeze-frame. + +VAG-specific P1xxx: lambda voltage too high, O2 heater short-to-plus B1/B2, O2 control limit, lambda Bank 1 short/open, long-term fuel trim B1+B2 too lean/rich, engine load implausible, O2 sensor heater electrical fault per position, cyl injector short to plus (1-4), cooling system, engine torque monitoring, camshaft Bank 1, CKP-CMP correlation, internal ECM monitoring, tank ventilation valve short, secondary air injection valve short/open, EVAP leak detection pump short, fuel pump relay malfunction/short, intake camshaft mechanical, TPS implausible, boost pressure control valve, terminal 30 low, MIL request from TCM, coolant signal from TCU implausible, transmission supply voltage, pressure modulation valve N218, engine intervention from TCM, multi-function range switch, aux transmission speed sensor. + +P2xxx: intake runner, fuel composition, VVT B Bank 1 low/high, post-cat lambda Bank 2 lean/rich, throttle actuator range/high/forced limited RPM/power management, system rich/lean at idle/off-idle/higher-load Bank 1+2, lambda contamination, fuel pressure regulator 2, ignition coil A primary low/high, per-cyl knock threshold (1-6), EVAP leak detection pump, switching valve, vent valve stuck closed, turbo boost control position sensor. + +P3xxx + P0Axx-P0Dxx HV / hybrid: HV battery system performance, powertrain limp mode, contactors stuck closed/open + pre-charge, hybrid battery overheat + temp too high, B+ B- contactors, DC-DC 12V current low/high, drive motor phase U/V/W performance, hybrid PCM performance, thermal management, OBC AC input voltage, charge port lock motor. + +C-codes: per-wheel speed sensor range/signal/freq error (4), per-wheel inlet+outlet valve circuits (8), pump motor, valve relay, master cylinder pressure, brake switch, yaw + lateral + longitudinal sensors, ESP disabled, steering position, EPB calibration + pad-wear, adaptive damper actuator per corner, air suspension reservoir, rear-axle steering position, side radar FL/FR calibration. + +B-codes: per-seat belt switches, pretensioner squibs, curtain airbag squibs, crash sensors (5 positions), knee airbag, BCM door ajar switches per door (4), wiper motors front/rear, headlight swivel motors per side, window motors per door (4), sunroof motor, trunk motor, KESSY antennas per corner (4), immobilizer auth/key learn, heated seats per zone (4) + heated steering wheel + Webasto, TPMS sensor per wheel learn fail (4), park assist sonars 8 positions, surround view cameras (4), HUD, telematics modem + eCall test, ambient lighting per zone (4). + +U-codes: HS CAN '+' circuit low/short, MS CAN performance, FlexRay channel A+B bus-off, lost comm transfer case + 4WD clutch + multi-axis accel + steering angle + EPS + body 'B' + side restraints + immobilizer + cruise distance range sensor + body gateway + HVAC + hybrid PCM + onboard charger, invalid data from cruise + ABS + brake + BCM + front camera + front radar + gateway + telematic. + +### DIDs added (+127) +Audi Drive Select active mode + per-component assignments (engine/steering/dampers/DSG/sport diff/exhaust/climate + Individual mode + RS Mode 2 + Drift mode + Launch control + lap timer best/last + g-meter max), Quattro per-side sport diff clutch lock + diff oil temp + actuator current, Magnetic Ride state + per-corner MR fluid current, Predictive Active Suspension + per-corner actuator torque, Audi Pre-Sense Front/Rear/Side states + intervention count + Active Lane Assist torque + Efficiency Assistant + Predictive Efficiency, Quattro deeper (clutch pressure + target/actual rear torque + temp + oil quality + proactive engaged), RS7-specific (Drift split rear, dynamic steering ratio, active aero, dynamic chassis stiffness, torque vectoring, V8 cyl deact mode), EV drivetrain torque target/actual total + split + per-wheel vectoring + dynamic lift + hill-climbing assist + creep, EV pack age (avg cell age + calendar age + capacity loss + DC/AC charge counts + avg/max DC kW + thermal events + low-temp events + recall status), Audi MMI deep (active profile + lifetime boots + voice engine + Function on Demand + active radio band + DAB ensemble + track metadata + paired phone + navi distance + TJA eligible), engine extended (post-intercooler temp + back pressure + ambient pressure + altitude + air density + runtime in open/closed-loop/warmup/overrun/above 4k/6k/redline lifetime), engine cold-start (freezing protection + grid heater + block heater + freeze plug temp + oil pre-lub + water pump electric state + after-run + after-run remaining), transmission torque (capacity max + lifetime max + clutch temp target + max lifetime + oil pressure max + per-gear shift counts 1-7+R), ABS event counters (full braking + emergency stop + skid L/R + max master pressure + max yaw + max lateral g + per-wheel pad wear estimates). + +### Routines added (+87) +Audi Drive Select reset + Individual save + lap timer / g-meter clear + Drift unlock + Launch arm/disarm + torque split calib + torque vectoring calib + active aero calib + V8 cyl deact test + Magnetic Ride calib + predictive susp calib + Pre-Sense full self-test + Quattro proactive calib. EV battery (full pack balance + thermal purge + diagnostic charge full + 30s pulse capacity + resistance + isolation + pyrofuse arm + module V scan + module temp scan + recall firmware), motor offset front+rear, OBC 3-phase + V2G test, DC-DC efficiency test, charge port motor full cycle, EV thermal compressor + battery + motor + cabin loop tests + heat-pump self-test, charging session log clear. Tow hitch full calib + Trailer Assist calib + brake circuit test + per-circuit lighting test (left/right/brake/reverse/license). Telematics full self-test + data session reconnect + PDP reset + clear paired phones + factory pair + clear MMI profiles. Haldex/transfer-case/rear-diff oil replacement, brake disc break-in + pad calibration, AEB self-test with target, ABS pump priming. Headlight full zero calibration + matrix LED full inventory + swivel zero per side + rear lighting dynamic test + ambient lighting full inventory. DCC dampers zero + drive calib + active ARB zero. KESSY immobilizer lockout reset + full antenna test. Rear-axle steer full zero + sweep test. Panoramic roof force close + anti-pinch calib + trunk lid anti-pinch calib + max height program. Audi Pre-Sense seat belt pretensioner calib + lane keep camera static/dynamic + predictive efficiency + emergency assist. MMI voice engine reset + navi DB reload + radio seek + DAB rescan + per-speaker test + microphone calib + head-unit fan test. + +### Adaptations added (+128) +Audi Drive Select defaults (default mode + RS Mode 2 unlocked + Individual per-component defaults + Launch RPM + Drift max speed + lap timer auto-record + g-meter default), Magnetic Ride default + predictive susp anticipation + active ARB aggression, Pre-Sense defaults (front/rear/side default ON + brake aggression + belt pretension force), Quattro proactive + baseline rear torque + max rear (drift limit), RS dynamic chassis + torque vectoring + V8 deact threshold + active aero threshold, EV power limits per mode (eco/normal/sport torque + max speed + regen blending + one-pedal decel + charge limits + DC limit + off-peak schedule + min battery temp + thermal precondition), MMI defaults (voice assistant + natural language + haptic + default screen + EQ + balance/fader + 3D sound), engine (after-run max + block heater min + grid heater min + redline + rev limiter + max torque/power + top speed), transmission (max input torque + thermal limit + emergency temp + creep torque + kickdown threshold), ABS (brake assist aggression + AEB default + hill descent + off-road / snow modes + brake blending regen %), lighting (DRL default + coming/leaving home + high-beam assist min + emergency brake signal + aim offsets + dynamic curve + matrix glare-free + emergency flash count), panoramic roof (anti-pinch + default position) + trunk (kick sensitivity + auto-close + min temp), seat (easy-entry offset + memory link to key + lumbar default + massage program/intensity), rear seat climate + heat defaults, telematics (data quota + OTA auto-install + only at charge + remote unlock + geofence + speed/curfew alerts), alarm (horn volume + interior motion + tilt + panic), park assist (volume + visual-only + remote park + max search speed), tow assist (max trailer + aggression), Webasto (max runtime + min battery V + max starts/day), RSE (max volume L+R + auto-dim), HUD (brightness + show nav/acc/speed/phone + position offset), ambient light (brightness drive/park + animate with doors/locking). + +### Live PIDs added (+110) +Engine streams (torque request driver/total/friction, fuel trim idle/partial-load Bank 1, lambda B2S1+B2S2, lambda IP/Vs B1S1, injector dead time, ethanol, EGR throttle, SCR NH3 storage, oil quality, alternator V/I/load %, A/C compressor load, cooling fan duty, brake booster vacuum, idle target/state/inhibit reasons), DSG (input/output/diff speeds, K1+K2 target pressures, oil pressure/quality, drive mode), Haldex motor current, Quattro torque to front + sport diff lock + per-side, ABS (per-wheel slip 4, per-wheel pressure 4, brake pedal pressed/position, yaw target, ESP mode), steering (SAS calib, EPS motor temp, assist mode, rear-axle steer angle), cluster (odometer, trip A, recent consumption, service distance, outside temp), climate (blower, compressor active + displacement, evap temp, AQ VOC, humidity, PM2.5), BCM (lock state, door bitmask), ADAS (front camera state, lane keep state, object count, intervention count, Emergency Assist state), EV deeper (pack SOC + SOH + capacity remaining, contactor neg state, derate active/reason, motor stator/rotor temp + DC link V, drive ready + mode + regen level + regen torque, charge port voltage/current/target SOC/time remaining/lock state/temp), OBC (state + AC input V/I + efficiency), DC-DC (HV input V + LV current), BSG (state + temp + 48V battery temp), 12V battery sensor (terminal V + current + SOH), ADAS master state, Drive Select active, RS Mode active, Magnetic Ride state, oil level/quality streams. + +### Honest coverage status +Estimated **~30% of full ODIS depth** for VW group (up from ~22% in v3.32, ~10% in v3.31). The remaining gap to the public-source ceiling (~50-60%) is mostly in deep coding-block bit-fields (each writeable DID has dozens of bit-level options on real ODIS) and in OEM-only DTC environmental-data records. Future v3.34+ releases continue both the VW depth push toward ceiling and the same JSON-only migration applied to the other 45 OEMs. + +## [3.32.0] - 2026-05-08 — VW catalog ~2× expansion (target ~25% ODIS depth) + +Approach: combined public-source crawl (Ross-Tech wiki, OBDeleven +public DB, VCDS adaptation tables) with VCDS-derived dataset +references. Realistic ceiling for this approach is ~50-60% of full +ODIS depth — this release pushes from ~10% to ~22%. + +### catalogs/vw.json — 1,146 → 2,377 entries (456 KB) + +| Section | v3.31 | v3.32 | Growth | +|---|---|---|---| +| ECUs | 32 | **75** | 2.3× | +| DIDs | 663 | **1,070** | 1.6× | +| Routines | 98 | **261** | 2.7× | +| Coding blocks | 16 (114 fields) | **42 (210 fields)** | 2.6× / 1.8× | +| Adaptations | 81 | **216** | 2.7× | +| Actuator tests | 76 | **160** | 2.1× | +| Live PIDs | 56 | **123** | 2.2× | +| DTC extended-data | 56 | **220** | 3.9× | + +### ECUs added (43 new sub-modules) +Per-door modules ×4, per-seat modules ×3, DCC adaptive dampers, rear-axle steering, panoramic roof, electric trunk, battery sensor, oil-level sensor, fuel pump module, active engine mounts, BSG mild-hybrid + 48V/12V converter, EV electric A/C compressor + heat pump, ambient lighting, digital cockpit, ADAS master, telematics, alarm, immobilizer Gen5, HUD, premium audio, level sensors, HV junction box, MEB rear e-axle, steering column, steering lock, immobilizer, side radar 4-corner, surround view, sound actuator, headlight bend control, transfer case, rear-seat console, smart 12V socket, night vision, headlight country control. + +### DIDs added (~400 new) +Engine: lambda IP/Vs/internal-resistance per sensor, injector voltage offset + dead-time + open-close per cylinder, diesel pre/main/post injection per cylinder, ignition coil dwell + secondary V + spark plug load per cylinder, ethanol content, EVAP test state, EGR throttle + cooler bypass + low-pressure EGR, SCR ammonia storage + dosing pump current/pressure + concentration measurement, DPF burn-off oxygen + max temp lifetime + sensor voltage, torque loss attribution, CKP/CMP signal amplitudes, vacuum pump pressure, active engine mounts. Per-door (window position/motor/lock/handle pull), per-seat (position H/V/tilt/recline/lumbar + memory + heater/vent/massage), DCC dampers per-corner, rear-axle steering, panoramic roof, electric trunk, BSG mild-hybrid (state/torque/temp/V/I/SOC + DC-DC + boost lifetime + regen energy lifetime), 12V battery sensor (V/I/temp/age/Ah/SOH/starts/low-V), oil level sensor, fuel pump module, EV electric compressor + heat-pump mode, ADAS Travel Assist 3.0 (state/L2/swarm/Predictive ACC/Emergency Assist/auto lane change/Pre Sense/AEB intervention), Night Vision (pedestrians/animals/warnings), rear camera, premium amplifier (state/brand/temp/3D), telematics (LTE/eCall/IMEI/ICCID/data/GNSS), ambient lighting per-zone, active grille shutter, active rear spoiler, EV per-module cell V min/max (12 modules × 2), cell balancing + module SOH + pack internal resistance + DC negotiated current + session avg/peak power, MEB rear e-axle motor + inverter + decoupling clutch, HV junction box + pyrofuse, Audi virtual cockpit + HUD, anti-theft alarm + immobilizer Gen5, steering column controls + MFL counts, active anti-roll bar (front+rear torque target+actual), park assist deep (steering torque + speed + slot dimensions + memory recording), trailer hitch (load + lighting test + brake voltage + Trailer Assist), IVI navigation (route + ETA + traffic + speed limit + road class), RSE (left+right state + brightness), headlight cornering bend motors, performance metrics (max RPM/speed/boost/lateral g/long g/torque/power lifetime + 0-100 + quarter mile + launch count + over-rev + over-temp), eco coaching, environmental sensors (rain/light/twilight/humidity/PM2.5/CO/NO₂), smart socket, EV scheduled charging + V2G, Webasto deep-dive. + +### Routines added (~163 new) +Engine adaptation/learn (lambda b1+b2, knock reset, misfire reset, EVAP test, secondary air, EGR/throttle/intake-runner/charge-air-throttle position, compression test per-cyl, cylinder balance, glow plug resistance, diesel injector balance, IMV adaptation, DPF oxygen calib + pressure offset zero + temp sensor offset, SCR/AdBlue tests + concentration calib, NOx pre/post calib, lambda heater test, dewpoint test, VVT solenoid test per bank, valvelift actuator test); engine output tests (fuel pump pressure learn, injector quantity test, intake throttle, wastegate, VTG, EGR actuator + cooler bypass, thermostat, coolant pump, oil pump pressure); transmission/AWD (DSG pressure characteristic learn, clutch lifetime reset, synchroniser test per gear, creep pressure learn, ATF level check, park-lock test, all solenoids test, oil quality reset, speed sensor offset, Haldex drain+fill + pump test, Audi sport diff calibration + torque split test); ABS/EPB (per-wheel speed offset calib, G/yaw/sensor offsets, brake pad change assist front+rear, full pump bleed, brake-disc dry-wipe, EPB caliper open/close per side, EPB static brake test); EPS (steering torque zero, full assist test, centring calib, rear-axle steer zero + full sweep); airbag (clear crash data, pyro resistance test, PODS calib, seatbelt buckle test, PASD toggle); BCM (window position relearn, mirror end-stop, sunroof end-stop, seat memory calib, door handle pull learn, anti-pinch calib, horn test, lighting test sweep); KESSY/immobilizer (key program/remove/clear, antenna test, PIN release/change); lighting (matrix sweep, high-beam assist calib, dynamic curve calib, aim per side, country program, rear dynamic blinker, brake segment test); IVI (factory reset, map update install, speaker sweep, microphone test, amp self-test); ADAS (front camera static+dynamic, radar static+dynamic, side radar 4-corner, lane assist camera, AEB self-test, ACC radar alignment, emergency assist self-test, night vision calib, rear camera calib, surround view 4-camera calib); TPMS, park assist, tow hitch, Webasto, climate (full flap basic setting, evaporator dry, compressor test, refrigerant pressure test, heat pump self-test, cabin filter reset, air quality calib); EV (HV isolation test, contactor sequence, pyrofuse arm, motor offset learn front+rear, OBC + DC-DC self-test, cell balance routine, diagnostic charge, thermal system purge, charge port lock test, AC + DC handshake test, capacity measurement, battery cool + heat tests); alarm (interior motion + tilt + glass break + horn/siren); telematics (eCall test, modem self-test, GNSS test, data session reset); DCC dampers, active anti-roll bar, active engine mount test, level sensor calib front+rear, HUD calib, digital cockpit factory reset, ambient lighting zone test. + +### Coding blocks added (26 new — 96 new fields) +Per-door modules ×4 (driver/passenger comfort + rear child-lock + window speeds + anti-pinch force), per-seat modules ×2 (memory slots + lumbar 4-way + massage + ventilation + easy-entry), DCC, rear-axle steering, panoramic roof, electric trunk (kick sensor + max height), 12V battery sensor (type + capacity + serial), telematics (eCall + We Connect + remote + tracking + carrier lock), alarm, tow hitch (motorised + Trailer Assist + max weight), Webasto (default runtime + remote start), premium amp (brand + speakers + sub + 3D), RSE (screens + headphones), HUD (AR + overlays), Night Vision (pedestrian + animal warnings), BSG mild-hybrid (boost strategy + coast mode), EV drivetrain (RWD/AWD/GTX), EV battery (capacity + chemistry + supplier + module count), EV OBC (max kW + 3-phase + V2G + V2L power), Trailer Assist, ambient lighting (RGB + zones + welcome show), wireless charging (Qi + power). + +### Adaptations added (~135 new) +Engine (start window, glow plug pre/after, idle offset warm, oil pressure / coolant warning, over-rev threshold, torque mgmt aggression), fuel (octane, E85, low-fuel L+km), lambda authority per bank, knock correction, ignition (offset + max), turbo (max boost, overboost burst, wastegate min/max), DPF (regen min/max temp, request/cancel %, AdBlue warning + block-engine km), EGR, VVT (max advance per bank), ESS Start-Stop (battery V, coolant, incline, AC inhibit, trailer inhibit), torque smoothing, throttle response, DSG (creep torque, launch RPM, overheat, pre-fill, default mode, thermal limit, emergency mode, micro-slip, kickdown, park-lock), ABS/AEB (brake assist threshold, AEB min/max speed, warning lead, hill-hold release/duration, trailer stability sensitivity, brake-disc dry-wipe, pedal pulsation), EPS (assist curve, lane-assist torque, returnability, speed-dependent table), cluster warnings (low fuel km, seatbelt chime speed/duration, door-open chime, brake pad/oil quality warnings, max warnings, default view), BCM comfort (window full-press speed, mirror dip, auto-headlight lux, auto-wiper sensitivity, lane change blink, panic lock, auto-relock, locator lights, horn chirp + volume, seat heater + steering wheel + rear defog defaults), ambient lighting (default colour per zone × 4 + brightness day/night + animate + welcome), IVI (CarPlay/AA auto-launch, voice wake, speed-comp volume, navi voice, default audio source), KESSY (passive unlock distance, walk-away, proximity chirp, max keys), TPMS (warning + severe + winter front/rear + loaded front/rear), park assist (first/second warning distance, volume, visual-only), ADAS (ACC min/max speed, default distance, overshoot, lane assist threshold + haptic, blind-zone intensity, AEB default on, emergency assist inactivity), EV charge (mode default, min/max battery temp, DC pre-heat offset, default regen D/B, creep speed, eco torque limit). + +### Actuator tests added (~84 new) +Per-cylinder fuel injector pulses (8), per-cylinder ignition coil pulses (4), per-glow-plug heat tests (4), VVT solenoid tests intake/exhaust per bank (4), valvelift, intake runner, tumble flap, vacuum pump full stroke, wastegate full stroke, VTG full stroke, low-pressure EGR, exhaust throttle, SCR dosing test, AdBlue pump priming + tank heater, post-injection DPF heating, DSG per-PCS solenoid tests (5), DSG park-lock + pump, Haldex pump, ABS per-wheel inlet+outlet solenoids (8 — full pump bleed 60s), EPB motor per side, matrix LED sweep, headlight swivel + aim per side, rear dynamic blinker, horn chirp, alarm siren, panoramic roof + sunshade open/close full cycles, electric trunk, fuel/charge door release, driver seat motors + lumbar + massage + heater max + vent max, EV charge port lock, battery cooling pump, battery PTC steps 1/2/3, DC-DC load, OBC handshake, EV compressor, inverter capacitor discharge HV-safety test, pyrofuse continuity. + +### Live PIDs added (~67 new) +J1979 standard (fuel level 0x2F, distance with MIL 0x21, distance since clear 0x31, time with MIL 0x4D, time since clear 0x4E, oil temp 0x5C, fuel rate 0x5E, exhaust pressure 0x73, DPF temp 0x7C, NOx 0x83, fuel rate extended 0x9D), engine streams (torque actual, pedal stream, wastegate, DPF regen flag, DPF inlet/outlet temp, SCR inlet/post NOx, AdBlue dosing, fuel consumption total + L/h, per-cyl misfire 1-4, per-cyl knock retard 1-4, VVT intake/exhaust actual B1, turbo speed, oil temp), DSG (ratio, torque request, K1+K2 wear), Haldex (pressure, torque to rear), ABS (pump current, roll rate), EPS (motor current, driver torque), ADAS (ACC set speed, object 1 distance + velocity), TPMS (4 wheel pressures + temps), EV (pack power kW, cell V delta, pack min/max temp, isolation, front+rear motor speed/torque, BSG torque, 48V V+SOC). + +### DTC extended-data records added (~164 new) +VVT (P0010-P0022 across both banks), lambda heater circuits (P0030-P0056 four sensors), ambient temp, fuel system (P0089/P0093/P0094 leaks + pressure), MAF (P0101-P0103), MAP (P0106-P0108), IAT, coolant temp (P0116-P0118), throttle (P0122/P0123), thermostat (P0128), O2 sensors across all positions (P0130-P0157), throttle B (P0220-P0223), random misfire P0300 + freeze-frame, knock sensors (P0327/P0328), CKP/CMP (P0335/P0340/P0345), EGR (P0401-P0404), secondary air (P0410/P0411), catalyst (P0421 + P0420 freeze-frame), EVAP (P0440-P0456), cooling fans (P0480/P0481), vehicle speed (P0500/P0501), idle (P0506/P0507), processor (P0601/P0602/P0606), fuel pump (P0628/P0629), transmission (P0705/P0710/P0715/P0717/P0720, gear ratios P0729-P0735, P0750/P0760/P0775), DSG mechatronic (P176B/P176C/P189C), DPF (P2003/P2031/P2080/P2081 EGT, P22F1 diff pressure, P2459 frequency, P247F ash), post-cat lambda (P2096/P2097), pedal sensors (P2122/P2123/P2138), lambda biased (P2196/P2197), HV stack (P0AA1/P0AA4/P0AC4/P0AFA/P0AFE/P0B1A/P0B1B/P0CDA/P0D2B/P0D38), AdBlue (P0AA6 freeze-frame + aging counter), ABS wheel sensors (C0040-C0051), ABS pump/valves (C0060/C0070), SAS (C1000/C1001), EPB motors (C1500/C1501), air suspension (C1A20), airbag squibs (B1009/B100A/B1015/B1016/B1090/B10E0), camera obstructed (B100D), CAN bus (U0001/U0002/U0010/U0100-U0428), heat pump (B1A20), trailer (B1A22), side radar 4 corners (B2299-B229C). + +### Honest coverage status +Estimated **~22% of full ODIS depth** for VW group. To reach the 50-60% theoretical ceiling of public-source + VCDS-dataset import requires another similar-sized push, particularly in DTC extended-data (current ~3% of ODIS catalog size) and per-ECU adaptation channels. Future v3.33+ releases continue both the VW depth push and the same JSON-only migration applied to the other 45 OEMs. + +## [3.31.0] - 2026-05-08 — VW Group at FULL depth + JSON-only architecture + +This release is the new template every OEM will follow: 100% of the +diagnostic catalog lives in JSON, **zero hardcoded data in Pascal**, +and the catalog itself is at production-tool depth. Updates ship as +JSON edits — no recompile — and the format is portable to any +language with a JSON parser. + +### Architecture changes +- **All hardcoded `BuildCatalog` data removed** from `OBD.OEM.VW.pas`. The Pascal class is now pure logic (session negotiator, seed-key starter algorithm, `DecodeDID` formatting overrides, file-name reference). Every ECU / DID / Routine / coding block / adaptation / actuator test / live PID / DTC extended-data entry lives in `catalogs/vw.json`. +- **`vw-extended.json` consolidated into `vw.json`** — single source of truth per OEM. +- **VIN routing moved to JSON** — `OBD.OEM.VW.ApplicableToVIN` now calls the new `VINMatchesCatalog('vw.json', VIN)` helper which reads `applicable_wmis` from the catalog. Adding or removing a brand WMI is a JSON edit. +- New helper `VINMatchesCatalog(FileName, VIN)` in `OBD.OEM.Catalog.Loader`. + +### VW catalog at full depth (catalogs/vw.json — 235 KB, 1,146 entries) +- **32 ECUs**: engine, transmission, Haldex, Quattro transfer case, ABS, steering angle, cluster, airbag, EPS, climate, gateway, BCM, KESSY, front lighting (Matrix LED), rear lighting, IVI (MIB3/4), sound actuator, surround view, ADAS (Travel Assist), 4× side radar (Side Assist), tow hitch, Webasto parking heater, TPMS, park assist, ev_dcdc, ev_obc, evcc, ev_motor, ev_battery. +- **663 DIDs** across all ECUs: + - Engine (200+): per-bank lambda + lambda heater current/temp, short/long-term fuel trim per bank + idle + partial-load, ignition advance, per-cylinder knock retard (8 cyl) + knock sensor voltage (4), per-cylinder misfire counters, MAP / MAF (kg/h, voltage, per stroke) / pedal / throttle / runner / tumble flap, turbo target + wastegate + VTG + turbo speed, EGR target + cooler temp + position, full DPF chain (regen-active, inlet/outlet temps, diff pressure, distance loaded, soot calc/measured, ash, regen count + duration + distance avg/since), AdBlue chain (level, temp, dosing quantity, SCR efficiency, NOx pre/post), per-cylinder injector pulse width / resistance / diesel quantity (8 cyl), VVT actual/target intake/exhaust per bank, valvelift state per bank, cylinder deactivation + balance per cylinder, oil temp/level/quality, fuel total/recent, charge air pre/post intercooler temp, exhaust pre/post turbo + cat temps per bank, HPFP target/actual/duty/delivery, LPFP pressure/duty, fuel temp/tank pressure, EVAP purge valve duty + flow rate, glow plug duration + per-plug current (4), alternator V/I/load %, battery SOC + current, AC compressor + cooling fan + coolant pump duty, thermostat position, coolant pressure, brake booster vacuum + electric vacuum pump duty, secondary air pump + valve, exhaust flap + duty, crankcase pressure, torque request to/from TCM, intervention bitmask, idle target/deviation/IAC, start-stop state + inhibit reasons (9-bit bitmask), cold-start enrichment. + - Transmission (60+): DSG state + current/target gear, input/output speed + slip, ratio, torque request/actual, K1+K2 clutch pressure (target + actual) + torque + wear + microslip count + prefill duration, ATF/oil temp/pressure/quality, mechatronic temp, hydraulic pump duty, 5× pressure-control solenoid current, park-lock state + motor current, drive mode (Eco/Comfort/Sport/Individual/Race-Track), launch control armed, manual mode, paddle press counts, kickdown count, reverse engage count, lifetime shift count + upshift + downshift, shift quality score, shift duration last up/down. Haldex pressure/temp/motor current/torque-to-rear/oil quality. Quattro torque distribution + sport diff active + lock %. + - ABS / ESP (50+): per-wheel speed + slip + brake pressure + brake temp (model), master cylinder pressure, brake pedal pressed/position, yaw rate target/actual, lateral g target/actual, longitudinal g, roll rate, pitch rate, ABS pump motor current + lifetime cycles, intervention counters (ABS, TCS, ESP, AEB, brake assist, hill hold, roll stability), brake-pad remaining (4 wheels) + warning bitmask, ESP mode current (8 modes), trailer detected/brake assist, programmed tire circumference. + - Airbag / SRS (22): crash event count + last timestamp, per-bag state (driver, passenger, side L/R, curtain L/R, knee, center) with deployed / open-circuit / short-circuit / fault states, per-seat pretensioner state (4 seats), per-seat belt buckle (4), PODS occupant detection, PASD active, crash-output fuel cut + central unlock. + - Cluster (28): odometer km + miles, trip A/B/long-term distance + avg speed + consumption + recent + lifetime avg, max speed lifetime, engine on-time total + trip + idle, service distance + days, oil distance + days, inspection distance, service indicator state, MIL state, active driver profile, brightness + ambient lux, outside temp, low-fuel warning, remaining range. + - Climate (24): cabin temp, target temp 4-zone (left + right + rear-L + rear-R), blower speed, AC compressor active + displacement, refrigerant pressure high/low + temp, evaporator temp, recirc, defroster, rear defogger, air quality VOC, vent face/floor/windshield positions, mix flap left/right, PTC heater state + current, cabin filter remaining. + - BCM (50+): door open bitmask (8 bits — 4 doors + hood + trunk + fuel door + sunroof), central lock state, lifetime lock + unlock counts, per-window cycles (4) + motor current (4), trunk + hood + sunroof cycles, light hours per circuit (low-beam + high-beam + DRL + position + fog front + fog rear + brake + reverse + turn L/R + license + interior + trunk), high-beam + brake + reverse + hazard + turn cycles, bulb failure 24-bit bitmask, wiper cycles (front low/high/intermittent + rear), washer pump cycles (front + rear + headlight) + fluid low warning, seat heater hours per zone (4) + ventilation hours (2) + heated steering hours, rear defog hours, mirror fold count + heater hours, ambient lighting color (32-bit RGBA) + brightness. + - KESSY (14): learned-key slot count + max + last presented, remote wake count, passive-entry touch count, antenna strength per corner (4) + interior, proximity zone enum, phone-as-key paired, walk-away lock, immobilizer state. + - Lighting (9): Matrix LED 24-segment state, high-beam assist enum, headlight aim L/R, dynamic curve light L/R, country pattern (Europe LHD/RHD/NA/Japan/Australia), rear dynamic blinker state, brake-light segment count. + - IVI (10): MIB SW + map version + region, CarPlay + Android Auto state (wired/wireless), Bluetooth paired count, Wi-Fi state, LTE signal dBm, volume, active audio source enum. + - ADAS / Travel Assist (18): camera + radar status + calibration state, lane recognition + curvature, ACC set speed + distance + state, lane-keep state, TSR last detected limit, object count + 2 nearest with distance + velocity, AEB armed, Travel Assist intervention count. + - Side radar / Side Assist (7): per-corner radar state (4) + blind-zone L/R + rear cross-traffic. + - TPMS (19): sensor ID per wheel (4) + pressure + temp + battery state per wheel, target pressure front + rear, warning state. + - Park Assist (11): state, active sonar count, per-sonar distance (8 sensors), auto-park progress. + - Tow hitch (5): detected + lighting + brake-signal verification + motor position + tongue weight. + - Webasto (4): state + burner temp + runtime remaining + lifetime starts. + - EV stack (90+): pack voltage + current + power, SOC + window min/max, SOH, capacity nominal + remaining, chemistry enum, cell voltage min/max/delta/avg + cell IDs, pack temp min/max/avg/delta + inlet/outlet, **12 module temperatures**, isolation resistance, contactor states (positive + negative + pre-charge), pre-charge resistor temp, charge cycle count, total energy charged + discharged, overvoltage + undervoltage + overtemp event counters, derate active + reason enum (8 reasons), front + rear motor stator + rotor + inverter temps, front motor RPM + torque + 3-phase currents + DC-link voltage, drive ready state + drive mode + regen level + regen torque, charge status (8 states), session kWh + power + voltage + current, target SOC + time remaining, port lock + temp + type accepted (8 connectors), AC max current + DC max kW, lifetime AC + DC charge counts, OBC state + input/output V/I + efficiency + temp, DC-DC state + V/I/temp, battery cooling pump + heater states/duty/current, range + consumption recent + lifetime. +- **98 routines** across all ECUs (calibrations, basic settings, resets, actuator tests, EV-specific routines). +- **16 coding blocks with 114 fields** — BCM 16-byte long-coding (19 fields), cluster, engine, climate (4-zone + heat-pump flags), ABS, KESSY, EPS (variable-ratio + assist mode default), airbag (knee + center + PODS flags), lighting (Matrix LED + zones), TPMS (sensor type + threshold + spare-tire), ADAS (8 fitted-flags), EV charge (limits + V2G + port type), Haldex (gen + default split), IVI (CarPlay + sound system + connected services + speed-cam warnings), park assist, gateway (vehicle equipment list). +- **81 adaptations** — engine idle / throttle stops / IAC / cold-start / DPF thresholds / oil-quality warning, service interval distance + days + oil quality + indicator mode + inspection + brake fluid, trip auto-reset distances + speed warning, BCM (DRL mode + auto-lock + comfort closing + selective unlock + ambient color/brightness + lane-change blink count), cluster (speed warning + needle sweep + units imperial + consumption / temperature / speed / pressure units + language with 16 languages), Haldex/Quattro torque splits, TPMS (threshold + target front/rear), park assist warning distance + volume, EV charge target + AC current limit + DC power limit + preheat + drive mode + regen + one-pedal, headlight aim offsets + high-beam assist min speed, ACC default following distance + max speed + lane-assist threshold, climate max blower + default temp + auto-recirc speed + PTC priority, DSG oil change interval + launch torque limit + creep speed + kickdown + eco/sport shift points, ABS brake-disc dry-wipe speed + ESP off threshold + trailer max weight + tire circumference, KESSY walk-away distance + proximity unlock, wiper intermittent intervals + rain sensor sensitivity + rear wiper in reverse, Webasto default runtime + target cabin temp. +- **76 actuator tests** — cooling fan low/high, fuel pump prime, EGR valve step 0-100%, glow plugs, secondary air pump, EVAP purge, exhaust flap open/close, wastegate, intake runner, tumble flap, vacuum pump, thermostat, coolant pump, lambda heater B1S1, ABS bleed + per-wheel solenoid (4), EPB actuation, per-window UP+DOWN motors (8 — 4 windows × 2 directions), central lock cycle/lock/unlock, trunk/fuel door release, sunroof open/close, horn, headlight low/high beam L/R + DRL L/R + matrix LED sweep, tail/brake/reverse/fog (front+rear)/license/interior/trunk lamp tests, wipers low/high + rear, washer pumps (front+rear+headlight), HVAC blower step + compressor + recirc/face/floor/defroster flaps + rear defogger + PTC step1, EV charge port lock + battery cooling pump + battery heater + DC-DC load + OBC handshake, park-assist sonar sweep, tow-hitch deploy/stow. +- **56 live PIDs** — 11 J1979 service01 PIDs (load, coolant, MAP, RPM, speed, ignition timing, intake temp, MAF, throttle, control module voltage, ambient temp) + 45 mode 0x22 streams across engine, transmission, ABS, steering, EPS, cluster, climate, EV. +- **56 DTC extended-data records** — P0301-P0308 misfire occurrence + miles + aging + freeze-frame, P0420 + P0430 catalyst occurrence + aging, P0299 + P0234 turbo occurrence + freeze-frame, P0171/172/174/175 fuel-trim occurrence, P2002 + P244A/B + P20EE + P204F DPF/SCR records, P0011/0014 VVT occurrence, P052E crankcase, P0700 + P0741 + P17BF/P17C0 transmission, P0AA6 HV isolation occurrence + miles + OEM status, P0A2A/P0A7A/P0AC0 EV motor + battery, P0D29/P0D2A charge coupler, C1135 brake-pedal switch, U0073 CAN bus-off + freeze-frame, U0146 gateway, U0184 IVI, B116F smart-key, B1318 BCM low voltage. + +### Why this matters +- A community contributor or in-house engineer can now extend VW coverage by editing `vw.json`. No Delphi setup, no rebuild. +- Porting the framework to another language only needs a JSON parser plus the same record types — no Pascal-specific data extraction. +- The same template applies to every other OEM: subsequent v3.32+ releases migrate BMW, Mercedes, Porsche, etc. to the same JSON-only pattern. + +### Out of scope for v3.31 (queued) +- Migration of the other 45 OEM extensions to the JSON-only pattern (v3.32+). +- Per-OEM full-depth content expansion for non-VW brands (v3.33+). + +## [3.30.0] - 2026-05-08 — VW Group reference deep-dive (Phase B start) + +This is the first OEM brought to the per-ECU enrichment + +extended-catalog depth that subsequent OEMs will follow as a +template. VW was chosen because the public diagnostic surface +(VCDS / Ross-Tech wiki / OBDeleven) is the best-documented in +the industry — it's where the patterns get validated. + +### Added (`catalogs/vw-extended.json` — new file, ~700 lines) +- **+85 DIDs** beyond `vw.json`'s 41, bringing VW to **126 DIDs total** across **19 ECUs**: + - Engine (0x7E0): per-bank lambda (b1s1 / b1s2 / b2s1 / b2s2), short + long fuel trim per bank, ignition advance, per-cylinder knock retard + misfire counters, MAP / MAF / pedal / target throttle, turbo target boost + wastegate position, EGR target + cooler temp, full DPF chain (regen-active flag, inlet temp, outlet temp, diff pressure, distance loaded), AdBlue dosing + SCR inlet temp + outlet NOx + remaining range + - Transmission (0x7E1): DSG K1 + K2 clutch pressures, target gear, input + output shaft speeds, torque request to engine, oil quality model %, lifetime shift count + - ABS / ESP (0x710): four wheel speeds, lateral g, master brake-cylinder pressure, brake-pad remaining (front + rear), TCS + ESP intervention counters + - EPS (0x718): torque request + motor current + - Comfort / BCM (0x746): door-open bitmask, lifetime lock count, per-window cycle counts (FL / FR), low-beam + high-beam hours-on + - KESSY (0x748): learned-key slot count + last-presented key ID + - Cluster (0x714): Trip A + B + long-term distance, average + recent consumption, distance + days to next service, distance to next oil change + - Climate (0x740): cabin temperature, left + right zone targets, blower speed, A/C compressor active + - EV stack (0x7E5 / 0x7E6 / 0x7E7): pack voltage / SOC / SOH, motor temp, charge status enum, charge power (up to 200 kW DC on ID.x), remaining range +- **+12 routines**: throttle body alignment, idle relearn, camshaft adaptation, DPF ash reset, 12V battery registration, DSG basic setting, brake-pad reset (front + rear), TPMS relearn, EPS calibration, park-assist calibration, Travel Assist camera + radar calibration +- **+11 ECUs** registered: Haldex, airbag, EPS, BCM (J393), KESSY, IVI (MIB3 / MIB4), front camera + radar (Travel Assist), TPMS (J502), Park Assist (J791), and the EV evcc / motor / battery trio + +### Added (Schema v2 sections in `vw-extended.json`) +- **6 coding blocks** with **27 fields total**: `vag_bcm_long_coding` (16-byte payload — comfort unlock, auto-lock speed, comfort window close, DRL + DRL-via-high-beam, rear fog, headlight country pattern, coming-home delay), `vag_cluster_long_coding` (needle sweep, language, imperial units, shift indicator, speed warning), `vag_engine_long_coding` (adaptive cruise, start-stop, dual-mass-flywheel, exhaust flap), `vag_climate_long_coding`, `vag_abs_coding` (ESP sport mode, off threshold, brake-disc dry-wipe, trailer mode), `vag_kessy_coding` +- **15 adaptation channels**: idle RPM target, throttle stop, service-interval distance + days, oil quality remaining, trip-A reset threshold, DRL operating mode, auto-lock speed, comfort window close, audible speed warning, Haldex default torque split, TPMS warning threshold, park-assist warning distance, EV charge target SOC, EV AC charge current limit +- **12 actuator tests**: cooling fan low + high speed, fuel pump prime, EGR valve step 0-100%, glow-plug heating, ABS pump bleed, window motor (FL up + down), central lock cycle, horn beep, HVAC blower step 0-7, tail lamp test +- **15 live PIDs**: 14 mode 0x22 streams (MAP, fuel rail pressure, b1s1 lambda, ignition advance, engine load, MAF, DPF diff pressure, FL wheel speed, master brake pressure, EPS torque, cabin temp, EV pack voltage + SOC, EV charge power) + 1 J1979 service01 PID 0x0C (RPM) +- **11 DTC extended-data records** across P0301-P0304 misfires (occurrence counters), P0420 catalyst (occurrence + aging), P0299 turbo underboost (occurrence + freeze-frame template), P0AA6 HV battery isolation (occurrence + OEM status byte), plus a P0301 `miles_since_cleared` record + +### Added (Pascal) +- `OBD.OEM.VW` overrides the new `BuildExtendedCatalog` hook from v3.29 and calls `MergeExtendedCatalogJSON('vw-extended.json', ...)`. The flat-section additions (DIDs, routines, ECUs) merge through a second `MergeCatalogJSON('vw-extended.json', ...)` call in `BuildCatalog`. +- `Tests.OEM.VW.Deep` — 17 cases asserting per-ECU DID coverage (lambda per bank, misfire counters, DSG clutch pressures, four wheel speeds, cluster trip + service counters, EV stack), new ECU registration, schema v2 sections (coding blocks with the BCM DRL field, adaptation bounds, actuator-test safety warning, live PID modes, DTC extended-data kinds), and that the extension implements `IOBDOEMExtensionV2`. + +### Why this matters +- VW is now at production-tool depth for live data, configuration, calibration, and DTC analysis. A coding tool can read `vag_bcm_long_coding`, render it as a form, capture user edits, and write the modified payload back. A diagnostic tool can drive the cooling fan, prime the fuel pump, or step the EGR valve through `actuator_tests[]`. A live-data dashboard can stream all the per-cylinder telemetry. Everything else in the framework is built on the same schema, so the next release can apply the template to BMW / Mercedes / Porsche / etc. without redesign. + +### Out of scope for v3.30 (queued for v3.31+) +- Subsequent OEM deep-dives (BMW, Mercedes, Porsche, JLR, MINI, Bentley, Rolls-Royce, Volvo, Polestar, Renault, Stellantis, Ferrari, McLaren, Aston, Dacia, Lada, then Asian + American + Chinese + HD). + +## [3.29.0] - 2026-05-08 — Schema v2: extended catalog (Phase A) + +### Added (additive schema — every existing v3.28 catalog continues to parse) +- **Coding blocks** (`coding_blocks[]`) — writeable DIDs with bit-field structure. Each block carries a `payload_size` and a `fields[]` list of `bit` / `uint8` / `uint16_be` / `uint32_be` / `int16_be` / `int32_be` / `ascii` / `enum` / `bitmask` fields with `byte_offset`, `bit_offset`, `bit_width`, `default`, `min`, `max`, and per-enum `values` maps. UI tools render this as a coding form (checkboxes for bits, combos for enums, spinners for numerics). +- **Adaptations** (`adaptations[]`) — numbered adaptation channels (VAG-style). Read with SID 0x22, write with SID 0x2E. Each entry carries `min` / `max` / `default` / `unit` plus optional `enum` `values` map for clamped + factory-reset support. +- **Actuator tests** (`actuator_tests[]`) — forced-output catalog (cycle the cooling fan, fire injector N, EVAP solenoid, ABS pump bleed, etc.). Each entry carries `id` (RoutineControl RID), `duration_ms`, `safety_warning` (surfaced in the UI before firing), and `response_kind` / `response_label`. +- **Live PIDs** (`live_pids[]`) — streamable signals with framing layout. `mode` is `service01` (J1979) or `service22` (16-bit OEM PIDs). Each entry carries `frame_offset` (byte offset into response payload) plus `decoder` (kind / scale / offset / unit). +- **DTC extended-data records** (`dtc_extended_data[]`) — per-DTC record templates for UDS 0x19 0x06. Kinds: `occurrence_counter`, `aging_counter`, `miles_since_cleared`, `freeze_frame_template`, `oem_status_byte`, `environmental_data`. + +### Added (Pascal API) +- New record types in `OBD.OEM.pas`: `TOBDCodingField` / `TOBDOEMCodingBlock` / `TOBDOEMAdaptation` / `TOBDOEMActuatorTest` / `TOBDOEMLivePID` / `TOBDDtcExtendedDataRecord`, plus shared `TOBDOEMDecoderKind` / `TOBDCodingFieldKind` / `TOBDAdaptationKind` / `TOBDActuatorResponseKind` / `TOBDLivePIDMode` / `TOBDDtcExtendedDataKind` enums. +- New companion interface `IOBDOEMExtensionV2` (separate GUID — keeps `IOBDOEMExtension` binary-compatible). Adds `CodingBlocks`, `Adaptations`, `ActuatorTests`, `LivePIDs`, `DtcExtendedDataRecords` accessors. Implemented by `TOBDOEMExtensionBase` so every existing extension automatically supports it. +- New override-point `TOBDOEMExtensionBase.BuildExtendedCatalog`. Default is a no-op so the 46 v3.28 OEM extensions continue to compile + work unchanged. +- New loader helper `MergeExtendedCatalogJSON` in `OBD.OEM.Catalog.Loader`. Same merge semantics as `MergeCatalogJSON`: by-key replacement (DID, channel, identifier+ecu, mode+pid+ecu, code+record). +- `OBD.OEM.Catalog.JSON` extended to parse the five new sections, with `ParseOEMDecoderKind` / `ParseCodingFieldKind` / `ParseAdaptationKind` / `ParseActuatorResponseKind` / `ParseLivePIDMode` / `ParseDtcExtendedKind` helpers. +- Test fixture `catalogs/test-schema-v2.json` exercising every new section. +- `Tests.OEM.SchemaV2` — 22 cases across parser, kind-string mapping, merge semantics, and a regression assertion that every v1 catalog still parses under the v2 loader. + +### Changed +- `docs/CATALOG_FORMAT.md` adds the **Schema v2** section with examples for each new array and a Pascal opt-in snippet. + +### Why this matters +- Schema v2 is the prerequisite for the rest of the per-OEM diagnostic-depth roadmap (Phase B per-ECU enrichment, Phase C coding tables, Phase D actuator + adaptation catalogs, Phase E live PID expansion, Phase F DTC depth). Shipping the schema first means subsequent phases drop content into pre-validated structures rather than redesigning the data model mid-flight. Backwards compatibility is total — no v3.28 catalog or extension needs editing. + +## [3.28.0] - 2026-05-08 — Unified coding / WriteDataByIdentifier API + +### Added +- **`OBD.OEM.Coding.Common`** — canonical `TOBDCodingFunctionKind` enum (19 coding kinds: vehicle order / FA / commission, As-Built code, FCA wiTech proxi, market region, Rolls-Royce Starlight, daytime running lights, auto-lock/unlock, rear fog lamp, needle sweep, ACC enable, lane-assist enable, TPMS threshold, headlight country, trailer mode, language, units imperial, TPMS calibration, comfort window, soft-top auto). The parallel of v3.25 `ServiceFunction` but for `WriteDataByIdentifier` (SID 0x2E) flows. +- `FindCodingFunction(Ext, Kind, out Func)` — first-match lookup of a writeable DID across any OEM extension's catalog. +- `ListCodingFunctions(Ext)` — enumerate every classifiable coding-write DID, ready for a "Coding" menu in a tool. +- `BuildWriteDataByIdentifier(DID, Data)` — wraps a payload with `2E DID-hi DID-lo …`. +- `BuildCodingFrame(Func, Data)` — same, against a resolved coding function. +- `ParseCodingResponse(Response, DID)` — checks the positive response (`6E DID-hi DID-lo`) and confirms the DID matches. +- `CodingFunctionKindName(Kind)` — display labels for UI binding. +- `Tests.OEM.CodingCommon` — 19 cases across registry classification, lookup against shipped Rolls-Royce + Mazda catalogs, frame builder, response parser, display labels. + +### Why this matters +- A coding tool no longer needs hard-coded dispatchers for "BMW writes FA, Bentley writes commission, Mazda writes as-built, FCA writes proxi". `FindCodingFunction(Ext, cfVehicleOrder)` works across every OEM that ships an FA-equivalent block, and `ListCodingFunctions` populates the tool's coding menu without OEM-by-OEM enumeration. + +## [3.27.0] - 2026-05-08 — Existing-OEM catalog deepening + +### Changed (16 catalogs deepened to baseline) +- **`byd.json`** — 6 → 29 DIDs (+ 6 routines). Adds Yangwang quad-motor + DiSus suspension + tri-motor stack + DiPilot lidar + DiLink IVI + brand code (BYD / Denza / Yangwang / FangChengBao) + drivetrain enum (DM-i / DM-p / EV / DM-o) + 8-in-1 thermal-mgmt controller + four-corner DiSus heights + Tank-Turn drive mode. +- **`tesla.json`** — 6 → 29 DIDs (+ 7 routines). Adds tri-motor Plaid (rear-2 inverter at 0x7E3) + Cybertruck four-wheel-steering + air-suspension controller + Octovalve thermal + FSD camera array + drive-mode enum (Chill/Standard/Sport/Plaid/Track) + four air-suspension heights + Supercharger station ID + V3/V4 charge power + 16 V LV battery + 4680 chemistry tag. +- **`honda.json`** — 9 → 26 DIDs (+ 10 routines). Adds Honda Sensing camera/radar ECU + i-MMD operating-mode enum (EV/Series/Engine drive) + IMA hybrid SOC + temp + Honda e / Prologue HV stack (35 / 85 kWh) + chassis code, engine code (L15B7 / K20C1 Type R / J35Y8) + brake-pad remaining + 10 routines including Honda Sensing calibration + i-MMD battery test + TPMS relearn. +- **`mazda.json`** — 8 → 27 DIDs (+ 8 routines). Adds i-Activ AWD coupling + M Hybrid 24V/48V mild-hybrid + CX-60/90 PHEV + MX-30 EV + e-SkyActiv R-EV separate ECUs + chassis code (KE/KF/KK/MJ) + DPF soot load + boost + 8 routines including DPF force regen + battery registration + TPMS relearn. +- **`subaru.json`** — 8 → 28 DIDs (+ 8 routines). Adds Solterra dual-motor + 71.4 kWh HV pack + AC/DC charge controller + Starlink IVI + EyeSight stereo camera + e-Boxer mild-hybrid + chassis code (GP/SK/VB) + WRX engine code + X-MODE active flag + market code + trim level + 8 routines including EyeSight calibration + battery register + TPMS relearn. +- **`mitsubishi.json`** — 5 → 25 DIDs (+ 7 routines). Adds Outlander PHEV Twin-Motor (front + rear inverters) + 13.8 / 20 kWh PHEV pack + CHAdeMO + V2H/V2G charge enum + S-AWC torque split + drive-mode enum + AdBlue level + DPF soot load + Triton/L200 diesel DPF controller + MI-PILOT ADAS + 4N16 / 4B12 engine codes + PHEV operating-mode (EV/Series/Parallel) + 7 routines. +- **`geely.json`** — 6 → 28 DIDs (+ 6 routines). Adds dual-motor stack (front + rear inverters) + Aegis short-blade LFP pack + DHT-Pro hybrid 3-speed + Galaxy OS / Flyme Auto / LYNK OS IVI + Mobileye-derived Pilot Assist + brand code + model code + drive-mode enum (Eco/Comfort/Sport/Snow/Off-road) + four-corner motor data + charge port stack + 6 routines. +- **`nio.json`** — 5 → 30 DIDs (+ 7 routines). Adds Aquila ADAS suite (33 sensors / 4 lidar) + Adam 4×Orin-X compute + Banyan IVI / NOMI + active air-suspension + ET9 X-By-Wire rear-axle steering + swap count + pack capacity + 800 V architecture + Power Up to 500 kW liquid-cooled charge + 4-lidar status enum + 7 routines including X-By-Wire rear-steer calibration + Aquila ADAS calibration. +- **`xpeng.json`** — 5 → 29 DIDs (+ 7 routines). Adds X-Power AWD front motor + silicon-carbide rear inverter + Livox Tele-15 / Hesai lidar + Xmart OS 8155 / 8295 cabin computer + active air-suspension (G9 / X9) + X9 rear-wheel steering + S4 800V supercharger (480 kW) + drivetrain enum (RWD / X-Power AWD) + pack chemistry (NCM / LFP / short-blade) + 7 routines including XPILOT / XNGP calibration. +- **`gwm.json`** — 5 → 27 DIDs (+ 7 routines). Adds Hi4 / Hi4-T hybrid controller + dual-motor inverters + Honeycomb LFP / SVOLT pack + Tank crawl-mode controller + Coffee Pilot ADAS + tank drive-mode enum (Normal/Eco/Sport/Sand/Mud/Snow/Mountain/Crawl/Tank-Turn) + diff-lock state enum (Off/Center/Rear/Front+Rear) + low-range bool + brand code (HAVAL/WEY/ORA/TANK/POER) + 9HAT/9DCT TCU + 7 routines. +- **`cummins.json`** — 6 → 26 DIDs (+ 5 routines). Adds DEF doser module + hydrocarbon doser + combustion-diagnostic ECU + emissions family + displacement + J1939 SPN-mapped coolant / oil temp + oil pressure + boost + rail pressure + intake-air temp + fuel temperature + EGR valve position + DPF/SCR full chain (inlet temp / diff pressure / SCR inlet / NOx / DEF dosing / consumption) + DPF ash reset + EGR calibration + cylinder-balance test routines. +- **`detroit.json`** — 5 → 27 DIDs (+ 5 routines). Adds CPC (Common Powertrain Controller) + DEF doser + DD13/DD15/DD16/DD8 engine model code + DT12 clutch position + oil temp + current gear + full DPF/SCR chain (ash load / inlet temp / diff pressure / SCR inlet / NOx) + DT12 clutch calibration + EGR calibration + DEF quality test routines. +- **`scania.json`** — 5 → 27 DIDs (+ 5 routines). Adds Tachograph (TCO) + Visibility (VIS) + Lane Warning System (LWS) + Cab Climate (CCS) + BCS Body & Chassis + Scania BEV stack (motor + 624 kWh battery) + chassis type (R/S/G/P/L/XT) + Opticruise current gear + oil temp + EBS brake-pad remaining + retarder active % + DPF/SCR full chain + 5 routines including Opticruise calibration + brake-bleed. +- **`man.json`** — 5 → 26 DIDs (+ 5 routines). Adds Instrument Cluster (IC) + Lane Guard System (LGS) + Trailer Coupling Control (TTC) + MAN eTruck stack (motor + 480 kWh battery) + engine model (D08 / D26 / D38) + TipMatic / TraXon current gear + oil temp + DPF/SCR full chain + EBS brake-pad remaining + 5 routines including TipMatic calibration + brake-bleed. +- **`paccar.json`** — 5 → 25 DIDs (+ 6 routines). Adds aftertreatment ATD2 (SCR) + Bendix Wingman Fusion radar + Kenworth severe-duty hydraulic options + brand code (Peterbilt / Kenworth / DAF / Leyland) + chassis code expanded (579/567/T880/W990/XF/XG/XG+/Anthem) + factory code expanded (Denton/Chillicothe/Madison/Eindhoven/Leyland) + engine model (MX-11 / MX-13 / Cummins X15) + DPF/SCR full chain + transmission gear + oil temp + EBS brake-pad + Wingman radar status + 6 routines. +- **`volvotrucks.json`** — 5 → 28 DIDs (+ 6 routines). Adds Tachograph (DTCO 4.0) + VADS Active Driver Support + Lane Keeping Support (LKS) + Volvo FE/FH Electric stack (motor + 180/540 kWh battery) + brand code (Volvo/Mack/Renault Trucks) + engine model (D11/D13/D16/MP7/MP8) + I-Shift / mDRIVE current gear + oil temp + EBS brake-pad + VEB+ engine-brake active % + DPF/SCR full chain + remaining range + 6 routines including VADS calibration. + +### Changed (WMI hygiene, continued from v3.26) +- `paccar.json` no longer lists `SCB` in `applicable_wmis` (Bentley territory; PACCAR Leyland Trucks is `SAR`). + +### Total +- **DIDs added: ~330 across 16 OEMs** (was 110, now ~440). +- **Routines added: ~75 across 16 OEMs** (was 36, now ~111). +- All 16 catalogs now meet the established baseline (~25-30 DIDs / 5-10 routines per OEM). + +## [3.26.0] - 2026-05-08 — Six more OEMs (ultra-luxury British + Russian + Eastern-European) + +### Added (6 new full-depth OEM extensions) +- **`OBD.OEM.AstonMartin`** — Aston Martin Lagonda (1 WMI: SCF Gaydon + St Athan). 16-ECU map covering DB12 / Vantage / DBX / DBX 707 / Vanquish + Valhalla PHEV (charge controller + front-axle e-motor + 6.6 kWh PHEV pack at 0x7E5/0x7E6/0x7E7). 25 DIDs including Q by Aston Martin paint + trim codes, manettino-equivalent damper / drive modes, eDiff lock, four-corner air-suspension on DBX, oil pressure / level / runtime, Valhalla PHEV pack voltage / SOC / motor temp. 5 routines. +- **`OBD.OEM.Bentley`** — Bentley Motors (1 WMI: SCB Crewe). 16-ECU map covering Continental GT / GTC / Flying Spur / Bentayga + V8 PHEV variants (14.1 / 25.9 kWh). 27 DIDs including Bentley Mulliner paint code, commission number, drive mode, air-suspension mode, Dynamic Ride 48 V active-anti-roll status, Flying Spur Mulliner rear-wheel steering angle, four air-suspension heights, PHEV stack. 7 routines including Dynamic Ride calibration + rear-wheel steering calibration. +- **`OBD.OEM.RollsRoyce`** — Rolls-Royce Motor Cars (1 WMI: SCA Goodwood). BMW Group sub-brand inheriting BMW E-Sys / ISTA — 17-ECU map covering Phantom (RR1) / Ghost (RR21) / Cullinan (RR31) + Spectre EV (RR23) at 0x7E5/0x7E6/0x7E7 with 102 kWh Gen5 BMW eDrive pack. 28 DIDs including factory + current I-Stufe, FA SALAPA option codes, RR model code, Bespoke programme commission number, Starlight Headliner constellation pattern, Magic Carpet Ride active flag, four air heights, rear-wheel steering, Spirit OS version, Spectre pack voltage / SOC / SOH / front + rear motor temps / charge status / range. 7 routines including Bespoke Starlight constellation programming. +- **`OBD.OEM.McLaren`** — McLaren Automotive (1 WMI: SBM Woking MPC). 17-ECU map covering 720S / 750S / 765LT / GT + Artura V6 PHEV (front-axle e-motor + 7.4 kWh PHEV pack). 27 DIDs including MSO paint code, MonoCell carbon-tub serial, dual-bank turbo temperatures, PCCM handling + powertrain modes, active rear-wing position enum, Vehicle-Lift status, DCT clutch A/B temperatures, brake pad remaining, Artura PHEV stack. 7 routines including 7-DCT (SSG) calibration + active-aero calibration + lift-axle test. +- **`OBD.OEM.Lada`** — AvtoVAZ / Lada (3 WMIs: XTA Tolyatti + XTC Izhevsk + XTV Bronto). 13-ECU map covering Granta / Vesta / Niva Legend / Niva Travel / Largus with VAZ-21127 / 21179 / 21214 engines + JATCO JF015E CVT / 5AMT / 4AT. 26 DIDs including model code, engine code, transmission code, APS immobilizer state enum, EPS torque + motor current, intake MAF / temperature, throttle / pedal position, manifold pressure, Niva transfer-case mode (2H/4H/4L/N), CVT oil temperature + ratio, AMT clutch position. 6 routines including APS immobilizer key learning + 5AMT clutch calibration. +- **`OBD.OEM.Dacia`** — Automobile Dacia / Renault Group budget brand (4 WMIs: UU1 + UU3 Mioveni + LBR + LRY Dongfeng-Renault Wuhan). 16-ECU map covering Sandero / Logan / Duster / Jogger / Bigster + ECO-G LPG bi-fuel + Spring EV (26.8 kWh) + Bigster Hybrid 140. 27 DIDs including Renault Group part number, model code, engine code (TCe / ECO-G / Hybrid 140 / 5AQ), assembly plant (Mioveni / Wuhan / Tangier), LPG tank level + active flag, Spring EV pack voltage / SOC / SOH / motor temp / charge status / range, Duster 4x4 mode. 6 routines. + +### Added (DTC starters — full depth, 148 entries combined) +- `dtc-aston-martin.json`: 22 codes (8-cylinder misfires P0301-P0308 for V8 / V12, M177 turbo over/underboost, oil pressure, catalysts, Valhalla HV isolation, DBX air-suspension, Bilstein DTX damper, comm-loss). +- `dtc-bentley.json`: 24 codes (V8 + W12 misfires, turbo, oil, catalysts, hybrid HV isolation + battery deterioration + AC charge coupler, air-suspension, Dynamic Ride 48 V, rear-wheel steering, KESSY). +- `dtc-rolls-royce.json`: 25 codes (V12 + V8 misfires, turbo, oil, catalysts, Vanos, Spectre HV stack, AC + DC charge coupler, Magic Carpet air-suspension, rear-wheel steering, Spirit OS comm-loss, Starlight LED). +- `dtc-mclaren.json`: 24 codes (V8 cylinder misfires P0301-P0308, twin-turbo, dry-sump oil pressure, catalysts, Artura HV stack + AC charge coupler, 7-DCT TCC performance, Vehicle-Lift sensor, active rear-wing sensor, PCCM comm-loss). +- `dtc-lada.json`: 26 codes (MAF / coolant / TPS / O2 sensor circuits, lean / rich, 4-cylinder misfires, CKP / CMP, catalyst, EVAP, fuel pump, clutch switch, CKP self-learn, APS immobilizer auth, ABS, comm-loss, BCM low voltage). +- `dtc-dacia.json`: 27 codes (MAF / coolant circuits, lean / rich, TCe turbo, 4-cylinder misfires, catalyst, EVAP, ECO-G LPG injector + pressure, Spring EV HV stack + charge coupler, JF016E CVT, brake-pedal switch, Duster AWD coupling, comm-loss, UCH low voltage). + +### Changed (WMI hygiene — collision fixes) +- `OBD.OEM.PACCAR` no longer claims `SCB` (real-world WMI for PACCAR Leyland Trucks is `SAR`; `SCB` is exclusively Bentley). Regression test guards both directions. +- `OBD.OEM.Renault` no longer claims `UU1` / `UU3` / `UU6` — Dacia is delegated to its own extension. Regression test guards both directions. + +### Total +- **OEMs: 40 → 46** (six new full-depth extensions). +- **DTC entries: 148 new starter codes** across the v3.26 OEMs. + +## [3.25.0] - 2026-05-08 — Unified service-function API + +### Added +- **`OBD.OEM.ServiceFunction`** — canonical `TOBDServiceFunctionKind` enum (19 functions: oil-life reset, EPB service, SAS calibration, battery registration, DPF regen, TPMS relearn, throttle / idle / transmission / crank / immo / fuel-trim relearn, brake bleed, air-suspension calibration, hybrid battery test, Haldex calibration, basic setting, clear adaptations, DEF quality test) plus a name-token registry that maps the per-OEM routine names (`ferrari_oil_life_reset`, `mb_oil_maintenance_reset`, `reset_service_indicator`, ...) to the canonical kind via case-insensitive substring matching. +- `FindServiceFunction(Ext, Kind, out Func)` — first-match lookup against any OEM extension's routine catalog. Tools can now write *one* call to issue, e.g., an oil-life reset and have it work across every OEM that ships the routine. +- `ListServiceFunctions(Ext)` — enumerate every classifiable routine on an OEM extension, ready for a "Service" menu in a diagnostic tool. Skips routines that don't classify (returns no `sfUnknown` entries). +- `BuildServiceFunctionFrame(Func, Input)` — wraps the resolved RID with the StartRoutine SID + sub-function (`31 01 RID ...`). +- `ServiceFunctionKindName(Kind)` — display labels for UI binding ("Oil Life Reset", "EPB Service Mode", "Steering-Angle Sensor Calibration", ...). +- `Tests.OEM.ServiceFunction` — 25 cases across registry classification, lookup against shipped Ferrari / Mahindra / Tata / MINI catalogs, enumeration, frame builder, and display labels. + +### Why this matters +- A diagnostic tool no longer has to hard-code which OEM names its oil-life reset `oil_life_reset` vs `oil_maintenance_reset` vs `reset_service_indicator`. The same code works for every OEM that ships an oil-life routine, and `ListServiceFunctions` lets the tool's UI populate the "service" menu without listing OEMs by hand. + +## [3.24.0] - 2026-05-07 — Six more OEMs (Ferrari / Lucid / Mahindra / Tata / MINI / smart) + +### Added (6 new full-depth OEM extensions) +- **`OBD.OEM.Ferrari`** — Ferrari N.V. (1 WMI: ZFF Maranello). 16-ECU SD3 / Leonardo map covering ME engine + Marelli ECU + 7/8-DCT + secondary V8/V12 controller + SF90 / 296 / 12Cilindri hybrid stack (inverter + e-motor + HV battery) + manettino + Magneride + lift axle + PCCB-equivalent. **24 DIDs** including Ferrari model code (F142, F154, F160), paint code, individual options, Maranello assembly data, warranty block, oil pressure / level / temperature / runtime, rear-axle temp, hybrid pack voltage / SOC / SOH, manettino position enum (Wet / Sport / Race / CT-off / ESC-off / Qualify), Magneride mode enum, lift-axle status enum, four tire-surface temperatures. **6 routines** (DCT calibration, Magneride, lift-axle test, oil-life reset). +- **`OBD.OEM.Lucid`** — Lucid Group (1 WMI: 50A Casa Grande AMP-1). 15-ECU map for the Air sedan + Gravity SUV: VCU + front motor + tri-motor stack (Sapphire) + 900 V BMS + Wunderbox integrated charger + Pixel cluster + DreamDrive ADAS + lidar + Glass Canopy + heat-pump (CO₂) + thermal mgmt + air suspension. **22 DIDs** including model code (Air / Gravity / Sapphire), drivetrain (Pure / Touring / Grand Touring / Sapphire), battery pack (88/92/112/118 kWh), 900 V pack voltage / SOC / SOH / temp min/max, range, consumption, charge status / session kWh / 350 kW power, three motor temperatures, four-corner air-suspension heights, drive mode enum (Smooth / Swift / Sapphire Track / Tow). **5 routines**. +- **`OBD.OEM.Mahindra`** — Mahindra & Mahindra (3 WMIs: MAJ Chakan/Nashik + MA6 Bengaluru + M3M BE EV Pune; deliberately avoids MA1 to prevent JLR-Pune collision). 12-ECU map for engine (mHawk diesel / mStallion petrol) + Aisin AT / Punch CVT + BE EV charge controller + drive motor + AdrenoX IVI + ADAS Level 2 + air suspension (XUV700 AX7L). **23 DIDs** including model code (XUV700, ScorpioN, Thar), variant code (AX5/AX7/AX7L/Z8/Z8L), engine code, oil temperature, coolant temp, boost pressure, common-rail pressure, fuel level, runtime, DPF soot load, BE EV pack voltage / SOC / SOH / motor temp / charge status, AT/CVT temp, two-corner air heights. **6 routines**. +- **`OBD.OEM.Tata`** — Tata Motors (3 WMIs: MAT passenger Pune+Sanand + MAR commercial Jamshedpur+Lucknow + KMU Tata Daewoo Korea; JLR — also Tata-owned — uses its own extension). 12-ECU map for Revotron / Revotorq / Kryotec / TGDI engines + iCNG bi-fuel module + Ziptron / Acti.ev EV stack + iRA Connected Car / Harman IVI + ADAS Level 2 (Harrier / Safari / Curvv). **23 DIDs** including model code (Nexon / Punch / Curvv / Harrier / Safari), variant code (XE/XM/XT/XZ/XZ+), engine code (Revotron 1.2T, Kryotec 2.0L), oil + coolant temperature, boost, common-rail pressure, fuel level, CNG tank pressure, runtime, DPF soot load, Ziptron pack voltage / SOC / SOH / motor temp / charge status / range, brake-pad remaining. **7 routines**. +- **`OBD.OEM.MINI`** — MINI / BMW Group sub-brand (2 WMIs: WMW Oxford UK + SAW Spotlight Automotive China JV). Full BMW E-Sys / ISTA architecture inheritance: 13-ECU map (DME B38/B48/B58 + EGS Aisin/7DCT + DSC + KOMBI + FRM + CAS + ZGW + iDrive + IHKA + ACSM + MINI Cooper E / SE / Aceman EV stack). 23 DIDs including factory + current I-Stufe, FA SALAPA option codes, MINI chassis code (R56, F56, F60, J01, J05), oil temperature / level / runtime, boost pressure, fuel level + consumption, MINI Cooper E pack voltage / SOC / SOH / range / motor temp / charge status, brake-pad remaining, oil quality, remaining oil-service distance. **6 routines**. Inherits the BMW session negotiator (security access required for both extended + programming sessions; 1500 ms heartbeat). +- **`OBD.OEM.Smart`** — smart Automobile Co. / Mercedes-Geely 50/50 JV (2 WMIs: WME Hambach + L7M Xi'an China). 14-ECU map covering both legacy two-seater (451 / 453) and current Geely SEA platform (#1 / #3 / #5 SUV): VCU + front + rear motor inverters + 66/100 kWh BMS + on-board charger + cluster + HUD (#5 Premium) + Pilot Assist (Mobileye) + air suspension (#5). **20 DIDs** including model code, drivetrain (RWD/AWD/Brabus), battery pack (66 kWh BYD-LFP / 100 kWh CATL-NMC), software release, mileage, ambient temp, pack voltage / SOC / SOH / temp min/max, range, consumption, charge status / session kWh, motor temps, brake-pad remaining. **5 routines**. + +### Added (DTC starters — full depth, 144 entries combined) +- `dtc-ferrari.json`: 18 codes (cylinder misfires P0301-P0308, V8 turbo / V12 NA oil pressure + boost, hybrid system on SF90 / 296 / 12Cilindri, lift-axle, Magneride, CAN-FD). +- `dtc-lucid.json`: 31 codes (HV isolation, motor temp x3, charge coupler / lock, Wunderbox over-temp, BMS / IVI / DreamDrive comm-loss, DreamDrive front camera + lidar, glass canopy, Pixel cluster backlight, tri-motor torque vectoring). +- `dtc-mahindra.json`: 25 codes (mStallion turbo, mHawk diesel rail / EGR / DPF, Aisin AT, BE EV battery, AdrenoX comm-loss, AX7L air suspension). +- `dtc-tata.json`: 26 codes (Revotron T-GDi turbo / catalyst, Kryotec diesel rail / DPF, DCA transmission, iCNG fuel-pressure, Ziptron HV system + comm-loss, Harman iRA comm-loss). +- `dtc-mini.json`: 23 codes (cylinder misfires for B38 3-cyl + B48 4-cyl, VANOS solenoid stuck open/closed, Valvetronic eccentric-shaft sensor, B48 oil pump pattern, MINI Cooper E HV system, RDC tire-pressure, FlexRay bus-off). +- `dtc-smart.json`: 21 codes (HV isolation, AC + DC charge coupler, BMS / Pilot Assist comm-loss, heat-pump compressor, #5 air-suspension reservoir). + +### Tests +- `Tests.OEM.LuxuryAndIndian` — 19 new test cases: VIN routing for all 6 OEMs (Ferrari ZFF + Fiat ZFA disambiguation, Lucid Casa Grande, Mahindra all 3 plants, Tata MAT/MAR/KMU including Tata Daewoo, MINI WMW + SAW, smart WME + L7M), Mahindra-vs-JLR-Pune collision guard, catalog spot-checks (Ferrari manettino + lift axle, Lucid Wunderbox + DreamDrive, Mahindra BE EV controller, Tata iCNG + Ziptron, MINI security-access requirement, smart Geely SEA architecture), decoder spot-checks for each OEM's distinguishing DID. + +### Changed +- `Packages/RunTime.dpk` adds the 6 new units. The OEM registry now resolves **40 OEMs** total — 29 passenger + 6 heavy-duty + 5 Chinese. +- `examples/diagtool/DiagTool.dpr` self-registers the 6 new extensions. + +### Notes +- Combined v3.24 contribution: **131 new DID + routine entries** + **144 new DTC entries** across 12 catalog files. Catalogs ship at full depth (24-31 entries each), matching v3.22 / v3.18 / v3.7 baseline depth — not the slim starters of v3.14 / v3.17. +- All 12 new catalog files validated to parse cleanly via external `json.load`. + +## [3.23.0] - 2026-05-07 — OBD-II application helpers (readiness + freeze-frame + vehicle health) + +### Added +- **`OBD.ReadinessMonitor`** — decoder for SAE J1979 PID 0x01 (Monitor Status Since Codes Cleared). Returns a `TOBDReadinessReport` with MIL state, DTC count, and per-monitor readiness state for **17 monitor kinds** covering both spark-ignition (catalyst, heated catalyst, EVAP, secondary air, A/C refrigerant, oxygen sensor, oxygen sensor heater, EGR) and compression-ignition (NMHC catalyst, NOx aftertreatment, boost pressure, exhaust gas sensor, PM filter, EGR/VVT diesel) layouts plus the three universal continuous monitors (misfire, fuel system, components). `FormatReadinessSummary` produces a one-line status-bar string like `"MIL off, 0 DTCs, 5/8 readiness monitors complete (spark-ignition)"`. +- **`OBD.FreezeFrame`** — Service 02 wire helpers. `BuildFreezeFrameRequest(PID, FrameNum)` builds the `02 PID FrameNum` request; `ParseFreezeFrameResponse(bytes, expectedPID)` parses the `42 PID FrameNum DATA…` reply (with negative-NRC / wrong-SID / wrong-PID error paths) into a `TOBDFreezeFrameEntry`. `FormatFreezeFrameTriggerDTC` decodes the 2-byte payload of PID 0x02 (the DTC that triggered the freeze frame) into the canonical 5-character form, reusing the v3.7 ISO 15031-5 encoder. +- **`OBD.VehicleHealth`** — high-level `TOBDHealthCapture.Capture` orchestrator that aggregates everything an app actually wants in one call: + - VIN read (Service 09 PID 02) → auto-resolve OEM extension via `TOBDOEMRegistry.FindByVIN`. + - Active DTCs (Service 03), each annotated with the OEM catalog's description + severity. + - Pending DTCs (Service 07), same annotation pipeline. + - Readiness monitors (PID 0x01) decoded through `OBD.ReadinessMonitor`. + - Live values: battery voltage (PID 0x42), engine RPM (PID 0x0C), vehicle speed (PID 0x0D), coolant temperature (PID 0x05), engine load (PID 0x04). + - **Computed health score 0..100** with a documented penalty rubric (MIL on -10, critical DTC -20, warning DTC -8, info DTC -3, unknown DTC -10, pending DTC -2, each not-ready monitor -1; clamped to 0). + - One-line summary string suitable for a status bar. +- Each step is **best-effort** — a failed read populates the matching `*Error` field but doesn't abort the rest, so tools surface the partial result as "we got X but Y failed". This is exactly the contract a real diagnostic tool's "snapshot" button needs. +- `Tests.OBD.Helpers` — 17 new test cases. ReadinessMonitor (10): all-zeros baseline, MIL+DTC count, continuous monitor ready / not-ready, gasoline non-continuous catalyst, diesel-flag-and-monitors set, too-short rejection, summary-string format, monitor-kind / state name canonicalization. FreezeFrame (7): request encoding, positive-response parsing, too-short / wrong-SID / wrong-PID rejection, negative-NRC handling, trigger-DTC round-trip. + +### Changed +- `Packages/RunTime.dpk` adds the three new units. +- `tests/Tests.dpr` registers `Tests.OBD.Helpers`. + +### Notes +- This is the **application-enabling** milestone. Tools built on the framework can now call: + ```pascal + Capture := TOBDHealthCapture.Create(Async); + Snap := Capture.Capture; + StatusBar.SimpleText := Snap.SummaryLine; + // Snap.HealthScore drives the colour-coded indicator + // Snap.ActiveDTCs feeds the DTC list view + // Snap.Readiness powers the readiness-monitor grid + // Snap.BatteryVoltage / EngineRPM / etc. feed the live gauges + ``` +- The reference VCL tool (`examples/diagtool`) shipped in v3.20 already exposes the lower-level primitives (Service 03 read, PID 0x05 / 0x0C / 0x0D / 0x42 polling, DescribeDTC); a future milestone (v3.24+) will add a "Snapshot" tab that calls the v3.23 `TOBDHealthCapture` directly. +- `TOBDHealthCapture` is intentionally stateless — each `Capture` call re-reads everything. Production tools that want a live dashboard should run a polling loop on a worker thread and use the framework's existing async primitives. + +## [3.22.0] - 2026-05-07 — Premium / EV / heavy-commercial OEMs + +### Added (6 new OEM extensions) +- **`OBD.OEM.Porsche`** — Porsche AG (2 WMIs: WP0 + WP1, Stuttgart Zuffenhausen + Leipzig). Separate from VW Group because PIWIS is its own toolchain. **16-ECU map** covering DME engine + PDK transmission + PASM active suspension + PDCC active anti-roll + Taycan electric front/rear inverters + HV battery + PCM + climate + SRS + PCCB ceramic-brake + ESP + LWL fiber-bus + KESSY. **27 DIDs** including model code, paint code, interior code, M-Nummern options, factory commission, PCM PNO block, oil pressure / level / temperature / runtime, charge-air boost, Taycan pack voltage / SOC / SOH / range / charge status, PDK clutch wear, PASM ride heights, PCCB disc temperatures. **9 routines** (PDK calibration, PASM, PDCC, SAS, TPMS, KESSY relearn, battery register). +- **`OBD.OEM.JLR`** — Jaguar Land Rover (4 WMIs: SAJ Castle Bromwich + SAL Solihull + SAD Halewood + MA1 Pune India). **17-ECU map** including PCM + TCM + RDM rear drive (RR BEV) + EV charge controller + EV motor + IPC + HUD + CJB + RJB + ABS/DSC + ASM active suspension + SRS + TCB telematics + ATC climate + Pivi Pro IVI + ADAS + smart key. **23 DIDs** including model code, assembly plant, calibration ID, Topix release, factory options, vehicle mileage, oil temperature / life, runtime, boost pressure, fuel level, ambient temp, I-Pace HV pack voltage / SOC / SOH / range / charge status, four-corner air-suspension heights, Terrain Response selected mode enum. **9 routines** (oil-life reset, SAS, air-suspension calibration, battery registration, DPF regen, smart-key relearn, brake bleed). +- **`OBD.OEM.Iveco`** — Iveco S.p.A. (2 WMIs: ZCF Italy + VCF Spain). **14-ECU map** with FPT Cursor / NEF / S-FE engine + EuroTronic / HI-TRONIX AMT + power steering + Knorr-Bremse EBS + Intarder retarder + DID + body computer + VCM + BCM + ACM aftertreatment + TPMS + forward radar + eDaily EV charge + drive motor. **20 DIDs** with model code, emissions package, engine serial, chassis serial, options, mileage, engine hours, oil pressure / temperature, fuel rate / lifetime, boost, coolant temp, DEF tank level / quality, DPF soot load / temperatures / distance-since-regen, eDaily HV pack voltage / SOC / motor temp. **7 routines**. +- **`OBD.OEM.Isuzu`** — Isuzu Motors (7 WMIs: JAA / JAB / JAL / JAN / JAH Japan, 5RY / 4GD US Charlotte MI). **11-ECU map** for engine ECM (4HK1 / 6HK1 / 6WG1 / RZ4E / 4JJ1) + Aisin / MZW / Smoother AT + power steering + ABS / ESC + Telma retarder + IDD cluster + body computer + ASC stability + cab body + aftertreatment + TPMS. **20 DIDs** with chassis code, engine code, calibration ID, emissions family, mileage, engine hours, oil pressure / temperature, coolant temp, RPM, boost, common-rail pressure, fuel rate / lifetime, DEF tank level, DPF soot load / inlet+outlet temp / distance-since-regen, transmission oil temp + clutch wear, 24 V battery voltage. **7 routines**. +- **`OBD.OEM.Rivian`** — Rivian Automotive (1 WMI: 7PD Normal IL). **16-ECU map** including VCU + four motor inverters (FL / FR / RL / RR for the quad-motor R1) + driver display + Driver+ ADAS + camera fusion + BCM + rear body / Gear Tunnel / Tailgate + heat pump + central gateway + BMS + charge port + thermal management + air-suspension. **22 DIDs** including model code, drivetrain (Quad / Dual / Performance Dual / Tri-motor), battery pack ID, software release, mileage, 12 V battery voltage, HV pack voltage / SOC / SOH / temp min/max, remaining range, recent consumption, charge status, charge-session kWh, four motor temperatures, four-corner air-suspension heights, drive-mode enum (All-Purpose / Conserve / Sport / Off-Road Auto / Off-Road Rally / Off-Road Drift / Off-Road Rock Crawl / Tow). **5 routines**. +- **`OBD.OEM.Polestar`** — Polestar Performance AB / Geely (2 WMIs: LPS Polestar 2 + LFP Polestar 4 — does NOT collide with Volvo Cars). **15-ECU map** with CEM + DIM + HUD + SRS + ABS + PDM + climate / heat pump + Sensus Android Automotive IHU + TCAM telematics + BMS + on-board charger + front + rear motor inverters + Pilot Assist + Luminar lidar (P3 / P4). **23 DIDs** including model code, drivetrain, motor package, software release, options, mileage, ambient temp, range, consumption average, HV pack voltage / SOC / SOH / temp min/max, charge status / session kWh, motor temps, front + rear axle torque request, brake-pad remaining. **6 routines**. + +### Added (DTC starters, 100 entries combined) +- `dtc-porsche.json`: 22 codes (cylinder misfires P0301-P0306, hybrid system on Taycan, ceramic brakes, KESSY, CAN-FD bus). +- `dtc-jlr.json`: 18 codes (Ingenium turbo, AJ-V8 catalysts, ZF8HP TCC, I-Pace HV system, air suspension compressor + leak + reservoir, Pivi Pro touchscreen, telematics). +- `dtc-iveco.json`: 15 codes (Cursor fuel rail, EGR, J1939 SPN-FMI for DPF / SCR / DEF inducement, Daily 3.0 DEF heater). +- `dtc-isuzu.json`: 17 codes (4HK1/6HK1 VGT, RZ4E fuel rail, MZW transmission, J1939 SPN-FMI for DPF / SCR). +- `dtc-rivian.json`: 14 codes (HV isolation, drive-motor temp, DC/DC, VCU CRC, BMS comm, IVI, air suspension, quad-motor torque vectoring). +- `dtc-polestar.json`: 14 codes (HV isolation, motor temp, DC/DC, battery cooling, BMS / IHU / TCAM comm-loss). + +### Tests +- `Tests.OEM.Premium` — 19 new test cases: VIN routing for all 6 OEMs (Porsche WP0/WP1, JLR all 4 plants, Iveco IT/ES, Isuzu JP/US, Rivian Normal IL, Polestar non-Volvo-Cars), Polestar-vs-Volvo-Cars collision guard, catalog spot-checks (Porsche PDK + PASM, JLR air-suspension routine, Iveco FPT engine, Isuzu aftertreatment ECU, Rivian quad-motor count, Polestar EVCC + Pilot Assist), decoder spot-checks for each OEM's distinguishing DID. + +### Changed +- `Packages/RunTime.dpk` adds the 6 new units. The OEM registry now resolves **34 OEMs** total — 23 passenger + 6 heavy-duty + 5 Chinese. +- `examples/diagtool/DiagTool.dpr` self-registers the 6 new extensions in its uses clause so VIN-based routing in the reference VCL tool covers them out of the box. + +### Notes +- Catalogs ship the depth tool-builders actually need: ~30 DIDs and ~6-9 routines per OEM, similar to the established VW (52) / BMW (43) / Ford (35) / Toyota (32) catalogs from prior milestones. Combined v3.22 contribution: **185 new DID + routine entries + 100 new DTC entries** across 12 JSON catalogs. +- Per-OEM entries remain `verified: false` per the v3.3 provenance contract — sourced from the published community references the v3.18 vocabulary documents (piwis-community, sdd-community / topix-public, iveco-easy-community, idss-community, rivian-community, polestar-community). +- All 12 new catalog files validated to parse cleanly via external `json.load`. + +## [3.21.0] - 2026-05-07 — Catalog deepening (round 2) + +### Added (universal catalogs) +- **`catalogs/dtc-iso-15031.json` — +54 verified P/U codes** drawn from SAE J2012 (the master DTC nomenclature). New entries cover: cam-shaft / crank correlation (P0009-P0024 range), turbocharger boost solenoids (P0033-P0245), fuel volume / pressure regulator (P0001/P0002/P0090/P0182), oxygen sensor variants (P0096-P0099 IAT2 sensor), cylinder contribution / balance (P0263 / P0271), single-cylinder misfire (P0314), knock sensor 1+2 (P0325/P0327/P0331), camshaft phasing intermittent (P0344), EGR sensor 'A' low (P0405), warm-up catalyst bank 2 (P0432), EVAP loose-fuel-cap (P0457), fuel level sensor (P0461/P0463), EVAP vent-valve circuit (P0498), oil pressure switch (P0521), system voltage malfunction (P0560), control-module options error (P0610), steering control circuit (P0635), sensor reference voltage 'B' (P0651), ECM/PCM power relay sense (P0688), brake switch 'B' (P0703), transmission range PRNDL (P0705), turbine speed (P0716), gear-1..4 incorrect ratio (P0731-P0734), shift solenoid A/B (P0750/P0755), engine-start request (P082E), park/neutral switch (P0850), drive-cycle monitor not complete (P1000), CAN-A performance (U0028), gateway 'A' lost-comm (U0146), immobilizer lost-comm (U0167). Total **149 verified universal DTC entries** (up from 95). +- **`catalogs/obd2-pids.json` — +5 verified PIDs** in the 0xA7-0xC8 range: NOx sensor corrected (0xA7), NOx alternative encoding (0xAB), supported PIDs in 0xC1-0xE0 range (0xC3, the next supported-PIDs bitmask after 0x80/0xA0), fuel cetane rating (0xC4), engine friction percent torque (0xC8). Total **85 verified universal OBD-II PID entries**. + +### Added (per-OEM enrichment, round 2) +- **VW (`catalogs/vw.json`)** — +5 DIDs: diesel common-rail pressure (0xF430), AdBlue tank level (0xF431), distance-since-last-DPF-regen (0xF433), charge-air temperature (0xF435), DSG oil pressure (0x0290). **52 entries total.** +- **BMW (`catalogs/bmw.json`)** — +5 DIDs: oil quality (0xD305), remaining oil-service distance (0xD306), front + rear brake-pad remaining (0xD307/D308), xDrive torque split (0xD500). **43 entries total.** +- **Ford (`catalogs/ford.json`)** — +4 DIDs: EcoBoost intercooler IAT (0xDE08), engine runtime lifetime (0xDE09), oil life remaining (0xDE0A), powertrain immobilizer status enum (0xDF05). **35 entries total.** + +### Notes +- Universal DTC + PID catalogs are the highest-leverage growth vector — every OEM extension inherits them via the `MergeCatalogJSON('dtc-iso-15031.json', …)` / `MergeCatalogJSON('obd2-pids.json', …)` calls in `BuildCatalog`. Per-OEM enrichments require per-OEM PRs; SAE/ISO universal data is one source citation per batch. +- Citation discipline: every new universal entry cites either SAE J2012 (DTC nomenclature) or SAE J1979 / ISO 15031-6 (OBD-II PID table), so they qualify for `verified: true` per the v3.18 acceptable-citations table. +- Per-OEM additions remain `verified: false`, sourced from the published community references the v3.18 provenance vocabulary documents (ross-tech-wiki, obdeleven-public, esys-community, bimmer-utility, forscan-community, motorcraft-pubs). +- All 40 catalog JSON files validated to parse cleanly (external `json.load` round-trip). + +## [3.20.0] - 2026-05-07 — Reference desktop tool (VCL) + +### Added +- **`examples/diagtool/`** — full reference VCL diagnostic tool that exercises every shipping framework API end-to-end. Built programmatically (no `.dfm`) so the project has just two files (`DiagTool.dpr` + `DiagTool.MainForm.pas`) — drop them into any Delphi 11/12 VCL project as a starter template. +- **Connection wizard** — port + baud combo boxes drive `TOBDConnectionSerial` / `TOBDConnectionAsync` lifecycle (Connect / Disconnect with proper teardown). +- **OEM auto-detect** — Read VIN button issues OBD-II Service 09 PID 02; the response routes through `TOBDOEMRegistry.FindByVIN` and the form labels update to show display name + manufacturer key + the chosen session negotiator. +- **Session control** — Extended → button calls `TOBDDiagSession.BeginSession(sstExtendedDiagnostic, $7E0)` so the OEM-specific choreography (VW SH+CRA / BMW E-Sys / Mercedes XENTRY F198 / Ford ST 32 / GM SP 6 / Stellantis F198) and the heartbeat thread come for free. End Session reverses cleanly. +- **Live Data tab** — refreshes battery voltage / engine RPM / vehicle speed / coolant temperature via standard SAE J1979 Service 01 PIDs (0x42 / 0x0C / 0x0D / 0x05). +- **DTCs tab** — Service 03 read populates a list; Service 04 clear is gated by a confirmation dialog. Selecting a code calls `IOBDOEMExtension.DescribeDTC` and the right-pane memo shows the catalog entry (description + severity + possible causes + repair hints + source + verified flag). +- **DIDs tab** — combo-box auto-populates from the OEM's `DataIdentifiers` catalog (universal `uds-standard.json` entries + per-OEM overlay). Read DID issues `TOBDDiagSession.ReadDID`, runs the response through `IOBDOEMExtension.DecodeDID`, and appends a transcript line per read. +- **Routines tab** — combo-box of catalogued `RoutineControl` identifiers; Start (31 01) issues `TOBDDiagSession.StartRoutine` and prints the status payload. +- All 28 OEM extensions self-register via the `.dpr` uses clause so VIN-based routing works for any of the 17 passenger / 6 heavy-duty / 5 Chinese OEMs. +- `examples/diagtool/README.md` documents the architecture, the build steps, and the deliberate limitations (single-ECU model, no SecurityAccess UI, synchronous reads on the UI thread for clarity). + +### Changed +- Nothing. The tool consumes the framework as-is. + +### Notes +- The companion console example (`examples/diagsession_console`, v3.13) is the minimal proof-of-concept; the v3.20 VCL tool is the proof-of-product showing every framework API in one place. Together they cover the spectrum from "bare-minimum integration" to "ship-ready GUI tool". + +## [3.19.0] - 2026-05-07 — Engine-OEM auto-routing + +### Added +- **`IOBDOEMExtension.ApplicableToECUSupplier(const SupplierID: string): Boolean`** — companion to `ApplicableToVIN` for OEMs that ship engines / modules into other manufacturers' chassis. Engine OEMs (Cummins, Detroit Diesel) and supplier-only modules use this branch when the chassis VIN routes elsewhere. The `SupplierID` is what the ECU returns from J1939 PGN 65259 'Make' or ISO 14229 DID 0xF18A (system_supplier_identifier). +- **`TOBDOEMRegistry.FindByECUSupplier(SupplierID): IOBDOEMExtension`** — walks every registered extension and returns the first that claims the given supplier ID. Empty string short-circuits to nil. +- `TOBDOEMExtensionBase` ships a default `ApplicableToECUSupplier` that returns False — every existing extension is **upward-compatible** and only the engine OEMs (Cummins + Detroit Diesel) opt in to the new probe. +- **`OBD.OEM.Cummins.ApplicableToECUSupplier`** — claims `'CUMMINS'` and the legacy `'CMI'` (Cummins Inc) token some pre-2010 ECMs emit on F18A. Case-insensitive, whitespace-trimmed. +- **`OBD.OEM.DetroitDiesel.ApplicableToECUSupplier`** — claims `'DETROIT'`, `'DDC'`, and the older `'DETROITDDC'` single-token form some MCM-1 modules use. +- `Tests.OEM.SupplierRouting` — 10 new test cases: positive-match for all known tokens (Cummins / CMI / Detroit / DDC / DETROITDDC), negative-match for cross-OEM tokens, registry-level routing for both engine OEMs, empty-string short-circuit, default-False guarantee for non-engine OEMs (VW, Toyota), case-insensitive + whitespace-trim guarantee. + +### Changed +- `IOBDOEMExtension` adds one method. The registry routing now has two probes — VIN first, then supplier — so a tool can call: + ```pascal + Ext := TOBDOEMRegistry.FindByVIN(Vin); + if Ext = nil then + Ext := TOBDOEMRegistry.FindByECUSupplier(SupplierFromF18A); + ``` + to handle the mixed-fleet case (Cummins X15 in a PACCAR Peterbilt vs. a Volvo VNL). + +### Notes +- `TOBDDiagSession` (v3.11) doesn't yet auto-cascade through the two probes — that's a Phase-9-ish ergonomic addition. For now production tools call the two registry helpers explicitly per the snippet above. + +## [3.18.0] - 2026-05-07 — Catalog deepening + verification protocol + +### Added (per-OEM catalog enrichment) +Across the existing 17 passenger OEM catalogs, **~70 new DID + routine entries** were added (all `verified: false` per the v3.3 provenance contract until cross-validated). Highlights: +- **VW (`catalogs/vw.json`)** — +9 DIDs incl. oil pressure (0xF40B), Lambda Bank 1 Sensor 1, DPF soot load (0xF420), EGR actual position, turbo actual boost, DSG transmission oil temp + DSG K1/K2 clutch wear (0x028E/F). +5 routines: KESSY proximity relearn, EPB service, Haldex calibration, DPF force-regen, security access level 3. +- **BMW (`catalogs/bmw.json`)** — +10 DIDs incl. DME / EGS software ID, oil level mm, oil temp, charge-air temp + boost, engine runtime, EGS oil temp + clutch wear, DSC yaw rate (0xC100). +5 routines: EZS / KESSY relearn, EMF / EPB service, RDC tire-pressure relearn, BMS battery registration, BMW TPI DPF regen. +- **Ford (`catalogs/ford.json`)** — +9 DIDs incl. engine hours, engine starts, IAT / ECT / throttle, EcoBoost MAP, PowerStroke DPF soot load, PATS status enum + key count. +5 routines: PCM KAM reset, oil life reset, PATS key program, DPF force-regen, EPB service. +- **Toyota (`catalogs/toyota.json`)** — +8 DIDs incl. engine run time, throttle / IAT / ECT, hybrid inverter temp, hybrid battery max + min block voltage, vehicle grade. +3 routines: smart-key relearn, hybrid battery test, oil maintenance reset. +- **Mercedes-Benz** — +5 DIDs (oil pressure, oil level mm, DPF soot load, AdBlue tank level, steering angle). +3 routines (EIS relearn, DPF regen, battery registration). +- **GM** — +5 DIDs (engine run time, oil pressure, oil life, throttle, immobilizer status enum). +2 routines (oil life reset, PassKey relearn). +- **Stellantis** — +3 routines (DPF regen, oil life reset, PSA BSI battery reg). +- **Honda + HMG + Nissan + Subaru + Mazda + Renault + Volvo** — 3 DIDs + 2-3 routines each: oil life, hybrid / EV pack data, brand-specific routines (battery registration, SAS calibration, oil-life reset, DPF regen). + +### Added (verification protocol) +- **`docs/CATALOG_FORMAT.md`** gains a comprehensive **acceptable-citations table** documenting what `source` values qualify an entry for `verified: true` (ISO standard / SAE standard / capture fixture / OEM-published spec — and explicitly excluding NDA-protected dealer DBs and "I tried it and it worked"). +- A **provenance vocabulary table** lists every `source` token the shipped catalogs use (~30 entries: ISO / SAE / GMLAN / TIS2Web / Motorcraft / ForScan / Ross-Tech / OBDeleven / E-Sys / bimmer-utility / XENTRY / HHTwin / Techstream / HDS / GDS / KDS / Consult / SSM / OpenECU / M-MDS / CLIP / VIDA / Tesla Toolbox / SDT / MUT-III / INSITE / DDDL / DAVIE4 / PTT / SDP3 / MAN-cats / BYD / Geely / NIO / Xpeng / GWM communities) so PR authors know which token is appropriate without reading the full source. +- New **`Tests.OEM.CatalogSmoke`** fixture: cycles every shipped JSON catalog through `TOBDOEMJSONCatalog.Create` and asserts the file parses without raising, declares a non-empty `manufacturer_key` (where applicable), and contributes at least one DID or routine. The regression guard that catches a trailing-comma typo or a bad decoder kind before tagging — **31 catalogs covered**. + +### Changed +- `tests/Tests.dpr` registers the new smoke fixture. + +### Notes +- Production callers filter `Verified` for production-critical paths: + ```pascal + for D in Ext.DataIdentifiers do + if D.Verified then UseInProduction(D); + ``` +- Universal `uds-standard.json` + `obd2-pids.json` + `dtc-iso-15031.json` remain the largest pools of `verified: true` entries (built from ISO / SAE published tables). Per-OEM catalogs grow toward `verified: true` as community contributors cite published specs in their PRs. + +## [3.17.0] - 2026-05-07 — Chinese OEMs (BYD / Geely / NIO / Xpeng / GWM) + +### Added (5 new Chinese OEM extensions) +- **`OBD.OEM.BYD`** — BYD Auto Co. Ltd. (3 WMIs: L6T, LGX, 8GA — Xi'an + Changsha + Brazil). 9-ECU e-Platform 3.0 map: VCU + drive motor + Blade-battery BMS at 0x782 + charge port + iBooster electronic brake + DiPilot driver assistance + climate. Blade battery pack ID + model code DIDs; pack voltage / SOC / SOH; charge-status enum. +- **`OBD.OEM.Geely`** — Geely Auto + Lynk & Co + Zeekr (5 WMIs: LB3, LFM, LJV, LBE, LGZ). 10-ECU map across CMA / SEA / SPA / BMA platforms. Geely platform code + market code DIDs; covers ICE + PHEV (Hi4-shared) + Geometry/Zeekr EV charge controller. Volvo Cars (Geely-owned) stays on `OBD.OEM.Volvo` — collision guard test included. +- **`OBD.OEM.NIO`** — NIO Inc. (2 WMIs: LJN, LBL). EV-only; 10-ECU map for the Hefei plant: VCU + dual-motor (front + rear inverters) + swappable BMS at 0x782 + charge port + Aquila autonomous-driving sensor suite + Banyan/Aspen IVI computer (NOMI). NIO model code + battery-swap pack ID DIDs; pre-swap handshake routine for the NIO Power Swap network. +- **`OBD.OEM.Xpeng`** — Xpeng Motors (2 WMIs: LJY, LMZ — Zhaoqing + Guangzhou). EV-only; 10-ECU map covering the XPILOT ADAS computer + dual-motor stack + Xmart OS cabin computer. Xpeng model code + XPILOT software version DIDs. +- **`OBD.OEM.GreatWall`** — Great Wall Motor (4 WMIs: LGW, LGE, LGT, X9X). Covers the five GWM brands (Haval / WEY / ORA / Tank / Poer) on one platform. 10-ECU map incl. Hi4 hybrid controller, ORA / Coffee EV charge controller, Coffee Pilot ADAS. GWM brand code + platform code (Lemon / Tank / Coffee) DIDs. +- Five matching JSON catalogs (`catalogs/{byd,geely,nio,xpeng,gwm}.json`) and DTC starters (`catalogs/dtc-{byd,geely,nio,xpeng,gwm}.json`). EV-specific decoders for pack voltage, SOC, SOH, charge-status enum. + +### Changed +- `Packages/RunTime.dpk` adds the 5 new units. The OEM registry now resolves **28 OEMs** total (17 passenger + 6 heavy-duty + 5 Chinese). + +### Notes +- `Tests.OEM.China` ships 16 new test cases: VIN routing for all 5 OEMs (Volvo-Cars-vs-Geely-Zeekr collision guard included), catalog spot-checks (BYD Blade BMS at 0x782, NIO Aquila, Xpeng XPILOT, GWM Hi4 hybrid, Geely EVCC), decoder spot-checks for each OEM's distinguishing DID. +- WMI assignments for Chinese OEMs are issued by MIIT under GB 16735 and are sometimes inconsistently documented across sources. The shipped set covers the most-cited assignments per OEM; production users add edge-case WMIs via `OBD.OEM..ApplicableToVIN` overrides if needed. + +## [3.16.0] - 2026-05-07 — Heavy-duty (J1939) OEM extensions + +### Added (6 new heavy-duty OEM extensions) +- **`OBD.OEM.HD`** — shared base for J1939-coupled OEMs. `TOBDHDSessionNegotiator` widens the tester-present heartbeat to 3000 ms so UDS-on-J1939 doesn't race with the broadcast DM1 stream. Constants for the J1939-71 source-address allocations the framework references (`J1939_ADDR_ENGINE_1` = 0, `J1939_ADDR_TRANSMISSION_1` = 3, `J1939_ADDR_BRAKES_SYSTEM` = 11, `J1939_ADDR_AFTERTREATMENT_1` = 66, …). Helpers `FormatSPNFMI(SPN, FMI)` and `ParseDM1DTC(Bytes, Offset)` round-trip the DM1 packed-DTC layout into the canonical `"SPN0094-FMI4"` string used by the catalog. +- **`OBD.OEM.Cummins`** — engine-only OEM (X15 / L9 / B6.7 / ISX15 / ISL9). No VIN match — resolved via `TOBDOEMRegistry.FindByKey('CUMMINS')` once the engine OEM is detected from PGN 65259 (component identification). 3-ECU map (engine + DPF/SCR aftertreatment); engine serial + calibration ID + DEF tank level + DPF soot load DIDs. +- **`OBD.OEM.DetroitDiesel`** — engine-only (DD13 / DD15 / DD16 with GHG17 emissions package). Daimler Truck NA brand; appears as the ECM on Freightliner Cascadia / Western Star. 4-ECU map (MCM + DT12 AMT + DPF + SCR); Detroit-specific calibration / emissions-family DIDs. +- **`OBD.OEM.PACCAR`** — Peterbilt + Kenworth + DAF + Leyland (8 WMIs incl. 1XP/1NP/5KJ/1NK/1XK/2NK/XLR/SCB). 7-ECU map (engine, transmission, Bendix/Wabco brakes, Driver Information Cluster, Cab + Body controllers, aftertreatment); chassis code + factory code DIDs. +- **`OBD.OEM.VolvoTrucks`** — Volvo Trucks + Mack Trucks + Renault Trucks (9 WMIs incl. 4V4/YV2/4V2/1M1/1M2/4V5/4V1/VG6/VF6). Separate from Volvo Cars (Geely-owned, covered in `OBD.OEM.Volvo`). 8-ECU map (EMS/EMC + I-Shift/mDRIVE + EBS + MID 140 + MID 144 VECU + aftertreatment + TPMS); chassis code + emissions-package DIDs. +- **`OBD.OEM.Scania`** — Scania AB / Traton (4 WMIs: VLU, YS2, XLE, 9BS). 8-ECU map (EMS DC09/13/16 + Opticruise OPC + EBS + retarder + ICL + COO coordinator + ACM aftertreatment + AWD forward radar); chassis number + specification code + engine serial DIDs. +- **`OBD.OEM.MAN`** — MAN Truck & Bus / Traton (2 WMIs: WMA, 9BW). 8-ECU map (EDC + TipMatic + EBS + PriTarder retarder + ZBR central computer + FHRR driver assist + BWS body computer + ACM); MAN-specific chassis code + factory options + engine serial DIDs. +- Six matching JSON catalogs (`catalogs/{cummins,detroit,paccar,volvotrucks,scania,man}.json`) with starter DIDs (engine hours / fuel used / DEF tank level / DPF soot load) — all `verified: false` per the v3.3 provenance contract. Six matching DTC starters using the SPN-FMI canonical form (`SPN0094-FMI4`, `SPN3251-FMI16`, `SPN5571-FMI16`, …) covering common heavy-duty fault codes: low fuel rail pressure, DPF differential pressure / soot load, DEF inducement, J1939 communication abnormal update rate. + +### Changed +- `Packages/RunTime.dpk` adds the 6 HD units + the shared `OBD.OEM.HD` base. The OEM registry now resolves **23 OEMs** total — 17 passenger + 6 heavy-duty. + +### Notes +- Engine-only OEMs (Cummins, Detroit Diesel) intentionally return `False` from `ApplicableToVIN` since they don't ship vehicles. Production callers detect the engine OEM from the J1939 component-identification PGN (or DID 0xF18A on UDS-capable trucks) and resolve via `TOBDOEMRegistry.FindByKey(…)`. Phase 8 (engine-OEM auto-routing from a J1939 component-identification probe) is a natural follow-up. +- `Tests.OEM.HD` ships 22 new test cases: SPN-FMI helper round-trip, `ParseDM1DTC` decoding (including a vector for SPN 0148 / FMI 4), VIN routing for all six OEMs (incl. Volvo-Trucks-vs-Volvo-Cars disambiguation guard), 3000 ms heartbeat assertion, ECU-map presence checks (Cummins engine ECM at J1939 address 0, Detroit DPF + SCR, Volvo I-Shift + MID 140, Scania OPC, MAN PriTarder), `FindByKey` resolution, and decoder spot-checks for each OEM's chassis-code DID. + +## [3.15.0] - 2026-05-07 — More OEMs + universal catalog enrichment + +### Added (5 new OEM extensions) +- **`OBD.OEM.Renault`** — Renault Group: Renault SA + Dacia + Alpine + Renault Korea (11 WMIs incl. VF1/VF2/VS5/VR1/3W2/UU1/UU3/UU6/VFA/VFD/KNM). 9-ECU CLIP map (UCH at 0x760, instrument cluster, ABS, SRS, climate, PAS, EVCC for Zoe/Megane E-Tech). Renault calibration ID + market code + options-block DIDs; `'RNLT'` XOR-mask seed-key starter. +- **`OBD.OEM.Volvo`** — Volvo Cars (Geely-owned, separate from Volvo Trucks) (6 WMIs incl. YV1/YV4/LYV/LVS/LVY/7JR). 10-ECU VIDA / DiCE map (CEM at 0x740, DIM cluster, Sensus IHU, EVCC for EX30/EX90). Build week + factory + PNO option DIDs; **5000 ms** tester-present interval (matches VIDA's extended session). +- **`OBD.OEM.Tesla`** — Tesla, Inc. (4 WMIs incl. 5YJ/LRW/XP7/7SA — Fremont + Shanghai + Berlin + Austin). 8-ECU map covering Powertrain, Vehicle Gateway, BMS at 0x782, Autopilot at 0x724, Cabin/IHU, Charge Port. Tesla firmware version + hardware-platform DIDs; battery-pack voltage / SOC / SOH; charge status enum. +- **`OBD.OEM.Suzuki`** — Suzuki Motor Corp + Maruti Suzuki India (9 WMIs incl. JS1/JS2/JSA/JSB/TSM/LSJ/MA3/MBH/ML8). 7-ECU SDT-II map; Suzuki/Maruti chassis-code DID; KWP2000 two's-complement seed-key starter. +- **`OBD.OEM.Mitsubishi`** — Mitsubishi Motors (8 WMIs incl. JA3/JA4/JMB/JMY/4A3/4A4/MMB/6MM). 8-ECU MUT-III map incl. AWC for Outlander PHEV at 0x762, ETACS body controller; SST DCT calibration routine; chassis-code + market-code DIDs. +- Five matching JSON catalogs (`catalogs/{renault,volvo,tesla,suzuki,mitsubishi}.json`) and DTC starters (`catalogs/dtc-{renault,volvo,tesla,suzuki,mitsubishi}.json`) — each with 5-8 manufacturer-specific entries. + +### Fixed +- **WMI `VR1` moved from Stellantis to Renault.** VR1 is the Renault Tangier (Morocco) plant — incorrectly listed under Stellantis since v3.2 (the Stellantis-Renault confusion: PSA + FCA = Stellantis; Renault is separate). The fix updates both `OBD.OEM.Stellantis.ApplicableToVIN` and `catalogs/stellantis.json`. Regression guard test `StellantisNoLongerClaimsVR1` lives in `Tests.OEM.Extras2`. + +### Added (universal catalog enrichment) +- **`catalogs/obd2-pids.json` — 17 new verified entries** filling gaps in the SAE J1979 / ISO 15031-6 ranges 0x60-0xA6: dual-MAF (0x66), EGR temperature, boost / VGT control, exhaust pressure, EGT bank 1 + 2 (0x78 + 0x79), engine run-time variants (0x7E + 0x7F), NOx sensor (0x83), hybrid/EV system data (0x9A), diesel after-treatment (0x9B), odometer PID (0xA6). Brings the universal OBD-II catalog to ~80 verified entries. +- **`catalogs/dtc-iso-15031.json` — 47 new verified P-codes + U-codes** covering camshaft phasing (P0011/P0014/P0016/P0017), fuel-rail pressure (P0087/P0088/P0190), MAP / TPS sensor faults (P0107-P0123), oxygen sensors (P0030-P0150 range), turbocharger boost (P0234/P0299), cylinder 7+8 misfire, glow-plug, EGR / SAI, EVAP small-leak (P0442), idle control, system voltage, ECM internal failure, fuel pump, transmission torque-converter clutch, DPF (P2002), post-cat fuel trim, IAT correlation, CAN bus-off (U0073), MS-CAN (U0010), instrument cluster comm-loss (U0155). Brings the universal DTC catalog to ~95 verified entries. + +### Changed +- `Packages/RunTime.dpk` adds the 5 new OEM units. The OEM registry now resolves **17 OEMs from VIN** covering ~95% of the global passenger fleet by WMI prefix. + +### Notes +- `Tests.OEM.Extras2` ships 21 new test cases: VIN routing for the 5 new OEMs, regression guard for the Stellantis VR1 fix, catalog spot-checks (ECU map presence, Volvo extended heartbeat, Tesla autopilot ECU, Mitsubishi AWC), decoder spot-checks (Renault calibration ID, Volvo PNO code, Tesla firmware version, Suzuki + Mitsubishi chassis codes), and universal-catalog growth assertions (odometer + NOx PIDs present, P0017 + P2002 verified DTCs present). +- Every per-OEM starter remains `verified: false` per the v3.3 provenance contract; universal SAE / ISO entries are `verified: true`. + +## [3.14.0] - 2026-05-07 — OEM coverage expansion (Asia/Pacific fleet) + +### Added +- Six new OEM extensions covering the Japanese + Korean fleet, all built on the v3.3-v3.13 framework (catalog + ECU map + session negotiator + seed-key registry + DTC catalog + DID decoders): + - **`OBD.OEM.Toyota`** — Toyota / Lexus / Daihatsu (16 WMIs incl. JTD/JTE/JTH/JTJ/JTK/JTM/JTN/2T1/2T2/4T1/4T3/5TD/5TE/5TF/5TY/JDA). 8-ECU TechStream map (engine, transmission, hybrid, ABS, SRS, immobilizer, body, cluster) plus Toyota-specific F1A0 calibration ID list, F1A1 ECU serial, hybrid-battery DIDs at 0x7E2. + - **`OBD.OEM.Honda`** — Honda / Acura (14 WMIs incl. JHM/JHL/JHF/JH4/1HG/19U/19V/2HG/2HK/2HN/3HG/5J6/5FN/5FP). 7-ECU HDS map; Honda-specific chassis-code (F1A0) + factory-code (F1A2) DIDs; XOR-mask seed-key starter. + - **`OBD.OEM.HyundaiKia`** — Hyundai / Kia / Genesis (15 WMIs incl. KMH/KM8/KMF/KMT/5NP/5NM/5NX/KNA/KND/KNH/KNB/5XX/5XY/KNF/KMK). 10-ECU GDS / KDS map incl. EV charge controller at 0x7E5; ROM ID + calibration ID + vehicle-option DIDs; 1500 ms tester-present interval (matches GDS default). + - **`OBD.OEM.Nissan`** — Nissan / Infiniti / Datsun (12 WMIs incl. JN1/JN6/JN8/1N4/1N6/3N1/5N1/5BZ/JNK/JNR/JNX/MNT). 9-ECU Consult III+ map incl. IPDM at 0x745, AVM at 0x768, Leaf/Ariya EV charge controller at 0x793; chassis-code + market-code DIDs. + - **`OBD.OEM.Subaru`** — Subaru (5 WMIs incl. JF1/JF2/JF3/4S3/4S4). 7-ECU SSM4 map incl. dedicated AWD controller at 0x7E2; CVT relearn routine; byte-rotate seed-key starter. + - **`OBD.OEM.Mazda`** — Mazda (6 WMIs incl. JM1/JM3/JM7/JMZ/4F2/4F4). 8-ECU M-MDS map incl. RBCM at 0x726 (Mazda-specific rear body controller); Mazda As-Built code + market code DIDs. +- Six matching JSON catalogs (`catalogs/{toyota,honda,hmg,nissan,subaru,mazda}.json`) with starter DIDs (~6-8 per OEM) — all `verified: false` per the v3.3 provenance contract. +- Six matching DTC starter catalogs (`catalogs/dtc-{toyota,honda,hmg,nissan,subaru,mazda}.json`) with 7-8 manufacturer-specific codes each (P-codes for engine/trans, B-codes for body, U-codes for comm-loss). Production users contribute via JSON edits without recompiling. +- `Tests.OEM.AsiaPacific` — 19 new test cases: VIN routing for every OEM (positive matches + cross-OEM rejection + unknown-VIN check), catalog spot-checks (Toyota engine ECU, Honda seed-key starter, HMG 1500 ms heartbeat, Nissan IPDM, Subaru AWD controller, Mazda RBCM), and DID decoder spot-checks for each OEM's custom decode paths. + +### Changed +- `Packages/RunTime.dpk` adds the six new units. The `OBD.OEM.Registry` now resolves 12 OEMs from VIN (up from 6). + +### Notes +- Toyota covers most of the global Japanese-built fleet; Honda picks up American Honda manufacturing; HMG is the third-largest automaker globally; Nissan + Subaru + Mazda round out the Japanese mid-tier and the AWD-focused niche. +- Seed-key starters are placeholders (community-pr provenance, `verified: false`). Real algorithms live behind dealer NDAs; production users register their own at app startup via `Ext.SeedKeyRegistry.RegisterAlgorithm($01, …)`. +- Combined with the European (VW, BMW, Mercedes, Stellantis) + American (Ford, GM) extensions from v3.2, the framework now covers ~85% of the global passenger-vehicle fleet by VIN-prefix. + +## [3.13.0] - 2026-05-07 — OEM Catalog Phase 7 (golden-check helper + reference CLI) + +### Added +- **`OBD.OEM.GoldenCheck`** — framework-neutral spot-check helper. `CheckGoldenVectors(Ext, Vectors)` runs each `(DID, Payload, ExpectedSubstring, Description)` tuple through the OEM extension's `DecodeDID` and returns a list of `TOBDGoldenFailure` records with the actual output and a pre-formatted reason — empty when every vector passed. Callers decide whether to `Assert.Fail` the batch, surface the count, or post-process. +- `Tests.OEM.GoldenCheck` — 4 helper-behaviour tests (passes / missing-substring / empty-output / empty-substring matches non-empty), plus `TPerOEMGoldenTests` with curated golden vectors for all four shipping OEM extensions (VW + BMW + Mercedes + Ford), 12 vectors total covering VIN, mileage, battery voltage, manufacturing date, programming status. These are the spot-check suite to run before tagging. +- **`examples/diagsession_console/DiagSessionDemo.dpr`** — small reference console tool that drives `TOBDDiagSession` end-to-end against any ELM327-compatible adapter on a serial port. Demonstrates the v3.11 high-level API: connect, pick OEM extension by VIN prefix, `BeginSession(sstExtendedDiagnostic, $7E0)`, `ReadDID(F190 / F189 / D050)` with decoded output, `EndSession`. ~75 lines — the canonical "hello, OEM" template a tool-builder copy-pastes from. + +### Changed +- `Packages/RunTime.dpk` adds `OBD.OEM.GoldenCheck`. + +### Notes +- This closes the seven-phase OEM extension plan started in v3.3. Every phase is now ✅ in `docs/OEM_EXTENSION_PLAN.md`. The framework now ships: + - DID + routine + DTC catalogs with provenance flags (v3.3 + v3.7) + - Per-ECU sub-catalogs (v3.4) + - Per-OEM session negotiators + plan runner with heartbeat (v3.5) + - Pluggable seed-key algorithms (v3.6) + - VW long coding / BMW FA + I-Stufe / MB SCN / Ford AsBuilt codecs (v3.8) + - UDS RoutineControl framework (v3.9) + - Capture-replay validation (v3.10) + - High-level `TOBDDiagSession` wrapper (v3.11) + - DoIP / ISO 13400-2 frame builders + parsers (v3.12) + - Golden-vector spot-checks + reference CLI (v3.13) +- Future growth lives along the orthogonal axes documented across `docs/OEM_EXTENSION_PLAN.md`: scaling each per-OEM JSON catalog from `verified: false` starter to `verified: true` production data, registering NDA-protected seed-key algorithms at app startup, and contributing real ECU captures into `tests/fixtures/captures/`. The framework no longer needs structural work to absorb that growth. + +## [3.12.0] - 2026-05-07 — OEM Catalog Phase 6.2 (DoIP / ISO 13400-2) + +### Added +- **`OBD.OEM.DoIP`** — ISO 13400-2 frame builders + parsers for the Ethernet transport modern (post-2018) cars use for UDS: + - `BuildDoIPHeader` / `ParseDoIPHeader` — the 8-byte protocol header (Version + InvVersion + PayloadType + PayloadLength) with the inversion check. + - `BuildRoutingActivationRequest` (default + WWH-OBD + central-security + OEM-specific activation types) and `ParseRoutingActivationResponse` (handles both 2010 9-byte and 2012 13-byte payload variants — the OEM-specific 4-byte tail). + - `BuildVehicleIdentRequest` (broadcast on UDP/13400) + `BuildVehicleIdentRequestByVIN` + `ParseVehicleAnnouncement` returning VIN, logical address, EID, GID, FurtherActionRequired, optional sync status. + - `BuildAliveCheckRequest` / `BuildAliveCheckResponse`. + - `BuildDiagnosticMessage(Source, Target, UserData)` / `ParseDiagnosticMessage` — wraps an arbitrary UDS request in the DoIP envelope so a `TOBDDiagSession` (v3.11) can use a TCP DoIP connection identically to a CAN connection. +- Enums for the documented payload types, activation types, and routing-response codes (success, vehicle-confirmation, all 7 standard rejection codes). +- `Tests.OEM.DoIP` — 22 new test cases: header (version-inversion encoding + check, big-endian payload-type / length round-trip, malformed inversion + short-buffer rejection), routing activation (default + OEM-specific activation type, v2010 + v2012 response parsing, truncation rejection, wrong-payload-type returns False), vehicle ident (empty payload broadcast, VIN-too-short rejection, VIN round-trip, VehicleAnnouncement field extraction including 17-char VIN + 6-byte EID/GID + sync status), diagnostic message (UDS wrapping with header + addresses, empty-user-data rejection, address + payload extraction, full round-trip, alive-check pair). + +### Changed +- `Packages/RunTime.dpk` adds `OBD.OEM.DoIP`. + +### Notes +- The DoIP unit is transport-agnostic by design — it produces and consumes byte arrays. Pair it with the existing `OBD.Connection.UDP` / `OBD.Connection.Wifi` for the actual sockets, and the `TOBDDiagSession` wrapper drives the UDS layer on top exactly the same way it does for CAN. +- Phase 7 (ODX-D import + golden-test helper) is the final milestone in `docs/OEM_EXTENSION_PLAN.md`. + +## [3.11.0] - 2026-05-07 — OEM Catalog Phase 6.1 (high-level diagnostic session) + +### Added +- **`OBD.OEM.DiagSession`** — `TOBDDiagSession` is the high-level wrapper that turns the lower-level OEM machinery into the API a tool actually calls. One class binds an OEM extension to a connection and exposes `BeginSession`, `EndSession`, `UnlockSecurityAccess`, `ReadDID`, `StartRoutine`, `StopRoutine`, `RequestRoutineResults`, plus a `State` accessor and a `LastError` string for the simple failure-reporting path tools want. +- The wrapper owns the tester-present heartbeat thread end-to-end: `BeginSession` starts it, `EndSession` (and the destructor) stop it gracefully. Re-entering the same session is idempotent; cross-session transitions stop the heartbeat first so the next session-control request doesn't race against it. +- `UnlockSecurityAccess(Level, [Algorithm])` runs the full UDS 27 LL → 67 LL SEED → 27 LL+1 KEY exchange. By default it pulls the algorithm from the OEM extension's `SeedKeyRegistry`; the optional `Algorithm` parameter lets production users plug their NDA-protected algorithm in at the call site without registering it globally. +- `ReadDID(DID, out Payload: TBytes)` and `ReadDID(DID, out Decoded: string)` — the second form runs the bytes through the OEM's `DecodeDID` so tool UIs can render the human-readable string directly. +- `StartRoutine(RID, InputData, out Status)` / `StopRoutine(RID)` / `RequestRoutineResults(RID, out Status)` thread negative-response NRCs into `LastError` instead of raising, matching the tool-friendly contract `BeginSession` / `EndSession` use. +- `Tests.OEM.DiagSession` — construction-time guards (`RejectsNilConnection`, `RejectsNilExtension`). The bytes-on-the-wire integration sits with the existing console flashing example which already drives the same primitives end-to-end. + +### Changed +- `Packages/RunTime.dpk` adds `OBD.OEM.DiagSession`. + +### Notes +- This is the integration milestone — the layer that proves the v3.3-v3.10 work composes cleanly. A new tool now writes: + ```pascal + Session := TOBDDiagSession.Create(Conn, OEM); + Session.BeginSession(sstExtendedDiagnostic, $7E0); + Session.UnlockSecurityAccess($01); + Session.ReadDID($F190, Vin); + Session.StartRoutine($0F00, [], Status); + Session.EndSession; + ``` + …and the framework handles the OEM-specific session choreography, the security-access dance, the heartbeat thread, the SID echo stripping, and the negative-response routing for them. +- Phase 6.2 (multi-bus / DoIP routing activation, FlexRay) is the next milestone. + +## [3.10.0] - 2026-05-07 — OEM Catalog Phase 5 (capture-replay validation) + +### Added +- **`OBD.OEM.Captures`** — replay-driven validation of OEM extensions against recorded `.obdlog` conversations. Walks a `TOBDReplayer`'s entries, pairs each Sent line with its next Received line, normalises ELM327 framing (multi-line `0:` / `1:` prefixes, `SEARCHING…`, prompts), extracts the UDS service ID + DID + payload from the request and the matching response, and runs every `0x22 ReadDataByIdentifier` pair through the OEM extension's `DecodeDID`. +- `TOBDCapturePair` — one structured request/response from the conversation: `RequestText`, `ResponseText`, `ServiceID`, `DID` (when 0x22), `PayloadBytes` (with the SID + DID echo stripped on positive replies), `IsNegative` + `NegativeResponseCode` for `7F SID NRC` replies. +- `TOBDCaptureDecoded` — the validator's per-pair report: which OEM catalog entry it matched (`DidIsCatalogued` + `DidName`) and the decoder's `Display` output. Negative replies and non-0x22 service IDs flow through with their pair attached for caller-side post-processing. +- High-level helpers: `ExtractCapturePairs(entries)`, `ValidateAgainstExtension(pairs, ext)`, `ValidateCaptureFile(path, ext)` for the round-trip "give me a `.obdlog`, give me an OEM extension, tell me what each pair decodes to". `NormalizeResponseText` is exposed so callers can pre-process recorded data outside the validator. +- `tests/fixtures/captures/sample-{vw,bmw,mercedes,ford}.obdlog` — synthetic conversations exercising VIN reads, mileage, I-Stufe, programming-status, calibration-id, and a deliberate negative response per file. Cover the most common DIDs the v3.4 + v3.7 catalogs already decode. +- `Tests.OEM.Captures` — 12 new test cases. Extract layer: ELM multi-line stripping, prompt / SEARCHING handling, request/response pairing, DID extraction from `22 HiDID LoDID`, negative-response capture, response-echo stripping for non-0x22 services, hanging-request handling. Validator layer: VW capture decodes the F190 VIN read and surfaces the negative reply; BMW capture decodes I-Stufe + mileage; Mercedes capture decodes the F19E programming-status enum; Ford capture decodes the calibration-ID DF01; negative responses round-trip the NRC byte. + +### Changed +- `Packages/RunTime.dpk` adds `OBD.OEM.Captures`. + +### Notes +- The shipped fixtures are synthetic — exactly the bytes a real ECU would return for the catalogued DIDs, but hand-authored. Real ECU captures donated by the community are the natural growth path; the framework already accepts whatever `TOBDRecorder.SaveToFile` produces, so contributors only need to capture-and-commit. +- Phase 6 (DoIP / FlexRay / multi-bus extensions on top of the existing protocol layer) is the next milestone. + +## [3.9.0] - 2026-05-07 — OEM Catalog Phase 4 (RoutineControl schemas) + +### Added +- **`OBD.OEM.RoutineControl`** — UDS Service 0x31 (RoutineControl) wire helpers + argument schemas. Implements ISO 14229-1 §10.5.4 end-to-end: build a request, parse the positive / negative response, and project the status payload through a per-routine field schema for human-readable rendering. +- `TOBDRoutineRequestBuilder` — fluent builder for the request payload. `AddUInt8`, `AddUInt16BE`, `AddUInt32BE`, `AddInt16BE`, `AddInt32BE`, `AddAscii(s, FixedLength)` (zero-pads and rejects too-long input), `AddRawBytes`, `AddBcdDate(YY, MM, DD)`, `AddBcdYear`. `ToFrame(SubFunction, RID)` wraps the payload as `31 SF HiRID LoRID …`; `Clear` resets for re-use. +- `TOBDRoutineResponseReader` — cursor-based reader for the response status payload. `ReadUInt8 / ReadUInt16BE / ReadUInt32BE / ReadInt16BE / ReadInt32BE / ReadAscii(N) / ReadHexBytes(N) / ReadBcdDate`. `ReadAscii` strips trailing `#0` padding (the way most ECU firmware writes ASCII). Under-reads raise `EOBDRoutineError` with cursor + remaining-byte info for easier debugging. +- Top-level wire helpers: `BuildStartRoutine(RID, [InputData])`, `BuildStopRoutine(RID)`, `BuildRequestRoutineResults(RID)`, and `ParseRoutineResponse(Response, ExpectedSF, ExpectedRID)`. The parser distinguishes positive `71 SF RID …` replies (returns the status payload as `TBytes`) from negative `7F 31 NRC` replies (raises `EOBDRoutineError` with the NRC in the message) and from short / wrong-SID replies. +- `TOBDRoutineSchema` + `TOBDRoutineField` + `TOBDRoutineFieldKind` — output schemas mirror the v3.3 DID decoder format (uint8/16BE/32BE, int variants, ASCII, hex, BCD date, enum with named values, bitmask with bit names). `DecodeRoutineOutput(Schema, Bytes)` walks the response and produces one `TOBDDecodedField` per output (`Display` string + `Raw` slice). Truncated responses decode the prefix only — useful when an OEM optionally trails extra status bytes. +- `Tests.OEM.RoutineControl` — 27 new test cases covering: builder (uint/int big-endian round-trip, signed -1 → 0xFF FF FF FF, ASCII pad + too-long rejection, BCD date / year, ToFrame wrapping, Clear), reader (multi-byte BE, ASCII zero-pad strip, BCD date, hex slice, under-read rejection, HasMore tracking), wire frames (start with / without data, stop, request-results, parse positive, parse rejects wrong SID / SF / RID, parse on negative response, empty status payload), and schema decoding (uint8 with scale + offset + unit, ASCII + uint32 mileage, bitmask with named bits, enum with hex fallback, truncation handling). + +### Changed +- `Packages/RunTime.dpk` adds `OBD.OEM.RoutineControl`. + +### Notes +- `TOBDRoutineSchema` is the structural primitive — production callers typically pair it with a per-OEM `TDictionary` keyed by RID (Phase 4.1, future). The framework intentionally doesn't ship an OEM-wide schema registry yet because real schemas live in OEM-private ODX files. +- Phase 5 (real-capture `.obdlog` test fixtures cross-validating the catalog decoders) is the next milestone. + +## [3.8.0] - 2026-05-07 — OEM Catalog Phase 3 (coding / variant-write encoders) + +### Added +- **`OBD.OEM.Coding`** — shared base for OEM coding codecs. Exposes `HexStringToBytes` (strips whitespace + `-_:.` separators, rejects odd-length / non-hex), `BytesToHexString` (with optional separator), and bit-level `GetBit` / `SetBit` over a `TBytes`. +- **`OBD.OEM.Coding.VW`** — `TOBDVWLongCoding` mutable VAG long-coding string. Constructed from the hex returned by DID 0xF1A0 / 0xF1AF, gives byte and bit accessors, `HasNonZeroByte` for the dealer-tools "is this fresh coding?" check, and round-trips back via `ToHex`. Length is per-controller and fixed at construction; out-of-range writes raise `EOBDCodingError`. +- **`OBD.OEM.Coding.BMW`** — two records: + - `TOBDBMWFA` — vehicle-order option list. Parses comma / semicolon / whitespace-separated tokens, normalises to upper case, de-duplicates on add, sorts on `ToString` so equal orders always serialise identically (audit-friendly). + - `TOBDBMWIStufe` — `Project-YY-MM-Build` versioning quad. `Parse` validates each segment; `CompareTo` orders by Project → Year → Month → Build; `AtLeast` returns False across different projects (you should never compare an F-series to a G-series I-Stufe). +- **`OBD.OEM.Coding.Mercedes`** — `TOBDMercedesSCN` structured SCN (Standard-Codierung-Nummer). The framework treats segments as opaque strings — Hardware / Project / Build — and only validates the structure (3 segments, alphanumeric-only). Per-segment semantics live in caller-supplied lookup tables since they're FIN-keyed and NDA-protected. +- **`OBD.OEM.Coding.Ford`** — `TOBDFordAsBuiltBlock` for the per-DID 5-byte format used by FORScan / IDS exports. `ComputeChecksum` implements the documented FORScan algorithm (sum of all 5 data bytes mod 256); `IsValid` validates a parsed block; `Reseal` recomputes after editing. `ParseFordAsBuiltText` walks a multi-line export, skipping blank lines and `;` / `#` comments. +- `Tests.OEM.Coding` — 38 new test cases: hex/bit helpers (round-trip, separator stripping, odd-length rejection, bad-character rejection, bit operations + out-of-range), VW long coding (construction, byte/bit ops, has-non-zero detection, hex round-trip, snapshot independence, out-of-range rejection), BMW FA (parsing, dedup, normalisation, removal, sort-on-serialise, case-insensitive lookup, empty rejection), BMW I-Stufe (parse round-trip, malformed input rejection, ordering by Y/M/Build, cross-project AtLeast, zero-padding), Mercedes SCN (3-segment parsing, segment-count rejection, illegal-character rejection, upper-casing, round-trip), Ford AsBuilt (checksum algorithm, line parsing, missing-checksum rejection, reseal-after-edit, comment skipping, round-trip). + +### Changed +- `Packages/RunTime.dpk` adds `OBD.OEM.Coding`, `.VW`, `.BMW`, `.Mercedes`, `.Ford`. + +### Notes +- These are the codec primitives — the wire format on each side. Per-controller bit-name maps for VW long coding and per-FIN SCN dictionaries belong to caller-supplied data files (and in many cases NDA-protected OEM catalogs); the framework gives you the mutable structure plus byte / bit accessors so an application can layer its own UI on top. +- Phase 4 (RoutineControl argument schemas — input encoders + output decoders) is the next milestone. + +## [3.7.0] - 2026-05-07 — OEM Catalog Phase 2 (DTC catalogs) + +### Added +- **`OBD.OEM.DTC`** — Diagnostic Trouble Code framework. `TOBDDtcCatalog` provides O(1) `FindByCode` over an indexed list of `TOBDDtcCatalogEntry` records (`Code`, `Severity`, `Description`, `PossibleCauses`, `RepairHints`, `Source`, `Verified`). Severities: `dtcSeverityInfo` / `Warning` / `Critical` / `Unknown`. +- ISO 15031-5 / SAE J2012 wire-format helpers: `FormatDtc(High, Low)` / `FormatDtc(TBytes)` decode the two-byte DTC into the canonical 5-character form (`P0301`, `B22A8`, `U0100`); `EncodeDtc(string)` round-trips back to bytes; `IsManufacturerDtc` flags the P1xxx / P3xxx / B1xxx / B3xxx / C1xxx / C3xxx / U1xxx / U3xxx ranges. +- JSON catalog format mirrors the v3.3 DID schema (provenance via `source` + `verified`). Supports both the canonical `{ default_source, dtcs: [...] }` envelope and a bare `[...]` array for trivial files. +- `OBD.OEM.DTC.Loader.MergeDtcCatalog(file, catalog)` reuses the catalog search path from v3.3 so DTC files live alongside the DID files in `catalogs/`. +- **`catalogs/dtc-iso-15031.json`** — universal SAE J2012 / ISO 15031-6 baseline of 47 P0xxx + U0xxx entries (misfires, oxygen sensors, EVAP, EGR, catalyst, transmission, communication-loss codes), all `verified: true`. +- **Per-OEM DTC starters** (all `verified: false`): `dtc-vw.json` (VAG cooling / TCM / DSG / mechatronic), `dtc-bmw.json` (VANOS / Valvetronic / DDE diesel boost / FlexRay), `dtc-mercedes.json` (CDI fuel / ESP / SRS / ESM), `dtc-ford.json` (KAM / EVAP / throttle limp), `dtc-gm.json` (HO2S / Tech 2 trans / SDM airbag), `dtc-stellantis.json` (FCA + PSA EVAP / BSI). 7-9 entries each, sourced from public service-manual summaries. +- `IOBDOEMExtension.DtcCatalog` + `DescribeDTC(Code, out Entry)` — every extension exposes its lazily-loaded catalog (universal baseline + per-OEM overlay) and a one-shot lookup helper. Production callers register additional entries on the catalog at runtime via `Cat.Add(Entry)`. +- `Tests.OEM.DTC` — 20 new test cases: every encoding case (P / C / B / U letters, manufacturer first-digits 1 + 3, byte round-trip, malformed-input rejection, manufacturer-vs-SAE detection, severity round-trip), and the catalog (top-level envelope vs bare array, case-insensitive lookup with whitespace trimming, duplicate-code replacement, possible-causes / repair-hints capture, default-source propagation, verified-flag default). + +### Changed +- `IOBDOEMExtension` gains `DtcCatalog` + `DescribeDTC`. `TOBDOEMExtensionBase` adds the lazy catalog accessor + virtual `SeedDefaultDtcCatalog` and `DtcCatalogFileName` override-points; all six OEM extensions chain to a baseline `dtc-iso-15031.json` load and append their per-OEM overlay. +- `Packages/RunTime.dpk` adds `OBD.OEM.DTC` and `OBD.OEM.DTC.Loader`. + +### Notes +- The universal `dtc-iso-15031.json` baseline is `verified: true` against SAE J2012 — safe to surface in production diagnostics. Per-OEM starters remain `verified: false` until cross-validated against an OEM service manual; the same provenance contract as v3.3 applies. +- Phase 3 (coding / variant-write encoders — VW long coding, BMW FA, Mercedes SCN, Ford AsBuilt) is the next milestone. + +## [3.6.0] - 2026-05-07 — OEM Catalog Phase 1.4 (seed-key plug-ins) + +### Added +- **`OBD.OEM.SeedKey`** — pluggable SecurityAccess (UDS service 0x27) algorithm framework. `IOBDSeedKeyAlgorithm` is a pure function (`ComputeKey(Seed, Level)`); `TOBDSeedKeyRegistry` maps levels (the odd byte in `27 LL`) to one or more candidate algorithms. Newer registrations win, so production users plug their NDA-protected real algorithm in at startup and the public starter steps aside automatically. +- Reference algorithm classes (publicly-documented, all `verified: false`): + - `TOBDSeedKeyKWP2000TwosComplement` — `key = (NOT seed) + 1` byte-wise with carry; the ISO 14229 textbook example. Several legacy KWP2000 modules accept it verbatim. + - `TOBDSeedKeyXorMask` — `key[i] = seed[i] XOR mask[i]` with the mask tiled when shorter than the seed; covers a class of aftermarket bypass dongles. + - `TOBDSeedKeyByteRotate` — caller-supplied shift, rotate (0..7) and mask; approximates publicly-described pre-UDS Ford / GM Class B variants. + - `TOBDSeedKeyConstant` — fixed key independent of seed; useful for lab fixtures and the few pre-2010 modules that accept Level 1 with a constant. +- Frame helpers: `RequestSeedFrame(Level)`, `SendKeyFrame(Level, Key)`, `ExtractSeed(Response, Level)` — round-trip the wire format with explicit error reporting (rejects even seed-request levels, wrong SID, level mismatch, empty key). +- `IOBDOEMExtension.SeedKeyRegistry` — every OEM extension exposes its registry; `TOBDOEMExtensionBase` lazily instantiates and seeds it via the new `SeedDefaultSeedKeyAlgorithms` override-point. +- All six OEM extensions ship a default starter algorithm at Level 1: VW + Mercedes + Stellantis use the KWP2000 two's-complement; BMW uses an XOR-mask placeholder from bimmer-utility; Ford uses a byte-rotate placeholder from the ForScan documentation; GM uses the public GMLAN Class B trial-mode constant. All `verified: false`. +- `Tests.OEM.SeedKey` — 28 new test cases: the four reference algorithms (textbook two's-complement vector, byte-wise carry across 0x12345678 → 0xEDCBA988, XOR mask tiling, rotation behaviour, constant-key seed-independence, empty-input rejection), the registry (register / find / find-all / unregister / level enumeration / clear / LIFO precedence), the frame helpers (request seed, send key, extract seed, every error path), and the per-OEM hookup (each of the six extensions has a starter at Level 1; production override shadows the starter; starters are unverified). + +### Changed +- `IOBDOEMExtension` gains `SeedKeyRegistry: TOBDSeedKeyRegistry`. `TOBDOEMExtensionBase.Destroy` cleans the per-instance registry up. +- `Packages/RunTime.dpk` adds `OBD.OEM.SeedKey`. + +### Notes +- **Real seed-key algorithms remain NDA-protected by every OEM.** Nothing shipped here will unlock a production ECU; the starters exist so the broader SecurityAccess flow (request → seed → key → respond) can be exercised end-to-end against a simulated ECU. Production users register their own algorithm at app startup; `RegisterAlgorithm` returns the new entry to the head of the level's list, so the public starter is automatically shadowed. +- Phase 2 (DTC catalogs — manufacturer-specific P1xxx / B / C / U codes) is the next milestone in `docs/OEM_EXTENSION_PLAN.md`. + +## [3.5.0] - 2026-05-07 — OEM Catalog Phase 1.3 (session negotiation) + +### Added +- **`OBD.OEM.Session`** — manufacturer-specific session-negotiation framework. `IOBDSessionNegotiator` describes an OEM's choreography for entering / leaving each diagnostic session as a *plan* (an ordered list of adapter and UDS steps plus a tester-present heartbeat spec). Plans are pure data, so the OEM core stays free of async dependencies. +- `TOBDSessionType` enum: `sstDefault`, `sstProgramming`, `sstExtendedDiagnostic`, `sstSafetySystem`, plus two reserved OEM-specific slots (`sstOEMSpecific1` / `sstOEMSpecific2`) for vendor session subtypes that don't fit the ISO 14229 four. +- `TOBDStandardSessionNegotiator` — pure ISO 14229 reference implementation (10 03 / 10 01, 3E 80 every 2000 ms, optional `AT SH ` header step). Used as the default for every extension that doesn't override. +- Six OEM negotiators, each modelling published service-tool behaviour: + - `TOBDVWSessionNegotiator` — emits `AT SH ` + `AT CRA ` before 10 03 (matches ODIS / VCDS). + - `TOBDBMWSessionNegotiator` — flags `RequiresSecurityAccess` for both extended diagnostic and programming (matches E-Sys); 1500 ms tester-present interval for older E-series DMEs. + - `TOBDMercedesSessionNegotiator` — appends a 22 F1 98 workshop-code probe after 10 03 (XENTRY default); 1500 ms heartbeat. + - `TOBDFordSessionNegotiator` — prepends `AT ST 32` (≈3.2 s adapter timeout) for programming sessions to absorb the FDRS pause. + - `TOBDGMSessionNegotiator` — locks the ELM327 to ISO 15765-4 11/500 (`AT SP 6`) before opening a session. + - `TOBDStellantisSessionNegotiator` — appends 22 F1 98 with an empty `ExpectedResponse` so PSA's required probe doesn't fail on FCA modules that NACK it. +- `IOBDOEMExtension.SessionNegotiator` — every extension exposes its negotiator; `TOBDOEMExtensionBase` caches the instance lazily and lets subclasses override `CreateSessionNegotiator`. +- **`OBD.OEM.Session.Runner`** — async-first plan executor: + - `TOBDSessionRunner.Execute(Plan)` walks each step against `TOBDConnectionAsync`, awaits its `IOBDFuture` reply, and validates the response against the step's `ExpectedResponse` prefix (empty prefix = "any non-empty reply passes" — that's what lets Stellantis' optional F198 step tolerate FCA NACKs). + - `TOBDSessionRunResult` returns a per-step audit trail (response text, success flag, error, wall-clock duration) so callers can log exactly which step failed and what the ECU said. + - `TOBDTesterPresentThread` — fire-and-forget heartbeat thread driven by the plan's `TesterPresentMs` / `TesterPresentRequest`. Exits cleanly on `StopGracefully` (cancels in-flight futures + waits for the thread to drain) and self-terminates if the connection drops, so a closed adapter doesn't spin. +- `Tests.OEM.Session` — 18 new test cases covering: standard negotiator (header step, default-vs-non-default heartbeat, EndSession 10 01, security-access flags, zero-address omits header) and the six per-OEM negotiators (VW SH+CRA, BMW security-access flags + 1500 ms heartbeat, Mercedes F198 probe, Ford ST 32 only on programming, GM SP 6 prefix, Stellantis F198 with empty `ExpectedResponse`); plus extension-level checks that each OEM resolves to the correct negotiator and the negotiator is cached across calls. + +### Changed +- `Packages/RunTime.dpk` adds `OBD.OEM.Session` and `OBD.OEM.Session.Runner`. + +### Notes +- The session negotiators describe the *protocol* choreography; security-access (seed-key) is intentionally out of scope here and lands in Phase 1.4 (`IOBDSeedKeyAlgorithm` registry per OEM / level). +- The runner is exercised end-to-end against a mock connection in Phase 1.4 once seed-key plays the second half of the session-entry handshake. The plan layer (negotiator outputs) is fully covered today. + +## [3.4.0] - 2026-05-07 — OEM Catalog Phase 1.2 (per-ECU sub-catalogs) + +### Added +- **Per-ECU sub-catalogs.** `IOBDOEMExtension` gains `ECUs: TArray` and `CatalogForECU(Address): TOBDOEMSubCatalog`. The framework now models the vehicle bus map: each catalogued DID and routine carries an `EcuAddress` field, and callers can request the subset that applies to a single ECU (engine 0x7E0 vs transmission 0x7E1 vs cluster 0x40, …) instead of walking a flat catalog where 0xF187 means whatever the answering ECU said. +- `TOBDOEMECU` record (`Address`, `Name`, `CommonName`) — describes one ECU on the bus. Helper `ECU(addr, name, common_name)` mirrors the existing `DID()` / `Routine()` builders. +- `TOBDOEMSubCatalog` record (`EcuAddress`, `DIDs`, `Routines`) — the filtered view returned by `CatalogForECU`. Globals (entries with `EcuAddress = 0`) flow through to every ECU; ECU-scoped entries are added when the address matches. +- JSON catalog schema additions: top-level `ecus` array (declares the bus map), top-level `default_ecu_address` (propagates to entries that omit `ecu_address`), and `ecu_address` is now also valid on routine entries. Schema documented in `docs/CATALOG_FORMAT.md`. +- `MergeCatalogJSON(file, var DIDs, var Routines, var ECUs)` overload merges the loaded `ecus` block alongside the DID + Routine merges. The original two-argument overload still works for callers that don't need the ECU map. +- All six OEM Pascal extensions (`OBD.OEM.{VW,BMW,Mercedes,Ford,GM,Stellantis}`) ship a hard-coded ECU map covering powertrain (engine, transmission), chassis (ABS / ESP / SRS), body (BCM / cluster / climate), and gateway addresses. Per-OEM `catalogs/.json` files now carry the same `ecus` block; the seed VW + BMW catalogs additionally annotate `ecu_address` per DID and per routine where the scope is known. +- `Tests.OEM.Catalog.TPerECUTests` — 7 new test cases: ECU list parsing, per-DID `ecu_address`, default-address propagation, explicit-address override, routine `ecu_address` parsing, `CatalogForECU` filter behaviour for scoped entries, and global-entry flow-through to every sub-catalog. + +### Changed +- `TOBDOEMExtensionBase.BuildCatalog` signature gains a third `var ECUs: TArray` parameter so subclasses populate DIDs, Routines, and the ECU map in a single hook. Callers outside this repository that subclassed `TOBDOEMExtensionBase` will need a one-line signature update. +- `OBD.OEM.Helpers.DID()` and `Routine()` zero-initialise their result records (so the new `EcuAddress` field is always defined) and gain three-argument overloads `DID(addr, name, desc, ecu_addr)` / `Routine(id, name, desc, ecu_addr)` for inline scoping. + +### Notes +- The ECU addresses shipped in the hard-coded Pascal maps and the seeded JSON `ecus` blocks are based on public-knowledge UDS request IDs (ISO 15765-4 0x7E0-0x7E7 for emissions, vendor-specific ranges from ross-tech, esys-community, forscan-community, tis2web-public, alfaobd / diagbox, xentry-community references). Per-DID `ecu_address` annotations remain `verified: false` until cross-checked against OEM specs or capture fixtures — the same provenance contract that landed in v3.3 applies. +- Phase 1.3 (manufacturer-specific session negotiation: `BeginSession` / `EndSession` / `StartTesterPresent` per OEM) is the next milestone in `docs/OEM_EXTENSION_PLAN.md`. + +## [3.3.0] - 2026-05-06 — OEM Catalog Phase 1.1 (DID scale-up infrastructure) + +### Added +- **External JSON catalog format** for OEM extensions, documented in `docs/CATALOG_FORMAT.md`. Schema v1 includes per-entry `source` and `verified` provenance flags so callers can filter unverified community data out of production-critical paths. +- `OBD.OEM.Catalog.JSON` (`src/Services/`) — `TOBDOEMJSONCatalog` loads and walks a v1 catalog file. Supports decoder kinds: `ascii`, `hex`, `uint8/16_be/32_be`, `int16_be`, `int32_be`, `bcd_date`, `enum` (with size + value lookup map), `bitmask` (with size + bit-name map), `seconds`. `DecodePayload(DID, Bytes)` formats raw ECU bytes per the catalog's decoder spec. +- `OBD.OEM.Catalog.CSV` — `TOBDCatalogCSVImporter` ingests RFC-4180 CSV with mandatory `did,name,description` columns plus optional `source,verified,ecu_address,decoder` columns. Decoder column accepts an embedded JSON sub-object via standard CSV double-quote escaping. Emits a v1 JSON catalog ready to drop into `catalogs/`. +- `OBD.OEM.Catalog.Loader` — bridges the JSON loader into `TOBDOEMExtensionBase.BuildCatalog`. Each OEM's extension calls `MergeCatalogJSON('.json', DIDs, Routines)` after populating its hard-coded fallback. JSON entries win on DID conflict; missing files leave the hard-coded set untouched (so binaries deployed without the catalog folder still work). +- `catalogs/uds-standard.json` — verified ISO 14229-1 universal F1xx range (31 DIDs + 4 routines, all `verified: true` against the ISO Annex F table). +- `catalogs/obd2-pids.json` — verified ISO 15031-6 / SAE J1979 OBD-II Service 01 PIDs (60+ entries, all verified, full unit conversions for RPM, MAF, fuel trim, oxygen sensors, fuel rate, catalyst temperatures, …). +- `catalogs/{vw,bmw,mercedes,ford,gm,stellantis}.json` — seeded per-OEM catalogs with community-sourced entries (all `verified: false`, with `source` cited per entry: ross-tech-wiki, esys-community, xentry-community, forscan-community, tis2web-public, alfaobd-community, diagbox-public, community-pr). +- All six existing OEM extensions (`OBD.OEM.VW`, `.BMW`, `.Mercedes`, `.Ford`, `.GM`, `.Stellantis`) now merge their JSON catalog + the universal `uds-standard.json` overlay on top of the hard-coded fallback. Per-OEM combined coverage jumps from ~15 hard-coded entries to 60–100+ entries depending on the manufacturer. +- `tools/import-csv/ImportCSV.dpr` — small console tool (`ImportCSV `) that drives `TOBDCatalogCSVImporter` for community catalog contributors who keep their data as CSV. +- `Tests.OEM.Catalog` — 16 test cases covering JSON parsing, every decoder kind (uint/int/ascii/hex/bcd_date/enum/bitmask/seconds), CSV → JSON round-trip, embedded-JSON decoder columns, comment lines, missing-mandatory-column rejection, default-source propagation, verified-flag default. + +### Notes +- This milestone ships the **infrastructure + provenance** for catalog growth, not a full OEM build-out. The `verified: false` entries in the per-OEM catalogs are starter community data and must NOT be trusted for production-critical decisions (flashing, security access). The path to `verified: true` is documented in `docs/CATALOG_FORMAT.md` (cite the OEM spec, or contribute a cross-validating capture in `tests/fixtures/`). +- Future phases of the OEM extension plan (1.2 per-ECU sub-catalogs, 1.3 session negotiation, 1.4 seed-key plugins, 2 DTC catalogs, 3 coding encoders, 4 routine schemas, 5 real-capture test fixtures, 6 multi-bus, 7 ODX importer) are tracked in `docs/OEM_EXTENSION_PLAN.md` as separate future milestones. + +## [3.2.0] - 2026-05-06 — Production Crypto + OEM Coverage (Proposal C) + +### Added +- `TOBDBCryptVerifier` (`src/Services/OBD.ECU.Signature.BCrypt.pas`) — production-grade firmware verification via Windows CNG (BCrypt). Handles **RSA-PKCS1-SHA256** and **ECDSA-P256-SHA256** out of the box. Imports SubjectPublicKeyInfo DER blobs through `CryptImportPublicKeyInfoEx2`; auto-detects the algorithm from the OID. ECDSA signatures in OpenSSL's ASN.1 DER form are transcoded to the fixed-size R||S the BCrypt API expects. No external DLLs — `crypt32.dll` and `bcrypt.dll` ship with every supported Windows version. +- `TOBDOpenSSLVerifier` (`src/Services/OBD.ECU.Signature.OpenSSL.pas`) — alternative verifier for shops that already ship OpenSSL or need RSA-PSS / non-stock curves. Dynamically loads `libcrypto-3.dll` (or v1.1 fallback) so projects without OpenSSL on the path don't fail to start; throws `EOBDOpenSSLNotAvailable` on construction when the library is missing. +- `IOBDHSMSession` + `TOBDHSMVerifier` (`src/Services/OBD.ECU.Signature.HSM.pas`) — contract for plug-in HSM-backed verification (PKCS#11, AWS CloudHSM, Azure Key Vault). Concrete sessions live in caller code; the framework exposes them as plain `IFirmwareSignatureVerifier` instances that slot into `TOBDECUFlashing` like any other. +- `TOBDNonceVault` (`src/Utilities/OBD.Security.Nonce.pas`) — anti-replay primitive: cryptographically-random nonces (Windows `RtlGenRandom`), TTL-based expiry, single-use redemption. Distinguishes unknown / expired / replay error states so audit logs can record which case fired. +- Four new OEM extensions: `OBD.OEM.Mercedes` (XENTRY-style — covers WDB / WDC / WDD / WDF / WD3 / WD4 / 4JG WMIs), `OBD.OEM.Ford` (covers 1FA-1FT, 2FA, 2FT, 3FA, 3FT, 1LN, 5LM, 1MR, 6FP, WF0), `OBD.OEM.GM` (Global B / GMLAN — covers 1G1, 1G2, 1G4, 1G6, 1G8, 1GC, 1GT, 2G1, 2GT, 3G1, 3GT, 5GR, 6G1), `OBD.OEM.Stellantis` (FCA + PSA — covers 1C3-1C6, 2C3-3C4, 1D4-3D4, 1J4/1J8, 1RR, ZFA-ZFC, 9BD, ZAR, ZAM, VF3, VF7, VR1, W0L, VXR). Each ships an initial DID + RoutineControl catalog and per-DID decoders for VIN, mileage, battery voltage, programming dates / status. **These are starter catalogs** — real production coverage is documented in [`docs/OEM_EXTENSION_PLAN.md`](docs/OEM_EXTENSION_PLAN.md). +- `examples/ecuflashing_console/` — end-to-end console example that loads firmware + signature + DER public key from disk, constructs `TOBDBCryptVerifier`, drives `TOBDECUFlashing` through every stage against a simulated ECU. Shows exactly which four callbacks need to be replaced with real OEM UDS sequences. +- `tests/fixtures/` — real RSA-2048 + ECDSA-P256 test vectors generated with OpenSSL 3.0 (DER public keys, signatures of "hello world"). Embedded in the test runner via `test-fixtures.inc` so the BCrypt + OpenSSL verifiers are exercised against actual cryptographic operations on the Windows runner. +- `Tests.ECU.Signature.BCrypt`, `Tests.ECU.Signature.OpenSSL`, `Tests.Security.Nonce`, `Tests.OEM.Extra` — 25+ new test cases covering verify-pass, tampered-firmware, tampered-signature, empty-input rejection (verifiers); issue / redeem / replay-rejection / expiry / reset (nonce); VIN routing and DID decoding for the four new OEMs. +- `docs/OEM_EXTENSION_PLAN.md` — concrete plan for taking the OEM catalogs from "starter" to "production-grade" via 7 phases (DID scale-up, per-ECU sub-catalogs, session negotiation, seed-key plugins, DTC catalogs, coding encoders, real-capture test fixtures, ODX/CSV import tooling). + +## [3.1.0] - 2026-05-06 — FMX Component Completion (Proposal A) + +### Added +- Framework-neutral renderer for every visual component, in `src/CustomControls/`: + - `OBD.Render.Tachometer`, `OBD.Render.TrendGraph`, `OBD.Render.DtcList`, + - `OBD.Render.Terminal`, `OBD.Render.Knob`, `OBD.Render.SegmentedSwitch`, + - `OBD.Render.LED`. Each ships a flat `TOBDRenderState` record and a `Render(Canvas, State)` function. VCL and FMX bindings both marshal their state into the record and delegate. +- FMX bindings, in `src/Components/`: + - `OBD.Tachometer.FMX`, `OBD.TrendGraph.FMX`, `OBD.DtcList.FMX`, + - `OBD.Terminal.FMX`, `OBD.Knob.FMX`, `OBD.SegmentedSwitch.FMX`, + - `OBD.LED.FMX`. Each extends `TSkPaintBox`, mirrors the VCL property surface with `TAlphaColor` colours, self-drives transitions via `TStopwatch` where applicable, handles FMX-style mouse / wheel / focus events. +- `Packages/RunTime.FMX.dpk` updated to ship every renderer + FMX binding. +- `Packages/DesignTime.FMX.dpk` (new) — IDE registration via `OBD.CustomControl.Register.FMX`. Drops every FMX component on the same "ERDesigns OBD" palette page as the VCL set. +- `examples/mobile_dashboard/` — FMX dashboard exercising all eight FMX components (Tachometer, three LinearGauges, TrendGraph with two series, DtcList, Terminal, two LEDs, SegmentedSwitch, Knob). Built entirely in code; runs on Win32, Win64, macOS, iOS, Android. + +### Changed +- Every VCL component listed above now marshals its `PaintSkia` state into the matching renderer record and delegates. Public API unchanged. Private `DrawSeries` / `DrawGrid` / `DrawLegend` (TrendGraph), `DrawRow` / `ColorForSeverity` / `StatusLabel` (DtcList), and `ColorForDirection` / `PrefixForDirection` (Terminal) helpers removed — their logic moved into the renderer. + +### Notes +- VCL `TOBDLed` keeps its existing snapshot-cache path because it integrates with VCL `TStyleManager`. The new FMX `TOBDLedFMX` uses the renderer directly. Unifying the two paths is a v3.2+ task that needs a platform-neutral style abstraction. + +## [3.0.0] - 2026-05-06 — FMX & OEM extensions + +### Added +- `OBD.Render.LinearGauge` — framework-neutral Skia renderer that the VCL `TOBDLinearGauge` and the new FMX `TOBDLinearGaugeFMX` both delegate to. Establishes the renderer-extract pattern that the remaining v3.1+ FMX bindings will follow. +- `TOBDLinearGaugeFMX` (`src/Components/OBD.LinearGauge.FMX.pas`) — first FMX visual component. Extends `TSkPaintBox`, mirrors the VCL property surface with `TAlphaColor` colours, drives its own ease-out-cubic value transition via `TStopwatch`. Lives in the new `Packages/RunTime.FMX.dpk` so VCL builds aren't dragged into FMX dependencies. +- `IOBDOEMExtension` + `TOBDOEMRegistry` + `TOBDOEMExtensionBase` (`src/Services/OBD.OEM.pas`) — extension framework for manufacturer-specific UDS coverage. Contract covers manufacturer key + display name, applicability check (typically by VIN WMI), DID + RoutineControl catalogs, per-DID decode. Registry is thread-safe and lookups are by VIN, by manufacturer key, or by enumerating `All`. +- `OBD.OEM.Helpers` — `DID()` and `Routine()` factory helpers for compact `[DID($1234, 'name', 'desc'), …]` literals when building catalogs. +- `OBD.OEM.VW` — reference VW Group extension (matches WVW / WV1 / WV2 / WAU / TRU / TMB / VSS WMIs). Ships a starter catalog of common UDS DIDs + routines and decodes `battery_voltage`, `vehicle_speed`, and `vin`. +- `OBD.OEM.BMW` — reference BMW extension (WBA / WBS / WBY / WMW / 5UX / 4US WMIs). Catalog includes `i_stufe` and `fa_assembly` DIDs (the inputs to E-Sys-style coding) and decodes `mileage`, `battery_voltage`, `vin`. +- `examples/oem_demo/` — console example: take a VIN, list the matching extension's catalog, optionally decode a DID payload from hex. +- `Tests.OEM` — 12 tests covering registry register/unregister/find, VIN matching for VW + BMW, idempotent register, unknown-DID fallback, all the implemented DID decoders. + +### Changed +- `TOBDLinearGauge.PaintSkia` now marshals its state into a `TOBDLinearGaugeRenderState` and calls `OBD.Render.LinearGauge.RenderLinearGauge`. Behaviour and published API unchanged; the rendering code moved. + diff --git a/PACKAGE_INSTALLATION_FIX.md b/PACKAGE_INSTALLATION_FIX.md deleted file mode 100644 index f2114a97..00000000 --- a/PACKAGE_INSTALLATION_FIX.md +++ /dev/null @@ -1,87 +0,0 @@ -# Package Installation Fix - -## Problem -When attempting to install the DesignTime package, users encountered the error: -**"Kan opgegeven module niet vinden"** (Dutch for "Cannot find specified module") - -This error occurred because the Skia DLL (`libskia.dll`) required by Skia4Delphi was not accessible when the package was being loaded. - -## Root Cause -The packages were configured to output BPL files to non-standard locations: -- **DesignTime.dpk**: No explicit BPL output path configured, defaulting to project directory -- **RunTime.dpk**: Hardcoded path `Z:\Projects\Delphi\OBD\Delphi-OBD\Packages\` - -When installed in these locations, the Delphi IDE couldn't find the Skia DLL because: -1. The Skia4Delphi packages and their DLLs are installed in the standard Delphi BPL directory -2. The OBD packages were in a different location -3. Windows DLL search path didn't include the Skia DLL location - -## Solution -Both packages have been reconfigured to output their BPL files to Delphi's standard package directory using the MSBuild variable `$(BDSCOMMONDIR)\Bpl`. - -### Changes Made - -#### DesignTime.dproj -- Added `$(BDSCOMMONDIR)\Bpl` to: - - Base configuration (applies to all platforms) - - Base_Win32 configuration - - Base_Win64 configuration (outputs to `\Bpl\Win64` subdirectory) - - Base_Win64x configuration (outputs to `\Bpl\Win64x` subdirectory) - - Cfg_1_Win32 configuration (Debug build) - -#### RunTime.dproj -- Changed `` from hardcoded `Z:\Projects\Delphi\OBD\Delphi-OBD\Packages\` to `$(BDSCOMMONDIR)\Bpl` in: - - Base configuration - - Base_Win32 configuration - - Base_Win64x configuration (outputs to `\Bpl\Win64x` subdirectory) - - Cfg_1_Win32 configuration (Debug build) - -### What `$(BDSCOMMONDIR)\Bpl` Resolves To -This MSBuild variable typically resolves to: -``` -C:\Users\Public\Documents\Embarcadero\Studio\[Version]\Bpl -``` - -For example: -- Delphi 11: `C:\Users\Public\Documents\Embarcadero\Studio\22.0\Bpl` -- Delphi 12: `C:\Users\Public\Documents\Embarcadero\Studio\23.0\Bpl` - -## Benefits -1. **Packages install in the standard location** where Delphi IDE expects them -2. **Skia DLL is accessible** because Skia4Delphi packages are in the same directory -3. **No manual DLL copying** required -4. **Platform-independent** configuration using MSBuild variables -5. **Consistent with Delphi best practices** -6. **Works across different Delphi versions** automatically - -## Installation Instructions -1. **Install Skia4Delphi** via GetIt Package Manager (required dependency) -2. **Compile and install RunTime.dpk** first -3. **Compile and install DesignTime.dpk** second -4. The packages will automatically output to the correct directory -5. Restart Delphi IDE if components don't appear in Tool Palette - -## Verification -After installation, you should see the BPL files in: -``` -C:\Users\Public\Documents\Embarcadero\Studio\[YourVersion]\Bpl\ -├── RunTime.bpl -├── DesignTime.bpl -├── Skia.Package.RTL.bpl (from Skia4Delphi) -├── Skia.Package.VCL.bpl (from Skia4Delphi) -└── libskia.dll (from Skia4Delphi) -``` - -## Troubleshooting -If you still encounter issues after this fix: -1. Verify Skia4Delphi is properly installed via GetIt Package Manager -2. Check that all BPL files are in the same directory -3. Ensure `libskia.dll` is present in the BPL directory -4. Try uninstalling and reinstalling both packages -5. Restart Delphi IDE - -## Technical Notes -- The fix does not change any source code, only project configuration -- Both 32-bit (Win32) and 64-bit ARM (Win64x) platforms are supported -- The Win64 configuration is included for future compatibility -- The configuration uses MSBuild variables for portability diff --git a/Packages/RunTime.dpk b/Packages/RunTime.dpk index f1da42f0..7e52aad9 100755 --- a/Packages/RunTime.dpk +++ b/Packages/RunTime.dpk @@ -178,6 +178,44 @@ contains OBD.OEM.Lada in '..\src\Services\OBD.OEM.Lada.pas', OBD.OEM.Dacia in '..\src\Services\OBD.OEM.Dacia.pas', OBD.OEM.ServiceFunction in '..\src\Services\OBD.OEM.ServiceFunction.pas', - OBD.OEM.Coding.Common in '..\src\Services\OBD.OEM.Coding.Common.pas'; + OBD.OEM.Coding.Common in '..\src\Services\OBD.OEM.Coding.Common.pas', + OBD.OEM.Coding.Diff in '..\src\Services\OBD.OEM.Coding.Diff.pas', + OBD.OEM.Coding.AuditLog in '..\src\Services\OBD.OEM.Coding.AuditLog.pas', + OBD.ECU.Flashing.Checkpoint in '..\src\Services\OBD.ECU.Flashing.Checkpoint.pas', + OBD.OEM.Coding.Toyota in '..\src\Services\OBD.OEM.Coding.Toyota.pas', + OBD.OEM.Coding.Honda in '..\src\Services\OBD.OEM.Coding.Honda.pas', + OBD.OEM.Coding.HMG in '..\src\Services\OBD.OEM.Coding.HMG.pas', + OBD.OEM.Coding.Stellantis in '..\src\Services\OBD.OEM.Coding.Stellantis.pas', + OBD.ECU.Signature.PQC in '..\src\Services\OBD.ECU.Signature.PQC.pas', + OBD.ECU.Flashing.VoltageGate in '..\src\Services\OBD.ECU.Flashing.VoltageGate.pas', + OBD.Protocol.DoIP.Discovery in '..\src\Protocol\OBD.Protocol.DoIP.Discovery.pas', + OBD.Adapter.Capabilities in '..\src\Adapters\OBD.Adapter.Capabilities.pas', + OBD.Protocol.SecOC in '..\src\Protocol\OBD.Protocol.SecOC.pas', + OBD.Protocol.IsoTp.Timing in '..\src\Protocol\OBD.Protocol.IsoTp.Timing.pas', + OBD.Adapter.PassThrough.J2534v2 in '..\src\Adapters\OBD.Adapter.PassThrough.J2534v2.pas', + OBD.EV.BatteryHealth in '..\src\Services\OBD.EV.BatteryHealth.pas', + OBD.Tachograph.Signature in '..\src\Services\OBD.Tachograph.Signature.pas', + OBD.OEM.ServiceRoutines in '..\src\Services\OBD.OEM.ServiceRoutines.pas', + OBD.Tachograph.Workshop in '..\src\Services\OBD.Tachograph.Workshop.pas', + OBD.Service06.Mode06 in '..\src\Services\OBD.Service06.Mode06.pas', + OBD.Protocol.WWHOBD in '..\src\Protocol\OBD.Protocol.WWHOBD.pas', + OBD.J1939.PGNs in '..\src\Protocol\OBD.J1939.PGNs.pas', + OBD.UDS.NRC in '..\src\Services\OBD.UDS.NRC.pas', + OBD.Protocol.WWHOBD.Readiness in '..\src\Protocol\OBD.Protocol.WWHOBD.Readiness.pas', + OBD.Service09.Calibration in '..\src\Services\OBD.Service09.Calibration.pas', + OBD.DriveCycle.Advisor in '..\src\Services\OBD.DriveCycle.Advisor.pas', + OBD.OEM.KeyAdaptation.BMW in '..\src\Services\OBD.OEM.KeyAdaptation.BMW.pas', + OBD.OEM.ComponentProtection.VAG in '..\src\Services\OBD.OEM.ComponentProtection.VAG.pas', + OBD.OEM.SCN.Mercedes in '..\src\Services\OBD.OEM.SCN.Mercedes.pas', + OBD.OEM.KeyAdaptation.HMG in '..\src\Services\OBD.OEM.KeyAdaptation.HMG.pas', + OBD.OEM.KeyAdaptation.Ford in '..\src\Services\OBD.OEM.KeyAdaptation.Ford.pas', + OBD.OEM.KeyAdaptation.Toyota in '..\src\Services\OBD.OEM.KeyAdaptation.Toyota.pas', + OBD.OEM.SessionHelper in '..\src\Services\OBD.OEM.SessionHelper.pas', + OBD.DriveCycle.Resolvers in '..\src\Services\OBD.DriveCycle.Resolvers.pas', + OBD.RadioCode in '..\src\RadioCode\OBD.RadioCode.pas', + OBD.RadioCode.Variants in '..\src\RadioCode\OBD.RadioCode.Variants.pas', + OBD.RadioCode.Registry in '..\src\RadioCode\OBD.RadioCode.Registry.pas', + OBD.RadioCode.Pending in '..\src\RadioCode\OBD.RadioCode.Pending.pas', + OBD.RadioCode.VinResolver in '..\src\RadioCode\OBD.RadioCode.VinResolver.pas'; end. diff --git a/Packages/RunTime.dproj b/Packages/RunTime.dproj index d98766d5..cd7b9b53 100644 --- a/Packages/RunTime.dproj +++ b/Packages/RunTime.dproj @@ -221,6 +221,11 @@ + + + + + Base diff --git a/QuickStart.md b/QuickStart.md index cc2d2252..74284bea 100644 --- a/QuickStart.md +++ b/QuickStart.md @@ -30,23 +30,17 @@ This quick start shows how to scaffold a Skia-enabled OBD UI with the updated pa --- -## What's New in v2.0 +## What's New in v3.79 -### Performance Improvements -- **Zero-Copy Rendering**: All visual components now use direct Skia rendering via `TSkSurface.MakeFromHDC()`, eliminating TBitmap buffering -- **Shared Animation Manager**: Centralized timer management with TStopwatch for high-resolution timing (microsecond precision) -- **Optimized Memory Usage**: Background images cached as ISkImage snapshots, reducing allocations -- **Frame-Independent Animation**: Smooth animations regardless of frame rate +- **Async UDS client** (`OBD.OEM.UdsClient.Async`) — future-returning facade over `IOBDUdsClient` so UI threads can fire-and-await every diagnostic call without blocking. Cooperative cancellation via `IOBDCancellationToken`. +- **Cross-platform DoIP** (`OBD.Protocol.DoIP.Session.Cross`) — TCP-side ISO 13400-2 §8 implementation on `System.Net.Socket` (Windows, macOS, Linux, iOS, Android), no Indy or Synapse dependency. +- **DoIP TLS** (`OBD.Protocol.DoIP.Session.TLS`) — ISO 13400-3 §7, TCP/3496, mutual TLS via Indy + OpenSSL. TLS 1.2 minimum. +- **Capture/replay transport** — `TCaptureReplayTransport` parses recorded `.obdlog` pairs for deterministic UDS testing. +- **Catalog Browser** (`examples/catalogbrowser`) — VCL app that walks every shipped OEM catalog (ECUs / DIDs / Routines / Coding Blocks / Adaptations / Actuator Tests / Live PIDs / DTC Extended Data). +- **Coverage harness** (`tools/coverage/`) — `delphi-code-coverage` against the DUnitX runner, emits HTML + Cobertura + LCOV. +- **79 OEM catalogs** / 247,279 entries / 5 vehicle classes (33 new motorcycles, agricultural, marine, powersports catalogs in v3.78). -### Architecture Changes -- **Direct Skia Integration**: Components inherit from `TOBDCustomControl` (based on `TSkCustomControl`) -- **Eliminated ~500 Lines**: Removed duplicate TBitmap buffering code across all components -- **Improved Error Handling**: All Paint methods protected with try-except blocks and validation checks - -### Bug Fixes -- Fixed ToBitmap compilation errors -- Fixed duplicate Canvas variable shadowing -- Fixed division by zero issues in gauge rendering +See [CHANGELOG.md](CHANGELOG.md) for the full history. --- @@ -66,18 +60,16 @@ This quick start shows how to scaffold a Skia-enabled OBD UI with the updated pa **Problem**: "Cannot find specified module" error when installing DesignTime package **Solution**: 1. Ensure Skia4Delphi is properly installed via GetIt Package Manager -2. The packages are now configured to output to the standard Delphi BPL directory (`$(BDSCOMMONDIR)\Bpl`) -3. This ensures the Skia DLL (libskia.dll) is accessible when the package loads -4. First install RunTime.dpk, then DesignTime.dpk -5. If the issue persists, verify that Skia4Delphi packages are installed in the same BPL directory +2. The packages output BPLs to `$(BDSCOMMONDIR)\Bpl` so the Skia DLL is on the IDE's search path +3. First install RunTime.dpk, then DesignTime.dpk ### Rendering Issues **Problem**: Blank or black components **Solution**: -1. Ensure parent form has `DoubleBuffered := True` -2. Check that Skia4Delphi is properly installed -3. Verify component `Visible := True` and proper `Align` settings +1. Check that Skia4Delphi is properly installed +2. Verify component `Visible := True` and proper `Align` settings +3. Do **not** set `DoubleBuffered := True` on the parent form — `TSkCustomControl` already buffers and form-level double buffering interferes with it (see `docs/COMPONENT_AUTHORING.md` §10). **Problem**: Jerky or stuttering animations **Solution**: @@ -111,17 +103,13 @@ This quick start shows how to scaffold a Skia-enabled OBD UI with the updated pa **Problem**: Memory usage grows over time **Solution**: -1. Ensure you're on v2.0+ with optimized memory management -2. Check for memory leaks using FastMM4 -3. Limit number of simultaneously visible components +1. Check for memory leaks using FastMM4 +2. Limit number of simultaneously visible components ### Common Errors **Error**: "Access violation at address..." -**Solution**: Update to v2.0+ which includes comprehensive error handling in Paint methods - -**Error**: "Division by zero" -**Solution**: Update to v2.0+ which includes validation in all property setters +**Solution**: Make sure you're on a recent v3.x release; all `PaintSkia` paths are now guarded. --- diff --git a/README.md b/README.md index 442ae677..36622d14 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![CI](https://github.com/erdesigns-eu/Delphi-OBD/actions/workflows/ci.yml/badge.svg)](https://github.com/erdesigns-eu/Delphi-OBD/actions/workflows/ci.yml) [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE.md) [![Delphi](https://img.shields.io/badge/Delphi-11%20%7C%2012-red.svg)](https://www.embarcadero.com/products/delphi) -[![Roadmap](https://img.shields.io/badge/Roadmap-v3.76%20Isuzu%20%7E18%25%20ODIS-brightgreen.svg)](docs/ROADMAP.md) +[![Roadmap](https://img.shields.io/badge/Roadmap-v3.79%20Async%20UDS%20%2B%20DoIP%20TLS-brightgreen.svg)](docs/ROADMAP.md) [![Changelog](https://img.shields.io/badge/Changelog-Keep%20a%20Changelog-yellow.svg)](CHANGELOG.md) Comprehensive Delphi library for OBD-II diagnostics, vehicle protocols, and automotive utilities. @@ -25,10 +25,15 @@ The repository is organized into logical folders for better maintainability: │ ├── Utilities/ # Logger, string helpers, settings, data modules │ ├── VIN/ # VIN decoder and utilities │ └── Wizards/ # IDE wizards for project creation -├── docs/ # Documentation +├── docs/ # Documentation (see docs/index.md) │ ├── README.md # Main documentation -│ ├── TASKS.md # Development tasks and roadmap -│ └── RADIO_CALCULATORS.md # Radio code calculator guide +│ ├── ARCHITECTURE.md # Module map and rendering pipeline +│ ├── PROTOCOLS.md # Wire-level protocol reference (CAN, DoIP, J1939, Legacy) +│ ├── COMPONENT_AUTHORING.md # How to add a new visual component +│ ├── CATALOG_FORMAT.md # OEM catalog JSON schema (v2) +│ ├── RADIO_CALCULATORS.md # Radio code calculator guide +│ ├── ROADMAP.md # Releases shipped + future backlog +│ └── TROUBLESHOOTING.md # Common installation and runtime issues ├── examples/ # Example applications │ ├── minimal/ # Minimal OBD connection example │ ├── simple/ # Simple diagnostic example @@ -100,7 +105,8 @@ See the `examples` folder for complete working examples. ### Protocols - **CAN** (ISO 15765-4) -- **DoIP** (ISO 13400) +- **DoIP** (ISO 13400) — Windows (WinSock), cross-platform (`System.Net.Socket`), and TLS-secured (ISO 13400-3 §7) variants +- **UDS** (ISO 14229) with async client (`OBD.OEM.UdsClient.Async`) — future-returning facade with cooperative cancellation - **J1939** (SAE J1939 for heavy-duty vehicles) - **Legacy** (ISO 9141-2, ISO 14230 KWP2000) @@ -109,9 +115,15 @@ See the `examples` folder for complete working examples. - **OBDLink** (ST command support: SX, MX, EX models) - Voltage monitoring - Connection retry with exponential backoff +- Capture/replay transport (`.obdlog` round-trip) for deterministic testing + +### OEM Coverage +- **79 OEM catalogs** / 247,279 entries / 5 vehicle classes (passenger, motorcycles, agricultural, marine, powersports) +- JSON Schema v2 (`catalogs/_schema/oem-catalog-v2.json`) with DTC formats for SAE J2012, J1939 SPN-FMI, and 22 OEM prefixes +- See [docs/CATALOG_FORMAT.md](docs/CATALOG_FORMAT.md) and [catalogs/INDEX.md](catalogs/INDEX.md) ### Radio Code Calculators -32 brand-specific radio code calculators: +32+ brand-specific radio code calculators: - **Japanese**: Nissan, Toyota, Honda, Mazda, Mitsubishi, Subaru, Suzuki, Hyundai/Kia - **European**: Mercedes, BMW, Opel, Volvo, VW, Audi, SEAT, Skoda, Renault, Peugeot, Citroen, Fiat - **American**: Ford, Chrysler/Jeep/Dodge, GM (Chevrolet, Cadillac, GMC, Buick) @@ -141,9 +153,14 @@ See `docs/RADIO_CALCULATORS.md` for details. ## Documentation -- **[Main Documentation](docs/README.md)** - Comprehensive guide -- **[Radio Calculator Guide](docs/RADIO_CALCULATORS.md)** - Radio code algorithms and usage -- **[Development Tasks](docs/TASKS.md)** - Roadmap and task tracking +- **[Documentation Index](docs/index.md)** - Map of every doc by topic +- **[Architecture](docs/ARCHITECTURE.md)** - Module map and rendering pipeline +- **[Protocols](docs/PROTOCOLS.md)** - Wire-level protocol reference +- **[Component Authoring](docs/COMPONENT_AUTHORING.md)** - How to add a new visual component +- **[Catalog Format](docs/CATALOG_FORMAT.md)** - OEM catalog JSON schema +- **[Radio Calculators](docs/RADIO_CALCULATORS.md)** - Radio code algorithms and usage +- **[Roadmap](docs/ROADMAP.md)** - Releases shipped + future backlog +- **[Troubleshooting](docs/TROUBLESHOOTING.md)** - Common installation and runtime issues ## Examples @@ -157,8 +174,10 @@ Browse the `examples` folder for working demonstrations: ## Requirements - Embarcadero Delphi 11 or higher -- Windows 7, 8/8.1, 10, 11 +- **VCL components**: Windows 7, 8/8.1, 10, 11 +- **FMX components and cross-platform DoIP**: Windows, macOS, Linux, iOS, Android - Skia4Delphi (for visual components) +- OpenSSL + Indy 10 (for DoIP TLS only) ## License @@ -170,7 +189,7 @@ Ernst Reidinga (ERDesigns) ## Contributing -Contributions are welcome! Please see `docs/TASKS.md` for current development priorities. +Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) and [docs/ROADMAP.md](docs/ROADMAP.md) for current development priorities. ## Support diff --git a/catalogs/adapter-capabilities.json b/catalogs/adapter-capabilities.json new file mode 100644 index 00000000..ff7cd93d --- /dev/null +++ b/catalogs/adapter-capabilities.json @@ -0,0 +1,89 @@ +{ + "schema_version": 1, + "description": "Adapter capability registry. Add a new adapter row without recompiling.", + "capability_keys": [ + "CAN", + "CANFD", + "ISO-TP", + "ISO-TP-LF", + "DoIP", + "J1939", + "K-Line", + "VoltageMonitor", + "SecureOnboard", + "J2534", + "J2534v2", + "BluetoothLE", + "WiFi", + "FTDI" + ], + "fields": { + "adapter_key": "stable lower-case identifier", + "display_name": "shown in UIs", + "capabilities": "list of capability_keys", + "max_iso_tp_frame_bytes": "0/7 = CAN-classic, 62 = CAN-FD, larger for DoIP" + }, + "entries": [ + { + "adapter_key": "elm327", + "display_name": "ELM327", + "capabilities": [ + "CAN", + "ISO-TP", + "K-Line", + "VoltageMonitor" + ], + "max_iso_tp_frame_bytes": 7 + }, + { + "adapter_key": "obdlink_mx", + "display_name": "OBDLink MX", + "capabilities": [ + "CAN", + "ISO-TP", + "K-Line", + "VoltageMonitor", + "BluetoothLE" + ], + "max_iso_tp_frame_bytes": 7 + }, + { + "adapter_key": "obdlink_ex", + "display_name": "OBDLink EX", + "capabilities": [ + "CAN", + "CANFD", + "ISO-TP", + "ISO-TP-LF", + "K-Line", + "VoltageMonitor", + "FTDI" + ], + "max_iso_tp_frame_bytes": 62 + }, + { + "adapter_key": "doip_gateway", + "display_name": "DoIP Gateway", + "capabilities": [ + "DoIP", + "ISO-TP", + "ISO-TP-LF", + "VoltageMonitor" + ], + "max_iso_tp_frame_bytes": 4095 + }, + { + "adapter_key": "j2534", + "display_name": "J2534 Pass-Through", + "capabilities": [ + "CAN", + "ISO-TP", + "K-Line", + "J1939", + "J2534", + "VoltageMonitor" + ], + "max_iso_tp_frame_bytes": 7 + } + ] +} \ No newline at end of file diff --git a/catalogs/drive-cycle-generic.json b/catalogs/drive-cycle-generic.json new file mode 100644 index 00000000..dbbfb400 --- /dev/null +++ b/catalogs/drive-cycle-generic.json @@ -0,0 +1,92 @@ +{ + "schema_version": 1, + "spec": "ISO 15031-7", + "description": "Generic per-monitor drive-cycle steps. Override per-OEM by registering a TDriveCycleResolver.", + "entries": [ + { + "monitor": "Misfire", + "description": "Cold start, idle 30 s, accelerate to 90 km/h, cruise 5 min, decelerate without braking. Repeat once.", + "duration_seconds": 600 + }, + { + "monitor": "FuelSystem", + "description": "Cruise at 80 km/h in closed loop for 5 minutes after warm-up.", + "duration_seconds": 300 + }, + { + "monitor": "Comprehensive", + "description": "After warm-up, idle 30 s and cruise 5 min in closed loop.", + "duration_seconds": 330 + }, + { + "monitor": "Catalyst", + "description": "Two stabilised cruises at 65 km/h for 3 min each, separated by 15 s of deceleration without braking.", + "duration_seconds": 420 + }, + { + "monitor": "HeatedCatalyst", + "description": "Cold start; let the catalyst reach light-off temperature.", + "duration_seconds": 600 + }, + { + "monitor": "EvaporativeSystem", + "description": "Cold start with fuel level between 1/4 and 3/4. Idle 4 min, cruise 50–80 km/h for 10 min.", + "duration_seconds": 900 + }, + { + "monitor": "SecondaryAirSystem", + "description": "Cold start; idle until secondary air pump cycles off (~30–90 s).", + "duration_seconds": 90 + }, + { + "monitor": "OxygenSensor", + "description": "Cruise at constant speed in closed loop for 10 minutes.", + "duration_seconds": 600 + }, + { + "monitor": "OxygenSensorHeater", + "description": "Cold start; let oxygen sensors heat up (~30 s after start).", + "duration_seconds": 60 + }, + { + "monitor": "EGRorVVTSystem", + "description": "Cruise at 80 km/h for 5 min, then decelerate to 30 km/h with foot off accelerator.", + "duration_seconds": 360 + }, + { + "monitor": "ACRefrigerant", + "description": "Run A/C for at least 10 minutes at idle and cruise.", + "duration_seconds": 600 + }, + { + "monitor": "NMHCCatalyst", + "description": "Diesel cold start; sustained cruise at 60–90 km/h for 15 min.", + "duration_seconds": 900 + }, + { + "monitor": "NOxAftertreatment", + "description": "Diesel: highway cruise 80–100 km/h for 20 min after AdBlue dosing.", + "duration_seconds": 1200 + }, + { + "monitor": "BoostPressureSystem", + "description": "Three full-throttle accelerations from 30–100 km/h with full warm-up.", + "duration_seconds": 600 + }, + { + "monitor": "ExhaustGasSensor", + "description": "Cold start; 20 min mixed driving including idle and cruise.", + "duration_seconds": 1200 + }, + { + "monitor": "PMFilter", + "description": "Diesel: cruise above 60 km/h for 20 min to reach regen temperature.", + "duration_seconds": 1200 + }, + { + "monitor": "EGRSystem", + "description": "Cruise 60–80 km/h for 10 min after warm-up.", + "duration_seconds": 600 + } + ] +} \ No newline at end of file diff --git a/catalogs/j1939-pgns.json b/catalogs/j1939-pgns.json new file mode 100644 index 00000000..a9650568 --- /dev/null +++ b/catalogs/j1939-pgns.json @@ -0,0 +1,511 @@ +{ + "schema_version": 1, + "spec": "SAE J1939-71/73/75/81/21", + "description": "Named PGN catalog. Add OEM-specific or trade-secret PGNs here without recompiling.", + "fields": { + "pgn": "Parameter Group Number (decimal or 0xHEX)", + "mnemonic": "ASCII short name", + "name": "Human-readable name", + "length_bytes": "Frame length; 0 = variable / multi-packet", + "default_priority": "0..7 (0 highest)", + "tx_rate_ms": "0 = on request only; -1 = on change only", + "spec_section": "Source citation, e.g. 'J1939-71 §5.3.1'" + }, + "entries": [ + { + "pgn": "0xF004", + "mnemonic": "EEC1", + "name": "Electronic Engine Controller 1", + "length_bytes": 8, + "default_priority": 3, + "tx_rate_ms": 20, + "spec_section": "J1939-71 §5.3.1" + }, + { + "pgn": "0xF003", + "mnemonic": "EEC2", + "name": "Electronic Engine Controller 2", + "length_bytes": 8, + "default_priority": 3, + "tx_rate_ms": 50, + "spec_section": "J1939-71 §5.3.2" + }, + { + "pgn": "0xFEDF", + "mnemonic": "EEC3", + "name": "Electronic Engine Controller 3", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 250, + "spec_section": "J1939-71 §5.3.3" + }, + { + "pgn": "0xFE9E", + "mnemonic": "EEC4", + "name": "Electronic Engine Controller 4", + "length_bytes": 8, + "default_priority": 3, + "tx_rate_ms": 100, + "spec_section": "J1939-71 §5.3.4" + }, + { + "pgn": "0xFEEE", + "mnemonic": "ET1", + "name": "Engine Temperature 1", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.3.6" + }, + { + "pgn": "0xFEEF", + "mnemonic": "EFL/P1", + "name": "Engine Fluid Level/Pressure 1", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 500, + "spec_section": "J1939-71 §5.3.7" + }, + { + "pgn": "0xFEF2", + "mnemonic": "LFE1", + "name": "Fuel Economy (Liquid)", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 100, + "spec_section": "J1939-71 §5.3.8" + }, + { + "pgn": "0xFEF1", + "mnemonic": "CCVS", + "name": "Cruise Control / Vehicle Speed", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 100, + "spec_section": "J1939-71 §5.3.9" + }, + { + "pgn": "0xFEF5", + "mnemonic": "AMB", + "name": "Ambient Conditions", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.3.10" + }, + { + "pgn": "0xFEF6", + "mnemonic": "IC1", + "name": "Inlet/Exhaust Conditions 1", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 500, + "spec_section": "J1939-71 §5.3.11" + }, + { + "pgn": "0xFEF7", + "mnemonic": "VEP1", + "name": "Vehicle Electrical Power 1", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.3.12" + }, + { + "pgn": "0xFEF8", + "mnemonic": "TRF1", + "name": "Transmission Fluids 1", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.3.13" + }, + { + "pgn": "0xFEFC", + "mnemonic": "DD", + "name": "Dash Display", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.3.14" + }, + { + "pgn": "0xFEFE", + "mnemonic": "AAI", + "name": "Auxiliary Analog Information", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.3.15" + }, + { + "pgn": "0xFEFF", + "mnemonic": "WFI", + "name": "Water in Fuel Indicator", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.3.16" + }, + { + "pgn": "0xFECA", + "mnemonic": "DM1", + "name": "Active Diagnostic Trouble Codes", + "length_bytes": 0, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-73 §5.7.1" + }, + { + "pgn": "0xFECB", + "mnemonic": "DM2", + "name": "Previously Active DTCs", + "length_bytes": 0, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-73 §5.7.2" + }, + { + "pgn": "0xFECC", + "mnemonic": "DM3", + "name": "Diagnostic Data Clear (Previously Active)", + "length_bytes": 0, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-73 §5.7.3" + }, + { + "pgn": "0xFECD", + "mnemonic": "DM4", + "name": "Freeze Frame Parameters", + "length_bytes": 0, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-73 §5.7.4" + }, + { + "pgn": "0xFECE", + "mnemonic": "DM5", + "name": "Diagnostic Readiness 1", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-73 §5.7.5" + }, + { + "pgn": "0xFED3", + "mnemonic": "DM11", + "name": "Diagnostic Data Clear (Active)", + "length_bytes": 0, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-73 §5.7.11" + }, + { + "pgn": "0xFED5", + "mnemonic": "DM12", + "name": "Emission-Related Active DTCs", + "length_bytes": 0, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-73 §5.7.12" + }, + { + "pgn": "0xFECF", + "mnemonic": "DM6", + "name": "Emission-Related Pending DTCs", + "length_bytes": 0, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-73 §5.7.6" + }, + { + "pgn": "0xFE2A", + "mnemonic": "DM7", + "name": "Test Results", + "length_bytes": 0, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-73 §5.7.7" + }, + { + "pgn": "0xFE2B", + "mnemonic": "DM8", + "name": "Test Results — broadcast", + "length_bytes": 0, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-73 §5.7.8" + }, + { + "pgn": "0xFE2C", + "mnemonic": "DM10", + "name": "Inactive DTCs Selected", + "length_bytes": 0, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-73 §5.7.10" + }, + { + "pgn": "0xFDB0", + "mnemonic": "DM23", + "name": "Emission-Related Previously Active DTCs", + "length_bytes": 0, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-73 §5.7.23" + }, + { + "pgn": "0xFE6F", + "mnemonic": "DM26", + "name": "Diagnostic Readiness 3", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-73 §5.7.26" + }, + { + "pgn": "0xFEAE", + "mnemonic": "AIR1", + "name": "Air Supply Pressure", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.4.1" + }, + { + "pgn": "0xF001", + "mnemonic": "EBC1", + "name": "Electronic Brake Controller 1", + "length_bytes": 8, + "default_priority": 3, + "tx_rate_ms": 100, + "spec_section": "J1939-71 §5.4.2" + }, + { + "pgn": "0xFEC1", + "mnemonic": "HRVD", + "name": "High Resolution Vehicle Distance", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 250, + "spec_section": "J1939-71 §5.4.4" + }, + { + "pgn": "0xFEC4", + "mnemonic": "EBS5", + "name": "Electronic Brake Stability", + "length_bytes": 8, + "default_priority": 3, + "tx_rate_ms": 20, + "spec_section": "J1939-71 §5.4.5" + }, + { + "pgn": "0xF002", + "mnemonic": "ETC1", + "name": "Electronic Transmission Controller 1", + "length_bytes": 8, + "default_priority": 3, + "tx_rate_ms": 10, + "spec_section": "J1939-71 §5.5.1" + }, + { + "pgn": "0xF005", + "mnemonic": "ETC2", + "name": "Electronic Transmission Controller 2", + "length_bytes": 8, + "default_priority": 3, + "tx_rate_ms": 100, + "spec_section": "J1939-71 §5.5.2" + }, + { + "pgn": "0xFFEC", + "mnemonic": "ETC3", + "name": "Electronic Transmission Controller 3", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 250, + "spec_section": "J1939-71 §5.5.3" + }, + { + "pgn": "0xFF00", + "mnemonic": "ETC7", + "name": "Electronic Transmission Controller 7", + "length_bytes": 8, + "default_priority": 3, + "tx_rate_ms": 100, + "spec_section": "J1939-71 §5.5.7" + }, + { + "pgn": "0xFEF0", + "mnemonic": "PTO", + "name": "Power Takeoff Information", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 100, + "spec_section": "J1939-71 §5.6.1" + }, + { + "pgn": "0xFEF3", + "mnemonic": "VP", + "name": "Vehicle Position", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 5000, + "spec_section": "J1939-71 §5.6.2" + }, + { + "pgn": "0xFEE9", + "mnemonic": "TIME", + "name": "Time / Date", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.6.4" + }, + { + "pgn": "0xFEEA", + "mnemonic": "VW", + "name": "Vehicle Weight", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 500, + "spec_section": "J1939-71 §5.6.5" + }, + { + "pgn": "0xFEEC", + "mnemonic": "VI", + "name": "Vehicle Identification (VIN)", + "length_bytes": 0, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-71 §5.6.6" + }, + { + "pgn": "0xFEEB", + "mnemonic": "CI", + "name": "Component Identification", + "length_bytes": 0, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-71 §5.6.7" + }, + { + "pgn": "0xFEE5", + "mnemonic": "EH", + "name": "Engine Hours / Revolutions", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.6.10" + }, + { + "pgn": "0xFE56", + "mnemonic": "AT1IG1", + "name": "After-treatment 1 Diesel Exhaust Fluid Tank 1", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.7.1" + }, + { + "pgn": "0xFD7C", + "mnemonic": "AT1S", + "name": "After-treatment 1 Status (DPF/SCR)", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.7.2" + }, + { + "pgn": "0xFD7D", + "mnemonic": "DPFC1", + "name": "Diesel Particulate Filter Control 1", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.7.3" + }, + { + "pgn": "0xFE57", + "mnemonic": "AT1IMG1", + "name": "After-treatment 1 DEF Quality", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.7.4" + }, + { + "pgn": "0xFE5B", + "mnemonic": "AT1OG1", + "name": "After-treatment 1 Outlet Gas", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-71 §5.7.5" + }, + { + "pgn": "0xEE00", + "mnemonic": "AC", + "name": "Address Claimed / Cannot Claim", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 0, + "spec_section": "J1939-81 §4.2" + }, + { + "pgn": "0xEC00", + "mnemonic": "TP.CM", + "name": "Transport Protocol Connection Management", + "length_bytes": 8, + "default_priority": 7, + "tx_rate_ms": 0, + "spec_section": "J1939-21 §5.10.1" + }, + { + "pgn": "0xEB00", + "mnemonic": "TP.DT", + "name": "Transport Protocol Data Transfer", + "length_bytes": 8, + "default_priority": 7, + "tx_rate_ms": 0, + "spec_section": "J1939-21 §5.10.2" + }, + { + "pgn": "0xFFC9", + "mnemonic": "GG", + "name": "Genset Group", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-75 §6.1" + }, + { + "pgn": "0xFFC8", + "mnemonic": "GAP", + "name": "Genset Average Power", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-75 §6.2" + }, + { + "pgn": "0xFFC7", + "mnemonic": "GTH", + "name": "Genset Total Hours", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-75 §6.3" + }, + { + "pgn": "0xFFC6", + "mnemonic": "GTHA", + "name": "Genset Total Hours — Active", + "length_bytes": 8, + "default_priority": 6, + "tx_rate_ms": 1000, + "spec_section": "J1939-75 §6.4" + } + ] +} \ No newline at end of file diff --git a/catalogs/key-platforms-ford.json b/catalogs/key-platforms-ford.json new file mode 100644 index 00000000..2f48a248 --- /dev/null +++ b/catalogs/key-platforms-ford.json @@ -0,0 +1,47 @@ +{ + "schema_version": 1, + "description": "Ford PATS chassis-code applicability table.", + "access_levels": [ + "open", + "pin_required", + "gateway_locked" + ], + "entries": [ + { + "chassis_key": "p552", + "display_name": "Ford F-150 P552", + "access": "open", + "notes": "Open via OBD; well-documented 2-key timing dance." + }, + { + "chassis_key": "cd391", + "display_name": "Ford Fusion CD391", + "access": "open", + "notes": "Open via OBD; up to 8 keys." + }, + { + "chassis_key": "c520", + "display_name": "Ford Focus C520", + "access": "open", + "notes": "Open via OBD; PATS reset documented in FORScan." + }, + { + "chassis_key": "p702", + "display_name": "Ford Ranger P702", + "access": "pin_required", + "notes": "Outgoing-key PIN required to add new key." + }, + { + "chassis_key": "cd542", + "display_name": "Ford Mustang Mach-E CD542", + "access": "gateway_locked", + "notes": "Gateway-protected; requires Ford IDS or licensed FDRS access." + }, + { + "chassis_key": "p708", + "display_name": "Ford F-150 Lightning P708", + "access": "gateway_locked", + "notes": "Gateway-protected; requires FDRS." + } + ] +} \ No newline at end of file diff --git a/catalogs/key-platforms-hmg.json b/catalogs/key-platforms-hmg.json new file mode 100644 index 00000000..22edf20b --- /dev/null +++ b/catalogs/key-platforms-hmg.json @@ -0,0 +1,47 @@ +{ + "schema_version": 1, + "description": "Hyundai/Kia/Genesis smart-key platform applicability table.", + "access_levels": [ + "open_with_pin", + "gateway_locked_post_my2020", + "certificate_required" + ], + "entries": [ + { + "platform_key": "rb", + "display_name": "Hyundai i20 RB (pre-MY2018)", + "access": "open_with_pin", + "notes": "Open with 4-digit PIN from dealer label." + }, + { + "platform_key": "ld", + "display_name": "Hyundai Elantra LD", + "access": "open_with_pin", + "notes": "4-digit PIN procedure documented in GDS." + }, + { + "platform_key": "jf", + "display_name": "Hyundai Sonata JF", + "access": "open_with_pin", + "notes": "6-digit PIN; SMK module accepts up to 4 keys." + }, + { + "platform_key": "qs", + "display_name": "Kia Stonic QS", + "access": "open_with_pin", + "notes": "KDS PIN procedure; up to 4 smart keys." + }, + { + "platform_key": "ev_e_gmp", + "display_name": "HMG E-GMP (post-MY2021)", + "access": "gateway_locked_post_my2020", + "notes": "Gateway-protected; smart-key registration locked behind dealer SST tool." + }, + { + "platform_key": "genesis_g80", + "display_name": "Genesis G80 (RG3)", + "access": "certificate_required", + "notes": "Requires Genesis-only certificate; out of scope for OBD." + } + ] +} \ No newline at end of file diff --git a/catalogs/key-platforms-toyota.json b/catalogs/key-platforms-toyota.json new file mode 100644 index 00000000..6abd3eaa --- /dev/null +++ b/catalogs/key-platforms-toyota.json @@ -0,0 +1,41 @@ +{ + "schema_version": 1, + "description": "Toyota/Lexus chassis-code smart-key applicability table.", + "access_levels": [ + "master_key", + "pin", + "certificate_required" + ], + "entries": [ + { + "chassis_key": "zre182", + "display_name": "Toyota Auris ZRE182", + "access": "master_key", + "notes": "Master-key timing dance documented; smart key adds via OBD." + }, + { + "chassis_key": "asv50", + "display_name": "Toyota Camry ASV50", + "access": "master_key", + "notes": "Master-key procedure; up to 6 keys." + }, + { + "chassis_key": "agz10", + "display_name": "Lexus NX AGZ10", + "access": "pin", + "notes": "PIN-required smart-key registration via Techstream." + }, + { + "chassis_key": "mxua70", + "display_name": "Toyota RAV4 MXUA70", + "access": "pin", + "notes": "PIN required from Toyota dealer portal." + }, + { + "chassis_key": "mxpa10", + "display_name": "Toyota Yaris MXPA10", + "access": "certificate_required", + "notes": "Certificate-locked Techstream after MY2021." + } + ] +} \ No newline at end of file diff --git a/catalogs/mode06-obdmids.json b/catalogs/mode06-obdmids.json new file mode 100644 index 00000000..4a668ab3 --- /dev/null +++ b/catalogs/mode06-obdmids.json @@ -0,0 +1,143 @@ +{ + "schema_version": 1, + "spec": "ISO 15031-5 Annex B OBDMID list", + "description": "Mode 06 OBDMID names. Add OEM-specific OBDMIDs without recompiling.", + "entries": [ + { + "obdmid": "0x01", + "name": "O2 Sensor Monitor Bank 1 Sensor 1" + }, + { + "obdmid": "0x02", + "name": "O2 Sensor Monitor Bank 1 Sensor 2" + }, + { + "obdmid": "0x03", + "name": "O2 Sensor Monitor Bank 1 Sensor 3" + }, + { + "obdmid": "0x04", + "name": "O2 Sensor Monitor Bank 1 Sensor 4" + }, + { + "obdmid": "0x05", + "name": "O2 Sensor Monitor Bank 2 Sensor 1" + }, + { + "obdmid": "0x06", + "name": "O2 Sensor Monitor Bank 2 Sensor 2" + }, + { + "obdmid": "0x07", + "name": "O2 Sensor Monitor Bank 2 Sensor 3" + }, + { + "obdmid": "0x08", + "name": "O2 Sensor Monitor Bank 2 Sensor 4" + }, + { + "obdmid": "0x21", + "name": "Catalyst Monitor Bank 1" + }, + { + "obdmid": "0x22", + "name": "Catalyst Monitor Bank 2" + }, + { + "obdmid": "0x31", + "name": "EGR Monitor" + }, + { + "obdmid": "0x32", + "name": "VVT Monitor" + }, + { + "obdmid": "0x39", + "name": "EVAP Monitor (Cap off)" + }, + { + "obdmid": "0x3A", + "name": "EVAP Monitor (0.040)" + }, + { + "obdmid": "0x3B", + "name": "EVAP Monitor (0.020)" + }, + { + "obdmid": "0x41", + "name": "Oxygen Sensor Heater Monitor Bank 1 Sensor 1" + }, + { + "obdmid": "0x42", + "name": "Oxygen Sensor Heater Monitor Bank 1 Sensor 2" + }, + { + "obdmid": "0x43", + "name": "Oxygen Sensor Heater Monitor Bank 2 Sensor 1" + }, + { + "obdmid": "0x44", + "name": "Oxygen Sensor Heater Monitor Bank 2 Sensor 2" + }, + { + "obdmid": "0x61", + "name": "Misfire Monitor — General" + }, + { + "obdmid": "0x71", + "name": "Misfire Cylinder 1" + }, + { + "obdmid": "0x72", + "name": "Misfire Cylinder 2" + }, + { + "obdmid": "0x73", + "name": "Misfire Cylinder 3" + }, + { + "obdmid": "0x74", + "name": "Misfire Cylinder 4" + }, + { + "obdmid": "0x75", + "name": "Misfire Cylinder 5" + }, + { + "obdmid": "0x76", + "name": "Misfire Cylinder 6" + }, + { + "obdmid": "0x77", + "name": "Misfire Cylinder 7" + }, + { + "obdmid": "0x78", + "name": "Misfire Cylinder 8" + }, + { + "obdmid": "0xA1", + "name": "PM Filter Monitor Bank 1" + }, + { + "obdmid": "0xA2", + "name": "PM Filter Monitor Bank 2" + }, + { + "obdmid": "0xB1", + "name": "NMHC Catalyst Bank 1" + }, + { + "obdmid": "0xB2", + "name": "NMHC Catalyst Bank 2" + }, + { + "obdmid": "0xC1", + "name": "NOx Adsorber Bank 1" + }, + { + "obdmid": "0xC2", + "name": "NOx Adsorber Bank 2" + } + ] +} \ No newline at end of file diff --git a/catalogs/mode06-tids.json b/catalogs/mode06-tids.json new file mode 100644 index 00000000..bf06ea80 --- /dev/null +++ b/catalogs/mode06-tids.json @@ -0,0 +1,95 @@ +{ + "schema_version": 1, + "spec": "ISO 15031-5 Annex B Test Identifier list", + "description": "Mode 06 Test Identifier names. Add OEM-specific TIDs without recompiling.", + "entries": [ + { + "tid": "0x01", + "name": "Rich-to-lean sensor threshold voltage" + }, + { + "tid": "0x02", + "name": "Lean-to-rich sensor threshold voltage" + }, + { + "tid": "0x03", + "name": "Low sensor voltage for switch time calculation" + }, + { + "tid": "0x04", + "name": "High sensor voltage for switch time calculation" + }, + { + "tid": "0x05", + "name": "Rich-to-lean switch time" + }, + { + "tid": "0x06", + "name": "Lean-to-rich switch time" + }, + { + "tid": "0x07", + "name": "Minimum sensor voltage for test" + }, + { + "tid": "0x08", + "name": "Maximum sensor voltage for test" + }, + { + "tid": "0x09", + "name": "Time between sensor transitions" + }, + { + "tid": "0x0A", + "name": "Sensor period" + }, + { + "tid": "0x0B", + "name": "EWMA misfire counts for last ten driving cycles" + }, + { + "tid": "0x0C", + "name": "Misfire counts for last/current driving cycle" + }, + { + "tid": "0x81", + "name": "Catalyst monitor — bank 1, sensor 1 (test 1)" + }, + { + "tid": "0x82", + "name": "Catalyst monitor — bank 1, sensor 2 (test 2)" + }, + { + "tid": "0x83", + "name": "Catalyst monitor — bank 2, sensor 1" + }, + { + "tid": "0x84", + "name": "Catalyst monitor — bank 2, sensor 2" + }, + { + "tid": "0x85", + "name": "EVAP monitor (0.040)" + }, + { + "tid": "0x86", + "name": "EVAP monitor (0.020)" + }, + { + "tid": "0x87", + "name": "EVAP monitor (cap off)" + }, + { + "tid": "0xA1", + "name": "EGR monitor" + }, + { + "tid": "0xA2", + "name": "PCV monitor" + }, + { + "tid": "0xB1", + "name": "Cold-start emission reduction monitor" + } + ] +} \ No newline at end of file diff --git a/catalogs/mode06-units.json b/catalogs/mode06-units.json new file mode 100644 index 00000000..5efe8f9f --- /dev/null +++ b/catalogs/mode06-units.json @@ -0,0 +1,193 @@ +{ + "schema_version": 1, + "spec": "ISO 15031-5 Annex B.3 Units & Scaling table", + "description": "Mode 06 Unit-and-Scaling identifiers. Each entry combines a scale factor and a unit string.", + "entries": [ + { + "ucsid": "0x01", + "scale": 1.0, + "unit": "count", + "description": "Raw count" + }, + { + "ucsid": "0x02", + "scale": 0.1, + "unit": "count", + "description": "Count, 0.1 resolution" + }, + { + "ucsid": "0x03", + "scale": 0.01, + "unit": "count", + "description": "Count, 0.01 resolution" + }, + { + "ucsid": "0x04", + "scale": 0.001, + "unit": "count", + "description": "Count, 0.001 resolution" + }, + { + "ucsid": "0x05", + "scale": 3.05e-05, + "unit": "count", + "description": "Count, 1/32768" + }, + { + "ucsid": "0x06", + "scale": 0.000305, + "unit": "count", + "description": "Count, 1/3276.8" + }, + { + "ucsid": "0x07", + "scale": 0.25, + "unit": "rpm", + "description": "Engine speed" + }, + { + "ucsid": "0x08", + "scale": 0.01, + "unit": "km/h", + "description": "Vehicle speed" + }, + { + "ucsid": "0x09", + "scale": 1.0, + "unit": "km/h", + "description": "Vehicle speed" + }, + { + "ucsid": "0x0A", + "scale": 0.122, + "unit": "mV", + "description": "Voltage" + }, + { + "ucsid": "0x0B", + "scale": 0.001, + "unit": "V", + "description": "Voltage" + }, + { + "ucsid": "0x0C", + "scale": 0.01, + "unit": "V", + "description": "Voltage" + }, + { + "ucsid": "0x0D", + "scale": 1.0, + "unit": "mA", + "description": "Current" + }, + { + "ucsid": "0x10", + "scale": 1.0, + "unit": "ms", + "description": "Time period" + }, + { + "ucsid": "0x11", + "scale": 100.0, + "unit": "ms", + "description": "Long time period" + }, + { + "ucsid": "0x12", + "scale": 1.0, + "unit": "s", + "description": "Time" + }, + { + "ucsid": "0x14", + "scale": 0.000305, + "unit": "kPa", + "description": "Gauge pressure" + }, + { + "ucsid": "0x15", + "scale": 0.001, + "unit": "kPa", + "description": "Air pressure" + }, + { + "ucsid": "0x16", + "scale": 0.01, + "unit": "kPa", + "description": "Pressure" + }, + { + "ucsid": "0x17", + "scale": 0.1, + "unit": "kPa", + "description": "Pressure" + }, + { + "ucsid": "0x18", + "scale": 1.0, + "unit": "kPa", + "description": "Pressure" + }, + { + "ucsid": "0x19", + "scale": 10.0, + "unit": "kPa", + "description": "Pressure" + }, + { + "ucsid": "0x20", + "scale": 0.01, + "unit": "%", + "description": "Percent" + }, + { + "ucsid": "0x21", + "scale": 0.001525, + "unit": "%", + "description": "%, 0..100 over uint16" + }, + { + "ucsid": "0x22", + "scale": 3.05e-05, + "unit": "lambda", + "description": "Equivalence ratio" + }, + { + "ucsid": "0x24", + "scale": 1.0, + "unit": "°C", + "description": "Temperature" + }, + { + "ucsid": "0x25", + "scale": 0.1, + "unit": "°C", + "description": "Temperature, 0.1 res." + }, + { + "ucsid": "0x26", + "scale": 0.01, + "unit": "°C", + "description": "Temperature, 0.01 res." + }, + { + "ucsid": "0x30", + "scale": 3.05e-05, + "unit": "g/s", + "description": "Mass flow rate" + }, + { + "ucsid": "0x31", + "scale": 0.000305, + "unit": "g/s", + "description": "Mass flow rate" + }, + { + "ucsid": "0x32", + "scale": 0.01, + "unit": "g/s", + "description": "Mass flow rate" + } + ] +} \ No newline at end of file diff --git a/catalogs/radiocode-becker4.json b/catalogs/radiocode-becker4.json new file mode 100644 index 00000000..81c6f80f --- /dev/null +++ b/catalogs/radiocode-becker4.json @@ -0,0 +1 @@ +{"schema_version": 1, "brand": "Becker", "format": "4-digit", "description": "Becker 4-digit radio code lookup. Index = serial (0..9999); value = code.", "count": 10000, "codes": ["1010", "1108", "1016", "1024", "1032", "1050", "1048", "1056", "1064", "1072", "1090", "1098", "1096", "0104", "0212", "0130", "0128", "0136", "0154", "0152", "0170", "0168", "0176", "0184", "0192", "0210", "0208", "0216", "0324", "0242", "0250", "0248", "0256", "0264", "0282", "0290", "0298", "0296", "0304", "0312", "0320", "0328", "0436", "0354", "0352", "0360", "0368", "0376", "0384", "0392", "0410", "0408", "0416", "0424", "0432", "0540", "0548", "0456", "0474", "0472", "0490", "0498", "0496", "0504", "4096", "4104", "4212", "4130", "4128", "4136", "4154", "4152", "4170", "4168", "4176", "4184", "4192", "4210", "4208", "4216", "4324", "4242", "4250", "4248", "4256", "4264", "4282", "4290", "4298", "4296", "4304", "4312", "4320", "4328", "4436", "4354", "4352", "4360", "4368", "4376", "4384", "4392", "5410", "5408", "5416", "5424", "5432", "5540", "5548", "5456", "5474", "5472", "5490", "5498", "5496", "4504", "4512", "4530", "4528", "4536", "4654", "4652", "4560", "4568", "4576", "4584", "4592", "4610", "8192", "8210", "8208", "8216", "8324", "8242", "8250", "8248", "8256", "8264", "8282", "8290", "8298", "8296", "8304", "8312", "8320", "8328", "8436", "8354", "8352", "8360", "8368", "8376", "8384", "8392", "8410", "8408", "8416", "8424", "8432", "8540", "8548", "8456", "8474", "8472", "8490", "8498", "8496", "8504", "8512", "8530", "8528", "8536", "8654", "8652", "8560", "8568", "8576", "8584", "8592", "8610", "8608", "8616", "8624", "8642", "8640", "8648", "8656", "8764", "8672", "8690", "8698", "8696", "3289", "3297", "2305", "2313", "2321", "2329", "2437", "2345", "2353", "2361", "2369", "2387", "2385", "2393", "2401", "2409", "2417", "2425", "2443", "2541", "2549", "2457", "2465", "2473", "2481", "2489", "2497", "2505", "2513", "2521", "2529", "2537", "2545", "2653", "2561", "2569", "2587", "2585", "2593", "2601", "2609", "2617", "2625", "2643", "2641", "2649", "2657", "2765", "2673", "2681", "2689", "2697", "2705", "2713", "2721", "2729", "2737", "2745", "2753", "2761", "2769", "2787", "2785", "2793", "6385", "6393", "6401", "6409", "6417", "6425", "6443", "6541", "6549", "6457", "6465", "6473", "6481", "6489", "6497", "6505", "6513", "6521", "6529", "6537", "6545", "6653", "6561", "6569", "6587", "6585", "6593", "7601", "7609", "7617", "7625", "7643", "7641", "7649", "7657", "7765", "7673", "7681", "7689", "7697", "6705", "6713", "6721", "6729", "6737", "6745", "6753", "6761", "6769", "6787", "6785", "6793", "6801", "6809", "6817", "6825", "6843", "6841", "6849", "6857", "6865", "6873", "6981", "6989", "0482", "0510", "0498", "0506", "0514", "0532", "0530", "0538", "0546", "0654", "0562", "0570", "0578", "0586", "0594", "0602", "0620", "0618", "0626", "0634", "0642", "0650", "0658", "0676", "0674", "0682", "0710", "0698", "0706", "0714", "0732", "0730", "0738", "0746", "0754", "0762", "0870", "0878", "0786", "0794", "0802", "0820", "0818", "0826", "0834", "0842", "0860", "0858", "0876", "0874", "0982", "0890", "0908", "0906", "0914", "0932", "0930", "0938", "0946", "0954", "0962", "0970", "0978", "0986", "4578", "4586", "4594", "4602", "4620", "4618", "4626", "4634", "4642", "4650", "4658", "4676", "4674", "4682", "4710", "4698", "4706", "4714", "4732", "4730", "4738", "4746", "4754", "4762", "4870", "4878", "4786", "4794", "4802", "4820", "4818", "4826", "4834", "4842", "4860", "4858", "4876", "4874", "4982", "4890", "4908", "4906", "4914", "4932", "4930", "4938", "4946", "4954", "4962", "4970", "4978", "4986", "5094", "5102", "5020", "5018", "5026", "5034", "5042", "5060", "5058", "5076", "5074", "5082", "8674", "8682", "8710", "8698", "8706", "8714", "8732", "8730", "8738", "8746", "8754", "8762", "8870", "8878", "8786", "8794", "9802", "9820", "9818", "9826", "9834", "9842", "9860", "9858", "9876", "9874", "9982", "9890", "9908", "8906", "8914", "8932", "8930", "8938", "8946", "8954", "8962", "8970", "8978", "8986", "9094", "9102", "9020", "9018", "9026", "9034", "9042", "9060", "9058", "9076", "9074", "9082", "9110", "9098", "9106", "9214", "9132", "9130", "9138", "9146", "9154", "9162", "9170", "9178", "1101", "1109", "1017", "1025", "1043", "1041", "1049", "1057", "1065", "1073", "1081", "1089", "1097", "0105", "0213", "0121", "0129", "0137", "0145", "0153", "0161", "0169", "0187", "0185", "0193", "0201", "0209", "0217", "0325", "0243", "0241", "0249", "0257", "0265", "0273", "0281", "0289", "0297", "0305", "0313", "0321", "0329", "0437", "0345", "0353", "0361", "0369", "0387", "0385", "0393", "0401", "0409", "0417", "0425", "0473", "0541", "0549", "0457", "0465", "0473", "0481", "0489", "0497", "0505", "4097", "4105", "4213", "4121", "4129", "4137", "4145", "4153", "4161", "4169", "4187", "4185", "4193", "4201", "4209", "4217", "4325", "4243", "4241", "4249", "4257", "4265", "4273", "4281", "4289", "4297", "4305", "4313", "4321", "4329", "4437", "4345", "4353", "4361", "4369", "4387", "4385", "4393", "5401", "5409", "5417", "5425", "5443", "5541", "5549", "5457", "5465", "5473", "5481", "5489", "5497", "4505", "4513", "4521", "4529", "4537", "4545", "4653", "4561", "4569", "4587", "4585", "4593", "4601", "8193", "8201", "8209", "8217", "8325", "8243", "8241", "8249", "8257", "8265", "8273", "8281", "8289", "8297", "8305", "8313", "8321", "8329", "8437", "8345", "8353", "8361", "8369", "8387", "8385", "8393", "8401", "8409", "8417", "8425", "8443", "8541", "8549", "8457", "8465", "8473", "8481", "8489", "8497", "8505", "8513", "8521", "8529", "8537", "8545", "8653", "8561", "8569", "8587", "8585", "8593", "8601", "8609", "8617", "8625", "8643", "8641", "8649", "8657", "8765", "8673", "8681", "8689", "8697", "3310", "3298", "2306", "2314", "2432", "2430", "2438", "2346", "2354", "2372", "2370", "2378", "2386", "2394", "2402", "2420", "2418", "2426", "2434", "2542", "2450", "2458", "2476", "2484", "2482", "2510", "2498", "2506", "2514", "2532", "2530", "2538", "2546", "2654", "2562", "2570", "2578", "2586", "2594", "2602", "2620", "2618", "2626", "2634", "2642", "2650", "2658", "2676", "2674", "2682", "2710", "2698", "2706", "2714", "2732", "2730", "2738", "2746", "2754", "2762", "2870", "2778", "2786", "2794", "6386", "6394", "6402", "6420", "6418", "6426", "6434", "6542", "6450", "6458", "6476", "6484", "6482", "6510", "6498", "6506", "6514", "6532", "6530", "6538", "6546", "6654", "6562", "6570", "6578", "6586", "6594", "7602", "7620", "7618", "7626", "7634", "7642", "7650", "7658", "7676", "7674", "7682", "7710", "7698", "6706", "6714", "6732", "6730", "6738", "6746", "6754", "6762", "6870", "6878", "6786", "6794", "6802", "6820", "6818", "6826", "6834", "6842", "6860", "6858", "6876", "6874", "6982", "6890", "0483", "0491", "0509", "0507", "0515", "0523", "0541", "0539", "0547", "0565", "0563", "0571", "0579", "0587", "0595", "0603", "0621", "0619", "0627", "0635", "0643", "0651", "0659", "0767", "0675", "0683", "0691", "0709", "0707", "0715", "0723", "0731", "0739", "0747", "0765", "0763", "0871", "0879", "0787", "0795", "0803", "0821", "0819", "0827", "0835", "0843", "0851", "0859", "0867", "0875", "0983", "0891", "0909", "0907", "0915", "0923", "0941", "0939", "0947", "0965", "0963", "0981", "0979", "0987", "4579", "4587", "4595", "4603", "4621", "4619", "4627", "4635", "4643", "4651", "4659", "4767", "4675", "4683", "4691", "4709", "4707", "4715", "4723", "4731", "4739", "4747", "4765", "4763", "4871", "4879", "4787", "4795", "4803", "4821", "4819", "4827", "4835", "4843", "4851", "4859", "4867", "4875", "4983", "4891", "4909", "4907", "4915", "4923", "4941", "4939", "4947", "4965", "4963", "4981", "4979", "4987", "5095", "5103", "5021", "5019", "5027", "5035", "5043", "5051", "5059", "5067", "5075", "5083", "8675", "8683", "8691", "8709", "8707", "8715", "8723", "8731", "8739", "8747", "8765", "8763", "8871", "8879", "8787", "8795", "9803", "9821", "9819", "9827", "9835", "9843", "9851", "9859", "9867", "9875", "9983", "9891", "9909", "8907", "8915", "8923", "8941", "8939", "8947", "8965", "8963", "8981", "8979", "8987", "9095", "9103", "9021", "9019", "9027", "9035", "9043", "9051", "9059", "9067", "9075", "9083", "9091", "9109", "9107", "9215", "9123", "9141", "9139", "9147", "9165", "9163", "9181", "9179", "1102", "1020", "1018", "1026", "1034", "1042", "1060", "1058", "1076", "1074", "1082", "1110", "1098", "0106", "0214", "0132", "0130", "0138", "0146", "0154", "0162", "0170", "0178", "0186", "0194", "0202", "0320", "0218", "0326", "0234", "0242", "0260", "0258", "0276", "0274", "0282", "0310", "0298", "0306", "0314", "0432", "0430", "0438", "0346", "0354", "0372", "0370", "0378", "0386", "0394", "0402", "0420", "0418", "0426", "0434", "0542", "0450", "0458", "0476", "0484", "0482", "0510", "0498", "0506", "4098", "4106", "4214", "4132", "4130", "4138", "4146", "4154", "4162", "4170", "4178", "4186", "4194", "4202", "4320", "4218", "4326", "4234", "4242", "4260", "4258", "4276", "4274", "4282", "4310", "4298", "4306", "4314", "4432", "4430", "4438", "4346", "4354", "4372", "4370", "4378", "4386", "4394", "5402", "5420", "5418", "5426", "5434", "5542", "5450", "5458", "5476", "5484", "5482", "5510", "5498", "4506", "4514", "4532", "4530", "4538", "4546", "4654", "4562", "4570", "4578", "4586", "4594", "4602", "8194", "8202", "8320", "8218", "8326", "8234", "8242", "8260", "8258", "8276", "8274", "8282", "8310", "8298", "8306", "8314", "8432", "8430", "8438", "8346", "8354", "8372", "8370", "8378", "8386", "8394", "8402", "8420", "8418", "8426", "8434", "8542", "8450", "8458", "8476", "8484", "8482", "8510", "8498", "8506", "8514", "8532", "8530", "8538", "8546", "8654", "8562", "8570", "8578", "8586", "8594", "8602", "8620", "8618", "8626", "8634", "8642", "8650", "8658", "8676", "8674", "8682", "8710", "8698", "3291", "3309", "2307", "2315", "2323", "2431", "2439", "2347", "2365", "2363", "2371", "2379", "2387", "2395", "2403", "2421", "2419", "2427", "2435", "2543", "2451", "2459", "2467", "2475", "2483", "2491", "2509", "2507", "2515", "2523", "2541", "2539", "2547", "2565", "2563", "2571", "2579", "2587", "2595", "2603", "2621", "2619", "2627", "2635", "2643", "2651", "2659", "2767", "2675", "2683", "2691", "2709", "2707", "2715", "2723", "2731", "2739", "2747", "2765", "2763", "2871", "2879", "2787", "2795", "6387", "6395", "6403", "6421", "6419", "6427", "6435", "6543", "6451", "6459", "6467", "6475", "6483", "6491", "6509", "6507", "6515", "6523", "6541", "6539", "6547", "6565", "6563", "6571", "6579", "6587", "6595", "7603", "7621", "7619", "7627", "7635", "7643", "7651", "7659", "7767", "7675", "7683", "7691", "7709", "6707", "6715", "6723", "6731", "6739", "6747", "6765", "6763", "6871", "6879", "6787", "6795", "6803", "6821", "6819", "6827", "6835", "6843", "6851", "6859", "6867", "6875", "6983", "6891", "0484", "0492", "0510", "0508", "0516", "0524", "0532", "0540", "0548", "0656", "0574", "0572", "0590", "0598", "0596", "0604", "0612", "0620", "0628", "0636", "0654", "0652", "0760", "0768", "0686", "0684", "0692", "0710", "0708", "0716", "0724", "0732", "0740", "0748", "0756", "0764", "0872", "0790", "0798", "0796", "0804", "0812", "0830", "0828", "0836", "0854", "0852", "0870", "0868", "0876", "0984", "0892", "0910", "0908", "0916", "0924", "0932", "0950", "0948", "0956", "0964", "0972", "0980", "1098", "4590", "4598", "4596", "4604", "4612", "4620", "4628", "4636", "4654", "4652", "4760", "4768", "4686", "4684", "4692", "4710", "4708", "4716", "4724", "4732", "4740", "4748", "4756", "4764", "4872", "4790", "4798", "4796", "4804", "4812", "4830", "4828", "4836", "4854", "4852", "4870", "4868", "4876", "4984", "4892", "4910", "4908", "4916", "4924", "4932", "4950", "4948", "4956", "4964", "4972", "4980", "5098", "5096", "5104", "5012", "5030", "5028", "5036", "5054", "5052", "5070", "5068", "5076", "5084", "8686", "8684", "8692", "8710", "8708", "8716", "8724", "8732", "8740", "8748", "8756", "8764", "8872", "8790", "8798", "8796", "9804", "9812", "9830", "9828", "9836", "9854", "9852", "9870", "9868", "9876", "9984", "9892", "8910", "8908", "8916", "8924", "8932", "8950", "8948", "8956", "8964", "8972", "8980", "9098", "9096", "9104", "9012", "9030", "9028", "9036", "9054", "9052", "9070", "9068", "9076", "9084", "9092", "9210", "9108", "9216", "9124", "9132", "9150", "9148", "9156", "9164", "9172", "9190", "1103", "1021", "1019", "1027", "1035", "1043", "1051", "1059", "1067", "1075", "1083", "1091", "1109", "0107", "0215", "0123", "0141", "0139", "0147", "0165", "0163", "0181", "0179", "0187", "0195", "0203", "0221", "0219", "0327", "0235", "0243", "0251", "0259", "0267", "0275", "0283", "0291", "0309", "0307", "0315", "0323", "0431", "0439", "0347", "0365", "0363", "0371", "0379", "0387", "0395", "0403", "0421", "0419", "0427", "0435", "0543", "0451", "0459", "0467", "0475", "0483", "0491", "0509", "0507", "4109", "4107", "4215", "4123", "4141", "4139", "4147", "4165", "4163", "4181", "4179", "4187", "4195", "4203", "4221", "4219", "4327", "4235", "4243", "4251", "4259", "4267", "4275", "4283", "4291", "4309", "4307", "4315", "4323", "4431", "4439", "4347", "4365", "4363", "4371", "4379", "4387", "4395", "5403", "5421", "5419", "5427", "5435", "5543", "5451", "5459", "5467", "5475", "5483", "5491", "5509", "4507", "4515", "4523", "4541", "4539", "4547", "4565", "4563", "4571", "4579", "4587", "4595", "4603", "8195", "8203", "8221", "8219", "8327", "8235", "8243", "8251", "8259", "8267", "8275", "8283", "8291", "8309", "8307", "8315", "8323", "8431", "8439", "8347", "8365", "8363", "8371", "8379", "8387", "8395", "8403", "8421", "8419", "8427", "8435", "8543", "8451", "8459", "8467", "8475", "8483", "8491", "8509", "8507", "8515", "8523", "8541", "8539", "8547", "8565", "8563", "8571", "8579", "8587", "8595", "8603", "8621", "8619", "8627", "8635", "8643", "8651", "8659", "8767", "8675", "8683", "8691", "8709", "3292", "2310", "2308", "2316", "2324", "2432", "2350", "2348", "2356", "2364", "2372", "2390", "2398", "2396", "2404", "2412", "2430", "2428", "2436", "2454", "2452", "2460", "2468", "2476", "2484", "2492", "2510", "2508", "2516", "2524", "2532", "2540", "2548", "2656", "2574", "2572", "2590", "2598", "2596", "2604", "2612", "2620", "2628", "2636", "2654", "2652", "2760", "2768", "2686", "2684", "2692", "2710", "2708", "2716", "2724", "2732", "2740", "2748", "2756", "2764", "2872", "2790", "2798", "2796", "6398", "6396", "6404", "6412", "6430", "6428", "6436", "6454", "6452", "6460", "6468", "6476", "6484", "6492", "6510", "6508", "6516", "6524", "6532", "6540", "6548", "6656", "6574", "6572", "6590", "6598", "6596", "7604", "7612", "7620", "7628", "7636", "7654", "7652", "7760", "7768", "7686", "7684", "7692", "6710", "6708", "6716", "6724", "6732", "6740", "6748", "6756", "6764", "6872", "6790", "6798", "6796", "6804", "6812", "6830", "6828", "6836", "6854", "6852", "6870", "6868", "6876", "6984", "6892", "0485", "0493", "0501", "0509", "0517", "0525", "0543", "0541", "0549", "0657", "0565", "0573", "0581", "0589", "0597", "0605", "0613", "0621", "0629", "0637", "0645", "0653", "0761", "0769", "0687", "0685", "0693", "0701", "0709", "0717", "0725", "0743", "0741", "0749", "0757", "0765", "0873", "0781", "0789", "0797", "0805", "0813", "0821", "0829", "0837", "0845", "0853", "0861", "0869", "0887", "0985", "0893", "0901", "0909", "0917", "0925", "0943", "0941", "0949", "0957", "0965", "0973", "0981", "0989", "4581", "4589", "4597", "4605", "4613", "4621", "4629", "4637", "4645", "4653", "4761", "4769", "4687", "4685", "4693", "4701", "4709", "4717", "4725", "4743", "4741", "4749", "4757", "4765", "4873", "4781", "4789", "4797", "4805", "4813", "4821", "4829", "4837", "4845", "4853", "4861", "4869", "4887", "4985", "4893", "4901", "4909", "4917", "4925", "4943", "4941", "4949", "4957", "4965", "4973", "4981", "4989", "5097", "5105", "5013", "5021", "5029", "5037", "5045", "5053", "5061", "5069", "5087", "5085", "8687", "8685", "8693", "8701", "8709", "8717", "8725", "8743", "8741", "8749", "8757", "8765", "8873", "8781", "8789", "8797", "9805", "9813", "9821", "9829", "9837", "9845", "9853", "9861", "9869", "9887", "9985", "9893", "8901", "8909", "8917", "8925", "8943", "8941", "8949", "8957", "8965", "8973", "8981", "8989", "9097", "9105", "9013", "9021", "9029", "9037", "9045", "9053", "9061", "9069", "9087", "9085", "9093", "9101", "9109", "9217", "9125", "9143", "9141", "9149", "9157", "9165", "9173", "9181", "1104", "1012", "1030", "1028", "1036", "1054", "1052", "1070", "1068", "1076", "1084", "1092", "0210", "0108", "0216", "0124", "0132", "0150", "0148", "0156", "0164", "0172", "0190", "0198", "0196", "0204", "0212", "0320", "0328", "0236", "0254", "0252", "0260", "0268", "0276", "0284", "0292", "0310", "0308", "0316", "0324", "0432", "0350", "0348", "0356", "0364", "0372", "0390", "0398", "0396", "0404", "0412", "0430", "0428", "0436", "0454", "0452", "0460", "0468", "0476", "0484", "0492", "0510", "0508", "4210", "4108", "4216", "4124", "4132", "4150", "4148", "4156", "4164", "4172", "4190", "4198", "4196", "4204", "4212", "4320", "4328", "4236", "4254", "4252", "4260", "4268", "4276", "4284", "4292", "4310", "4308", "4316", "4324", "4432", "4350", "4348", "4356", "4364", "4372", "4390", "4398", "4396", "5404", "5412", "5430", "5428", "5436", "5454", "5452", "5460", "5468", "5476", "5484", "5492", "4510", "4508", "4516", "4524", "4532", "4540", "4548", "4656", "4574", "4572", "4590", "4598", "4596", "4604", "8196", "8204", "8212", "8320", "8328", "8236", "8254", "8252", "8260", "8268", "8276", "8284", "8292", "8310", "8308", "8316", "8324", "8432", "8350", "8348", "8356", "8364", "8372", "8390", "8398", "8396", "8404", "8412", "8430", "8428", "8436", "8454", "8452", "8460", "8468", "8476", "8484", "8492", "8510", "8508", "8516", "8524", "8532", "8540", "8548", "8656", "8574", "8572", "8590", "8598", "8596", "8604", "8612", "8620", "8628", "8636", "8654", "8652", "8760", "8768", "8686", "8684", "8692", "8710", "3293", "2301", "2309", "2317", "2325", "2343", "2341", "2349", "2357", "2365", "2383", "2381", "2389", "2397", "2405", "2413", "2421", "2429", "2437", "2545", "2453", "2461", "2469", "2487", "2485", "2493", "2501", "2509", "2517", "2525", "2543", "2541", "2549", "2657", "2565", "2573", "2581", "2589", "2597", "2605", "2613", "2621", "2629", "2637", "2645", "2653", "2761", "2769", "2687", "2685", "2693", "2701", "2709", "2717", "2725", "2743", "2741", "2749", "2757", "2765", "2873", "2781", "2789", "2797", "6389", "6397", "6405", "6413", "6421", "6429", "6437", "6545", "6453", "6461", "6469", "6487", "6485", "6493", "6501", "6509", "6517", "6525", "6543", "6541", "6549", "6657", "6565", "6573", "6581", "6589", "6597", "7605", "7613", "7621", "7629", "7637", "7645", "7653", "7761", "7769", "7687", "7685", "7693", "6701", "6709", "6717", "6725", "6743", "6741", "6749", "6757", "6765", "6873", "6781", "6789", "6797", "6805", "6813", "6821", "6829", "6837", "6845", "6853", "6861", "6869", "6887", "6985", "6893", "0486", "0494", "0502", "0510", "0518", "0526", "0534", "0542", "0650", "0658", "0576", "0574", "0582", "0590", "0598", "0606", "0614", "0632", "0630", "0638", "0646", "0764", "0762", "0670", "0678", "0686", "0694", "0702", "0710", "0718", "0726", "0734", "0742", "0750", "0758", "0876", "0874", "0782", "0790", "0798", "0806", "0814", "0832", "0840", "0838", "0846", "0854", "0862", "0980", "0878", "0986", "0894", "0902", "0910", "0918", "0926", "0934", "0942", "0950", "0958", "0976", "0974", "0982", "1090", "4582", "4590", "4598", "4606", "4614", "4632", "4630", "4638", "4646", "4764", "4762", "4670", "4678", "4686", "4694", "4702", "4710", "4718", "4726", "4734", "4742", "4750", "4758", "4876", "4874", "4782", "4790", "4798", "4806", "4814", "4832", "4840", "4838", "4846", "4854", "4862", "4980", "4878", "4986", "4894", "4902", "4910", "4918", "4926", "4934", "4942", "4950", "4958", "4976", "4974", "4982", "5090", "5098", "5106", "5014", "5032", "5040", "5038", "5046", "5054", "5062", "5080", "5078", "5086", "8678", "8686", "8694", "8702", "8710", "8718", "8726", "8734", "8742", "8750", "8758", "8876", "8874", "8782", "8790", "8798", "9806", "9814", "9832", "9840", "9838", "9846", "9854", "9862", "9980", "9878", "9986", "9894", "8902", "8910", "8918", "8926", "8934", "8942", "8950", "8958", "8976", "8974", "8982", "9090", "9098", "9106", "9014", "9032", "9040", "9038", "9046", "9054", "9062", "9080", "9078", "9086", "9094", "9102", "9210", "9218", "9126", "9134", "9142", "9150", "9158", "9176", "9174", "9182", "1105", "1013", "1021", "1029", "1037", "1045", "1053", "1061", "1069", "1087", "1085", "1093", "0101", "0109", "0217", "0125", "0143", "0141", "0149", "0157", "0165", "0173", "0181", "0189", "0197", "0205", "0213", "0321", "0329", "0237", "0245", "0253", "0261", "0269", "0287", "0285", "0293", "0301", "0309", "0317", "0325", "0343", "0341", "0349", "0357", "0365", "0383", "0381", "0389", "0397", "0405", "0413", "0421", "0429", "0437", "0545", "0453", "0461", "0469", "0487", "0485", "0493", "0501", "0509", "4101", "4109", "4217", "4125", "4143", "4141", "4149", "4157", "4165", "4173", "4181", "4189", "4197", "4205", "4213", "4321", "4329", "4237", "4245", "4253", "4261", "4269", "4287", "4285", "4293", "4301", "4309", "4317", "4325", "4343", "4341", "4349", "4357", "4365", "4383", "4381", "4389", "4397", "5405", "5413", "5421", "5429", "5437", "5545", "5453", "5461", "5469", "5487", "5485", "5493", "4501", "4509", "4517", "4525", "4543", "4541", "4549", "4657", "4565", "4573", "4581", "4589", "4597", "4605", "8197", "8205", "8213", "8321", "8329", "8237", "8245", "8253", "8261", "8269", "8287", "8285", "8293", "8301", "8309", "8317", "8325", "8343", "8341", "8349", "8357", "8365", "8383", "8381", "8389", "8397", "8405", "8413", "8421", "8429", "8437", "8545", "8453", "8461", "8469", "8487", "8485", "8493", "8501", "8509", "8517", "8525", "8543", "8541", "8549", "8657", "8565", "8573", "8581", "8589", "8597", "8605", "8613", "8621", "8629", "8637", "8645", "8653", "8761", "8769", "8687", "8685", "8693", "8701", "3294", "2302", "2310", "2318", "2326", "2434", "2342", "2350", "2358", "2376", "2374", "2382", "2390", "2398", "2406", "2414", "2432", "2540", "2438", "2546", "2464", "2462", "2470", "2478", "2486", "2494", "2502", "2510", "2518", "2526", "2534", "2542", "2650", "2658", "2576", "2574", "2582", "2590", "2598", "2606", "2614", "2632", "2630", "2638", "2646", "2764", "2762", "2670", "2678", "2686", "2694", "2702", "2710", "2718", "2726", "2734", "2742", "2750", "2758", "2876", "2874", "2782", "2790", "2798", "6390", "6398", "6406", "6414", "6432", "6540", "6438", "6546", "6464", "6462", "6470", "6478", "6486", "6494", "6502", "6510", "6518", "6526", "6534", "6542", "6650", "6658", "6576", "6574", "6582", "6590", "6598", "7606", "7614", "7632", "7630", "7638", "7646", "7764", "7762", "7670", "7678", "7686", "7694", "6702", "6710", "6718", "6726", "6734", "6742", "6750", "6758", "6876", "6874", "6782", "6790", "6798", "6806", "6814", "6832", "6840", "6838", "6846", "6854", "6862", "6980", "6878", "6986", "6894", "0487", "0495", "0503", "0521", "0519", "0527", "0535", "0543", "0651", "0659", "0567", "0585", "0583", "0601", "0609", "0607", "0615", "0623", "0631", "0639", "0647", "0665", "0763", "0671", "0679", "0687", "0695", "0703", "0721", "0719", "0727", "0735", "0743", "0751", "0759", "0767", "0875", "0783", "0801", "0809", "0807", "0815", "0823", "0831", "0839", "0847", "0865", "0863", "0871", "0879", "0987", "0895", "0903", "0921", "0919", "0927", "0935", "0943", "0961", "0959", "0967", "0975", "0983", "1091", "4583", "4601", "4609", "4607", "4615", "4623", "4631", "4639", "4647", "4665", "4763", "4671", "4679", "4687", "4695", "4703", "4721", "4719", "4727", "4735", "4743", "4751", "4759", "4767", "4875", "4783", "4801", "4809", "4807", "4815", "4823", "4831", "4839", "4847", "4865", "4863", "4871", "4879", "4987", "4895", "4903", "4921", "4919", "4927", "4935", "4943", "4961", "4959", "4967", "4975", "4983", "5091", "5109", "5107", "5015", "5023", "5031", "5039", "5047", "5065", "5063", "5071", "5079", "5087", "8679", "8687", "8695", "8703", "8721", "8719", "8727", "8735", "8743", "8751", "8759", "8767", "8875", "8783", "8801", "8809", "9807", "9815", "9823", "9831", "9839", "9847", "9865", "9863", "9871", "9879", "9987", "9895", "8903", "8921", "8919", "8927", "8935", "8943", "8961", "8959", "8967", "8975", "8983", "9091", "9109", "9107", "9015", "9023", "9031", "9039", "9047", "9065", "9063", "9071", "9079", "9087", "9095", "9103", "9121", "9219", "9127", "9135", "9143", "9161", "9159", "9167", "9175", "9183", "1106", "1014", "1032", "1040", "1038", "1046", "1054", "1062", "1080", "1078", "1086", "1094", "0102", "0210", "0218", "0126", "0134", "0142", "0150", "0158", "0176", "0174", "0182", "0190", "0198", "0206", "0214", "0232", "0230", "0238", "0246", "0254", "0272", "0270", "0278", "0286", "0294", "0302", "0310", "0318", "0326", "0434", "0342", "0350", "0358", "0376", "0374", "0382", "0390", "0398", "0406", "0414", "0432", "0540", "0438", "0546", "0464", "0462", "0470", "0478", "0486", "0494", "0502", "0510", "4102", "4210", "4218", "4126", "4134", "4142", "4150", "4158", "4176", "4174", "4182", "4190", "4198", "4206", "4214", "4232", "4230", "4238", "4246", "4254", "4272", "4270", "4278", "4286", "4294", "4302", "4310", "4318", "4326", "4434", "4342", "4350", "4358", "4376", "4374", "4382", "4390", "4398", "5406", "5414", "5432", "5540", "5438", "5546", "5464", "5462", "5470", "5478", "5486", "5494", "4502", "4510", "4518", "4526", "4534", "4542", "4650", "4658", "4576", "4574", "4582", "4590", "4598", "4606", "8198", "8206", "8214", "8232", "8230", "8238", "8246", "8254", "8272", "8270", "8278", "8286", "8294", "8302", "8310", "8318", "8326", "8434", "8342", "8350", "8358", "8376", "8374", "8382", "8390", "8398", "8406", "8414", "8432", "8540", "8438", "8546", "8464", "8462", "8470", "8478", "8486", "8494", "8502", "8510", "8518", "8526", "8534", "8542", "8650", "8658", "8576", "8574", "8582", "8590", "8598", "8606", "8614", "8632", "8630", "8638", "8646", "8764", "8762", "8670", "8678", "8686", "8694", "8702", "3295", "2303", "2321", "2319", "2327", "2435", "2343", "2361", "2359", "2367", "2375", "2383", "2401", "2409", "2407", "2415", "2423", "2431", "2439", "2547", "2465", "2463", "2471", "2479", "2487", "2495", "2503", "2521", "2519", "2527", "2535", "2543", "2651", "2659", "2567", "2585", "2583", "2601", "2609", "2607", "2615", "2623", "2631", "2639", "2647", "2665", "2763", "2671", "2679", "2687", "2695", "2703", "2721", "2719", "2727", "2735", "2743", "2751", "2759", "2767", "2875", "2783", "2801", "2809", "6401", "6409", "6407", "6415", "6423", "6431", "6439", "6547", "6465", "6463", "6471", "6479", "6487", "6495", "6503", "6521", "6519", "6527", "6535", "6543", "6651", "6659", "6567", "6585", "6583", "6601", "6609", "7607", "7615", "7623", "7631", "7639", "7647", "7665", "7763", "7671", "7679", "7687", "7695", "6703", "6721", "6719", "6727", "6735", "6743", "6751", "6759", "6767", "6875", "6783", "6801", "6809", "6807", "6815", "6823", "6831", "6839", "6847", "6865", "6863", "6871", "6879", "6987", "6895", "0498", "0496", "0504", "0512", "0530", "0528", "0536", "0654", "0652", "0560", "0568", "0576", "0584", "0592", "0610", "0608", "0616", "0624", "0642", "0640", "0648", "0656", "0764", "0672", "0690", "0698", "0696", "0704", "0712", "0720", "0728", "0736", "0754", "0752", "0760", "0768", "0876", "0784", "0792", "0810", "0808", "0816", "0824", "0832", "0850", "0848", "0856", "0864", "0872", "0980", "0898", "0896", "0904", "0912", "0930", "0928", "0936", "0954", "0952", "0970", "0968", "0976", "0984", "1092", "4584", "4592", "4610", "4608", "4616", "4624", "4642", "4640", "4648", "4656", "4764", "4672", "4690", "4698", "4696", "4704", "4712", "4720", "4728", "4736", "4754", "4752", "4760", "4768", "4876", "4784", "4792", "4810", "4808", "4816", "4824", "4832", "4850", "4848", "4856", "4864", "4872", "4980", "4898", "4896", "4904", "4912", "4930", "4928", "4936", "4954", "4952", "4970", "4968", "4976", "4984", "5092", "5010", "5108", "5016", "5024", "5032", "5050", "5048", "5056", "5064", "5072", "5090", "5098", "8690", "8698", "8696", "8704", "8712", "8720", "8728", "8736", "8754", "8752", "8760", "8768", "8876", "8784", "8792", "9810", "9808", "9816", "9824", "9832", "9850", "9848", "9856", "9864", "9872", "9980", "9898", "9896", "8904", "8912", "8930", "8928", "8936", "8954", "8952", "8970", "8968", "8976", "8984", "9092", "9010", "9108", "9016", "9024", "9032", "9050", "9048", "9056", "9064", "9072", "9090", "9098", "9096", "9104", "9212", "9130", "9128", "9136", "9154", "9152", "9170", "9168", "9176", "9184", "1107", "1015", "1023", "1031", "1039", "1047", "1065", "1063", "1071", "1079", "1087", "1095", "0103", "0121", "0219", "0127", "0135", "0143", "0161", "0159", "0167", "0175", "0183", "0201", "0209", "0207", "0215", "0323", "0231", "0239", "0247", "0265", "0263", "0271", "0279", "0287", "0295", "0303", "0321", "0319", "0327", "0435", "0343", "0361", "0359", "0367", "0375", "0383", "0401", "0409", "0407", "0415", "0423", "0431", "0439", "0547", "0465", "0463", "0471", "0479", "0487", "0495", "0503", "0521", "4103", "4121", "4219", "4127", "4135", "4143", "4161", "4159", "4167", "4175", "4183", "4201", "4209", "4207", "4215", "4323", "4231", "4239", "4247", "4265", "4263", "4271", "4279", "4287", "4295", "4303", "4321", "4319", "4327", "4435", "4343", "4361", "4359", "4367", "4375", "4383", "4401", "4409", "5407", "5415", "5423", "5431", "5439", "5547", "5465", "5463", "5471", "5479", "5487", "5495", "4503", "4521", "4519", "4527", "4535", "4543", "4651", "4659", "4567", "4585", "4583", "4601", "4609", "4607", "8209", "8207", "8215", "8323", "8231", "8239", "8247", "8265", "8263", "8271", "8279", "8287", "8295", "8303", "8321", "8319", "8327", "8435", "8343", "8361", "8359", "8367", "8375", "8383", "8401", "8409", "8407", "8415", "8423", "8431", "8439", "8547", "8465", "8463", "8471", "8479", "8487", "8495", "8503", "8521", "8519", "8527", "8535", "8543", "8651", "8659", "8567", "8585", "8583", "8601", "8609", "8607", "8615", "8623", "8631", "8639", "8647", "8665", "8763", "8671", "8679", "8687", "8695", "8703", "3296", "2304", "2312", "2320", "2328", "2436", "2354", "2352", "2360", "2368", "2376", "2384", "2392", "2410", "2408", "2416", "2424", "2432", "2540", "2548", "2456", "2474", "2472", "2490", "2498", "2496", "2504", "2512", "2530", "2528", "2536", "2654", "2652", "2560", "2568", "2576", "2584", "2592", "2610", "2608", "2616", "2624", "2642", "2640", "2648", "2656", "2764", "2672", "2690", "2698", "2696", "2704", "2712", "2720", "2728", "2736", "2754", "2752", "2760", "2768", "2876", "2784", "2792", "2810", "6392", "6410", "6408", "6416", "6424", "6432", "6540", "6548", "6456", "6474", "6472", "6490", "6498", "6496", "6504", "6512", "6530", "6528", "6536", "6654", "6652", "6560", "6568", "6576", "6584", "6592", "7610", "7608", "7616", "7624", "7642", "7640", "7648", "7656", "7764", "7672", "7690", "7698", "7696", "6704", "6712", "6720", "6728", "6736", "6754", "6752", "6760", "6768", "6876", "6784", "6792", "6810", "6808", "6816", "6824", "6832", "6850", "6848", "6856", "6864", "6872", "6980", "6898", "6896", "0489", "0497", "0505", "0513", "0521", "0529", "0537", "0545", "0653", "0561", "0569", "0587", "0585", "0593", "0601", "0609", "0617", "0625", "0643", "0641", "0649", "0657", "0765", "0673", "0681", "0689", "0697", "0705", "0713", "0721", "0729", "0737", "0745", "0753", "0761", "0769", "0787", "0785", "0793", "0801", "0809", "0817", "0825", "0843", "0841", "0849", "0857", "0865", "0873", "0981", "0989", "0897", "0905", "0913", "0921", "0929", "0937", "0945", "0953", "0961", "0969", "0987", "0985", "1093", "4585", "4593", "4601", "4609", "4617", "4625", "4643", "4641", "4649", "4657", "4765", "4673", "4681", "4689", "4697", "4705", "4713", "4721", "4729", "4737", "4745", "4753", "4761", "4769", "4787", "4785", "4793", "4801", "4809", "4817", "4825", "4843", "4841", "4849", "4857", "4865", "4873", "4981", "4989", "4897", "4905", "4913", "4921", "4929", "4937", "4945", "4953", "4961", "4969", "4987", "4985", "5093", "5101", "5109", "5017", "5025", "5043", "5041", "5049", "5057", "5065", "5073", "5081", "5089", "8681", "8689", "8697", "8705", "8713", "8721", "8729", "8737", "8745", "8753", "8761", "8769", "8787", "8785", "8793", "9801", "9809", "9817", "9825", "9843", "9841", "9849", "9857", "9865", "9873", "9981", "9889", "9897", "8905", "8913", "8921", "8929", "8937", "8945", "8953", "8961", "8969", "8987", "8985", "9093", "9101", "9109", "9017", "9025", "9043", "9041", "9049", "9057", "9065", "9073", "9081", "9089", "9097", "9105", "9213", "9121", "9129", "9137", "9145", "9153", "9161", "9169", "9187", "9185", "0512", "0530", "0528", "0536", "0654", "0652", "0560", "0568", "0576", "0584", "0592", "0610", "0608", "0616", "0624", "0642", "0640", "0648", "0656", "0764", "0672", "0690", "0698", "0696", "0704", "0712", "0720", "0728", "0736", "0754", "0752", "0760", "0768", "0876", "0784", "0792", "0810", "0808", "0816", "0824", "0832", "0850", "0848", "0856", "0864", "0872", "0980", "0898", "0896", "0904", "0912", "0930", "0928", "0936", "0954", "0952", "0970", "0968", "0976", "0984", "1092", "1010", "1108", "1016", "4608", "4616", "4624", "4642", "4640", "4648", "4656", "4764", "4672", "4690", "4698", "4696", "4704", "4712", "4720", "4728", "4736", "4754", "4752", "4760", "4768", "4876", "4784", "4792", "4810", "4808", "4816", "4824", "4832", "4850", "4848", "4856", "4864", "4872", "4980", "4898", "4896", "4904", "4912", "4930", "4928", "4936", "4954", "4952", "4970", "4968", "4976", "4984", "5092", "5010", "5108", "5016", "5024", "5032", "5050", "5048", "5056", "5064", "5072", "5090", "5098", "5096", "5104", "5212", "8704", "8712", "8720", "8728", "8736", "8754", "8752", "8760", "8768", "8876", "8784", "8792", "9810", "9808", "9816", "9824", "9832", "9850", "9848", "9856", "9864", "9872", "9980", "9898", "9896", "8904", "8912", "8930", "8928", "8936", "8954", "8952", "8970", "8968", "8976", "8984", "9092", "9010", "9108", "9016", "9024", "9032", "9050", "9048", "9056", "9064", "9072", "9090", "9098", "9096", "9104", "9212", "9130", "9128", "9136", "9154", "9152", "9170", "9168", "9176", "9184", "9192", "9210", "9208", "2801", "2809", "2817", "2825", "2843", "2841", "2849", "2857", "2865", "2873", "2981", "2989", "2897", "2905", "2913", "2921", "2929", "2937", "2945", "2953", "2961", "2969", "2987", "2985", "3093", "3101", "3109", "3017", "3025", "3043", "3041", "3049", "3057", "3065", "3073", "3081", "3089", "3097", "3105", "3213", "3121", "3129", "3137", "3145", "3153", "3161", "3169", "3187", "3185", "3193", "3201", "3209", "3217", "3325", "3243", "3241", "3249", "3257", "3265", "3273", "3281", "3289", "3297", "4305", "6897", "6905", "6913", "6921", "6929", "6937", "6945", "6953", "6961", "6969", "6987", "6985", "7093", "7101", "7109", "7017", "7025", "7043", "7041", "7049", "7057", "7065", "7073", "7081", "7089", "7097", "7105", "7213", "7121", "7129", "7137", "7145", "7153", "7161", "7169", "7187", "7185", "7193", "7201", "7209", "7217", "7325", "7243", "7241", "7249", "7257", "7265", "7273", "7281", "7289", "7297", "7305", "7313", "7321", "7329", "7437", "7345", "7353", "7361", "7369", "7387", "7385", "7393", "7401", "1094", "1102", "1020", "1018", "1026", "1034", "1042", "1060", "1058", "1076", "1074", "1082", "1110", "1098", "2106", "2214", "2132", "2130", "2138", "2146", "2154", "2162", "2170", "2178", "2186", "2194", "1202", "1320", "1218", "1326", "1234", "1242", "1260", "1258", "1276", "1274", "1282", "1310", "1298", "1306", "1314", "1432", "1430", "1438", "1346", "1354", "1372", "1370", "1378", "1386", "1394", "1402", "1420", "1418", "1426", "1434", "1542", "1450", "1458", "1476", "1484", "1482", "1510", "1498", "5110", "5098", "5106", "5214", "5132", "5130", "5138", "5146", "5154", "5162", "5170", "5178", "5186", "5194", "5202", "5320", "5218", "5326", "5234", "5242", "5260", "5258", "5276", "5274", "5282", "5310", "5298", "5306", "5314", "5432", "5430", "5438", "5346", "5354", "5372", "5370", "5378", "5386", "5394", "5402", "5420", "5418", "5426", "5434", "5542", "5450", "5458", "5476", "5484", "5482", "5510", "5498", "6506", "6514", "6532", "6530", "6538", "6546", "6654", "6562", "6570", "6578", "6586", "6594", "9186", "9194", "9202", "9320", "9218", "9326", "9234", "9242", "9260", "9258", "9276", "9274", "9282", "9310", "9298", "9306", "9314", "9432", "9430", "9438", "9346", "9354", "9372", "9370", "9378", "9386", "9394", "9402", "9420", "9418", "9426", "9434", "9542", "9450", "9458", "9476", "9484", "9482", "9510", "9498", "9506", "9514", "9532", "9530", "9538", "9546", "9654", "9562", "9570", "9578", "9586", "9594", "9602", "9620", "9618", "9626", "9634", "9642", "9650", "9658", "9676", "9674", "9682", "9710", "0513", "0521", "0529", "0537", "0545", "0653", "0561", "0569", "0587", "0585", "0593", "0601", "0609", "0617", "0625", "0643", "0641", "0649", "0657", "0765", "0673", "0681", "0689", "0697", "0705", "0713", "0721", "0729", "0737", "0745", "0753", "0761", "0769", "0787", "0785", "0793", "0801", "0809", "0817", "0825", "0843", "0841", "0849", "0857", "0865", "0873", "0981", "0989", "0897", "0905", "0913", "0921", "0929", "0937", "0945", "0953", "0961", "0969", "0987", "0985", "1093", "1101", "1109", "1017", "4609", "4617", "4625", "4643", "4641", "4649", "4657", "4765", "4673", "4681", "4689", "4697", "4705", "4713", "4721", "4729", "4737", "4745", "4753", "4761", "4769", "4787", "4785", "4793", "4801", "4809", "4817", "4825", "4843", "4841", "4849", "4857", "4865", "4873", "4981", "4989", "4897", "4905", "4913", "4921", "4929", "4937", "4945", "4953", "4961", "4969", "4987", "4985", "5093", "5101", "5109", "5017", "5025", "5043", "5041", "5049", "5057", "5065", "5073", "5081", "5089", "5097", "5105", "5213", "8705", "8713", "8721", "8729", "8737", "8745", "8753", "8761", "8769", "8787", "8785", "8793", "9801", "9809", "9817", "9825", "9843", "9841", "9849", "9857", "9865", "9873", "9981", "9989", "9897", "8905", "8913", "8921", "8929", "8937", "8945", "8953", "8961", "8969", "8987", "8985", "9093", "9101", "9109", "9017", "9025", "9043", "9041", "9049", "9057", "9065", "9073", "9081", "9089", "9097", "9105", "9213", "9121", "9129", "9137", "9145", "9153", "9161", "9169", "9187", "9185", "9193", "9201", "9209", "2802", "2820", "2818", "2826", "2834", "2842", "2860", "2858", "2876", "2874", "2982", "2890", "2908", "2906", "2914", "2932", "2930", "2938", "2946", "2954", "2962", "2970", "2978", "2986", "3094", "3102", "3020", "3018", "3026", "3034", "3042", "3060", "3058", "3076", "3074", "3082", "3110", "3098", "3106", "3214", "3132", "3130", "3138", "3146", "3154", "3162", "3170", "3178", "3186", "3194", "3202", "3320", "3218", "3326", "3234", "3242", "3260", "3258", "3276", "3274", "3282", "3310", "3298", "4306", "6908", "6906", "6914", "6932", "6930", "6938", "6946", "6954", "6962", "6970", "6978", "6986", "7094", "7102", "7020", "7018", "7026", "7034", "7042", "7060", "7058", "7076", "7074", "7082", "7110", "7098", "7106", "7214", "7132", "7130", "7138", "7146", "7154", "7162", "7170", "7178", "7186", "7194", "7202", "7320", "7218", "7326", "7234", "7242", "7260", "7258", "7276", "7274", "7282", "7310", "7298", "7306", "7314", "7432", "7430", "7438", "7346", "7354", "7372", "7370", "7378", "7386", "7394", "7402", "1095", "1103", "1021", "1019", "1027", "1035", "1043", "1051", "1059", "1067", "1075", "1083", "1091", "1109", "2107", "2215", "2123", "2141", "2139", "2147", "2165", "2163", "2181", "2179", "2187", "2195", "1203", "1221", "1219", "1327", "1235", "1243", "1251", "1259", "1267", "1275", "1283", "1291", "1309", "1307", "1315", "1323", "1431", "1439", "1347", "1365", "1363", "1371", "1379", "1387", "1395", "1403", "1421", "1419", "1427", "1435", "1543", "1451", "1459", "1467", "1475", "1483", "1491", "1509", "5091", "5109", "5107", "5215", "5123", "5141", "5139", "5147", "5165", "5163", "5181", "5179", "5187", "5195", "5203", "5221", "5219", "5327", "5235", "5243", "5251", "5259", "5267", "5275", "5283", "5291", "5309", "5307", "5315", "5323", "5431", "5439", "5347", "5365", "5363", "5371", "5379", "5387", "5395", "5403", "5421", "5419", "5427", "5435", "5543", "5451", "5459", "5467", "5475", "5483", "5491", "5509", "6507", "6515", "6523", "6541", "6539", "6547", "6565", "6563", "6571", "6579", "6587", "6595", "9187", "9195", "9203", "9221", "9219", "9327", "9235", "9243", "9251", "9259", "9267", "9275", "9283", "9291", "9309", "9307", "9315", "9323", "9431", "9439", "9347", "9365", "9363", "9371", "9379", "9387", "9395", "9403", "9421", "9419", "9427", "9435", "9543", "9451", "9459", "9467", "9475", "9483", "9491", "9509", "9507", "9515", "9523", "9541", "9539", "9547", "9565", "9563", "9571", "9579", "9587", "9595", "9603", "9621", "9619", "9627", "9635", "9643", "9651", "9659", "9767", "9675", "9683", "9691", "0514", "0532", "0530", "0538", "0546", "0654", "0562", "0570", "0578", "0586", "0594", "0602", "0620", "0618", "0626", "0634", "0642", "0650", "0658", "0676", "0674", "0682", "0710", "0698", "0706", "0714", "0732", "0730", "0738", "0746", "0754", "0762", "0870", "0878", "0786", "0794", "0802", "0820", "0818", "0826", "0834", "0842", "0860", "0858", "0876", "0874", "0982", "0890", "0908", "0906", "0914", "0932", "0930", "0938", "0946", "0954", "0962", "0970", "0978", "0986", "1094", "1102", "1020", "1018", "4620", "4618", "4626", "4634", "4642", "4650", "4658", "4676", "4674", "4682", "4710", "4698", "4706", "4714", "4732", "4730", "4738", "4746", "4754", "4762", "4870", "4878", "4786", "4794", "4802", "4820", "4818", "4826", "4834", "4842", "4860", "4858", "4876", "4874", "4982", "4890", "4908", "4906", "4914", "4932", "4930", "4938", "4946", "4954", "4962", "4970", "4978", "4986", "5094", "5102", "5020", "5018", "5026", "5034", "5042", "5060", "5058", "5076", "5074", "5082", "5110", "5098", "5106", "5214", "8706", "8714", "8732", "8730", "8738", "8746", "8754", "8762", "8870", "8878", "8786", "8794", "9802", "9820", "9818", "9826", "9834", "9842", "9860", "9858", "9876", "9874", "9982", "9890", "9908", "8906", "8914", "8932", "8930", "8938", "8946", "8954", "8962", "8970", "8978", "8986", "9094", "9102", "9020", "9018", "9026", "9034", "9042", "9060", "9058", "9076", "9074", "9082", "9110", "9098", "9106", "9214", "9132", "9130", "9138", "9146", "9154", "9162", "9170", "9178", "9186", "9194", "9202", "9320", "2803", "2821", "2819", "2827", "2835", "2843", "2851", "2859", "2867", "2875", "2983", "2891", "2909", "2907", "2915", "2923", "2941", "2939", "2947", "2965", "2963", "2981", "2979", "2987", "3095", "3103", "3021", "3019", "3027", "3035", "3043", "3051", "3059", "3067", "3075", "3083", "3091", "3109", "3107", "3215", "3123", "3141", "3139", "3147", "3165", "3163", "3181", "3179", "3187", "3195", "3203", "3221", "3219", "3327", "3235", "3243", "3251", "3259", "3267", "3275", "3283", "3291", "3309", "4307", "6909", "6907", "6915", "6923", "6941", "6939", "6947", "6965", "6963", "6981", "6979", "6987", "7095", "7103", "7021", "7019", "7027", "7035", "7043", "7051", "7059", "7067", "7075", "7083", "7091", "7109", "7107", "7215", "7123", "7141", "7139", "7147", "7165", "7163", "7181", "7179", "7187", "7195", "7203", "7221", "7219", "7327", "7235", "7243", "7251", "7259", "7267", "7275", "7283", "7291", "7309", "7307", "7315", "7323", "7431", "7439", "7347", "7365", "7363", "7371", "7379", "7387", "7395", "7403", "1096", "1104", "1012", "1030", "1028", "1036", "1054", "1052", "1070", "1068", "1076", "1084", "1092", "2210", "2108", "2216", "2124", "2132", "2150", "2148", "2156", "2164", "2172", "2190", "2198", "2196", "1204", "1212", "1320", "1328", "1236", "1254", "1252", "1260", "1268", "1276", "1284", "1292", "1310", "1308", "1316", "1324", "1432", "1350", "1348", "1356", "1364", "1372", "1390", "1398", "1396", "1404", "1412", "1430", "1428", "1436", "1454", "1452", "1460", "1468", "1476", "1484", "1492", "1510", "5092", "5210", "5108", "5216", "5124", "5132", "5150", "5148", "5156", "5164", "5172", "5190", "5198", "5196", "5204", "5212", "5320", "5328", "5236", "5254", "5252", "5260", "5268", "5276", "5284", "5292", "5310", "5308", "5316", "5324", "5432", "5350", "5348", "5356", "5364", "5372", "5390", "5398", "5396", "5404", "5412", "5430", "5428", "5436", "5454", "5452", "5460", "5468", "5476", "5484", "5492", "5510", "6508", "6516", "6524", "6532", "6540", "6548", "6656", "6574", "6572", "6590", "6598", "6596", "9198", "9196", "9204", "9212", "9320", "9328", "9236", "9254", "9252", "9260", "9268", "9276", "9284", "9292", "9310", "9308", "9316", "9324", "9432", "9350", "9348", "9356", "9364", "9372", "9390", "9398", "9396", "9404", "9412", "9430", "9428", "9436", "9454", "9452", "9460", "9468", "9476", "9484", "9492", "9510", "9508", "9516", "9524", "9532", "9540", "9548", "9656", "9574", "9572", "9590", "9598", "9596", "9604", "9612", "9620", "9628", "9636", "9654", "9652", "9760", "9768", "9686", "9684", "9692", "0515", "0523", "0541", "0539", "0547", "0565", "0563", "0571", "0579", "0587", "0595", "0603", "0621", "0619", "0627", "0635", "0643", "0651", "0659", "0767", "0675", "0683", "0691", "0709", "0707", "0715", "0723", "0731", "0739", "0747", "0765", "0763", "0871", "0879", "0787", "0795", "0803", "0821", "0819", "0827", "0835", "0843", "0851", "0859", "0867", "0875", "0983", "0891", "0909", "0907", "0915", "0923", "0941", "0939", "0947", "0965", "0963", "0981", "0979", "0987", "1095", "1103", "1021", "1019", "4621", "4619", "4627", "4635", "4643", "4651", "4659", "4767", "4675", "4683", "4691", "4709", "4707", "4715", "4723", "4731", "4739", "4747", "4765", "4763", "4871", "4879", "4787", "4795", "4803", "4821", "4819", "4827", "4835", "4843", "4851", "4859", "4867", "4875", "4983", "4891", "4909", "4907", "4915", "4923", "4941", "4939", "4947", "4965", "4963", "4981", "4979", "4987", "5095", "5103", "5021", "5019", "5027", "5035", "5043", "5051", "5059", "5067", "5075", "5083", "5091", "5109", "5107", "5215", "8707", "8715", "8723", "8731", "8739", "8747", "8765", "8763", "8871", "8879", "8787", "8795", "9803", "9821", "9819", "9827", "9835", "9843", "9851", "9859", "9867", "9875", "9983", "9891", "9909", "8907", "8915", "8923", "8941", "8939", "8947", "8965", "8963", "8981", "8979", "8987", "9095", "9103", "9021", "9019", "9027", "9035", "9043", "9051", "9059", "9067", "9075", "9083", "9091", "9109", "9107", "9215", "9123", "9141", "9139", "9147", "9165", "9163", "9181", "9179", "9187", "9195", "9203", "9221", "2804", "2812", "2830", "2828", "2836", "2854", "2852", "2870", "2868", "2876", "2984", "2892", "2910", "2908", "2916", "2924", "2932", "2950", "2948", "2956", "2964", "2972", "2980", "3098", "3096", "3104", "3012", "3030", "3028", "3036", "3054", "3052", "3070", "3068", "3076", "3084", "3092", "3210", "3108", "3216", "3124", "3132", "3150", "3148", "3156", "3164", "3172", "3190", "3198", "3196", "3204", "3212", "3320", "3328", "3236", "3254", "3252", "3260", "3268", "3276", "3284", "3292", "4310", "4308", "6910", "6908", "6916", "6924", "6932", "6950", "6948", "6956", "6964", "6972", "6980", "7098", "7096", "7104", "7012", "7030", "7028", "7036", "7054", "7052", "7070", "7068", "7076", "7084", "7092", "7210", "7108", "7216", "7124", "7132", "7150", "7148", "7156", "7164", "7172", "7190", "7198", "7196", "7204", "7212", "7320", "7328", "7236", "7254", "7252", "7260", "7268", "7276", "7284", "7292", "7310", "7308", "7316", "7324", "7432", "7350", "7348", "7356", "7364", "7372", "7390", "7398", "7396", "7404", "1097", "1105", "1013", "1021", "1029", "1037", "1045", "1053", "1061", "1069", "1087", "1085", "1093", "2101", "2109", "2217", "2125", "2143", "2141", "2149", "2157", "2165", "2173", "2181", "2189", "2197", "1205", "1213", "1321", "1329", "1237", "1245", "1253", "1261", "1269", "1287", "1285", "1293", "1301", "1309", "1317", "1325", "1343", "1341", "1349", "1357", "1365", "1383", "1381", "1389", "1397", "1405", "1413", "1421", "1429", "1437", "1545", "1453", "1461", "1469", "1487", "1485", "1493", "1501", "5093", "5101", "5109", "5217", "5125", "5143", "5141", "5149", "5157", "5165", "5173", "5181", "5189", "5197", "5205", "5213", "5321", "5329", "5237", "5245", "5253", "5261", "5269", "5287", "5285", "5293", "5301", "5309", "5317", "5325", "5343", "5341", "5349", "5357", "5365", "5383", "5381", "5389", "5397", "5405", "5413", "5421", "5429", "5437", "5545", "5453", "5461", "5469", "5487", "5485", "5493", "6501", "6509", "6517", "6525", "6543", "6541", "6549", "6657", "6565", "6573", "6581", "6589", "6597", "9189", "9197", "9205", "9213", "9321", "9329", "9237", "9245", "9253", "9261", "9269", "9287", "9285", "9293", "9301", "9309", "9317", "9325", "9343", "9341", "9349", "9357", "9365", "9383", "9381", "9389", "9397", "9405", "9413", "9421", "9429", "9437", "9545", "9453", "9461", "9469", "9487", "9485", "9493", "9501", "9509", "9517", "9525", "9543", "9541", "9549", "9657", "9565", "9573", "9581", "9589", "9597", "9605", "9613", "9621", "9629", "9637", "9645", "9653", "9761", "9769", "9687", "9685", "9693", "0516", "0524", "0532", "0540", "0548", "0656", "0574", "0572", "0590", "0598", "0596", "0604", "0612", "0620", "0628", "0636", "0654", "0652", "0760", "0768", "0686", "0684", "0692", "0710", "0708", "0716", "0724", "0732", "0740", "0748", "0756", "0764", "0872", "0790", "0798", "0796", "0804", "0812", "0830", "0828", "0836", "0854", "0852", "0870", "0868", "0876", "0984", "0892", "0910", "0908", "0916", "0924", "0932", "0950", "0948", "0956", "0964", "0972", "0980", "1098", "1096", "1104", "1012", "1030", "4612", "4620", "4628", "4636", "4654", "4652", "4760", "4768", "4686", "4684", "4692", "4710", "4708", "4716", "4724", "4732", "4740", "4748", "4756", "4764", "4872", "4790", "4798", "4796", "4804", "4812", "4830", "4828", "4836", "4854", "4852", "4870", "4868", "4876", "4984", "4892", "4910", "4908", "4916", "4924", "4932", "4950", "4948", "4956", "4964", "4972", "4980", "5098", "5096", "5104", "5012", "5030", "5028", "5036", "5054", "5052", "5070", "5068", "5076", "5084", "5092", "5210", "5108", "5216", "8708", "8716", "8724", "8732", "8740", "8748", "8756", "8764", "8872", "8790", "8798", "8796", "9804", "9812", "9830", "9828", "9836", "9854", "9852", "9870", "9868", "9876", "9984", "9892", "8910", "8908", "8916", "8924", "8932", "8950", "8948", "8956", "8964", "8972", "8980", "9098", "9096", "9104", "9012", "9030", "9028", "9036", "9054", "9052", "9070", "9068", "9076", "9084", "9092", "9210", "9108", "9216", "9124", "9132", "9150", "9148", "9156", "9164", "9172", "9190", "9198", "9196", "9204", "9212", "2805", "2813", "2821", "2829", "2837", "2845", "2853", "2861", "2869", "2887", "2985", "2893", "2901", "2909", "2917", "2925", "2943", "2941", "2949", "2957", "2965", "2973", "2981", "2989", "3097", "3105", "3013", "3021", "3029", "3037", "3045", "3053", "3061", "3069", "3087", "3085", "3093", "3101", "3109", "3217", "3125", "3143", "3141", "3149", "3157", "3165", "3173", "3181", "3189", "3197", "3205", "3213", "3321", "3329", "3237", "3245", "3253", "3261", "3269", "3287", "3285", "3293", "4301", "4309", "6901", "6909", "6917", "6925", "6943", "6941", "6949", "6957", "6965", "6973", "6981", "6989", "7097", "7105", "7013", "7021", "7029", "7037", "7045", "7053", "7061", "7069", "7087", "7085", "7093", "7101", "7109", "7217", "7125", "7143", "7141", "7149", "7157", "7165", "7173", "7181", "7189", "7197", "7205", "7213", "7321", "7329", "7237", "7245", "7253", "7261", "7269", "7287", "7285", "7293", "7301", "7309", "7317", "7325", "7343", "7341", "7349", "7357", "7365", "7383", "7381", "7389", "7397", "7405", "1098", "1106", "1014", "1032", "1040", "1038", "1046", "1054", "1062", "1080", "1078", "1086", "1094", "2102", "2210", "2218", "2126", "2134", "2142", "2150", "2158", "2176", "2174", "2182", "2190", "2198", "1206", "1214", "1232", "1230", "1238", "1246", "1254", "1272", "1270", "1278", "1286", "1294", "1302", "1310", "1318", "1326", "1434", "1342", "1350", "1358", "1376", "1374", "1382", "1390", "1398", "1406", "1414", "1432", "1540", "1438", "1546", "1464", "1462", "1470", "1478", "1486", "1494", "1502", "5094", "5102", "5210", "5218", "5126", "5134", "5142", "5150", "5158", "5176", "5174", "5182", "5190", "5198", "5206", "5214", "5232", "5230", "5238", "5246", "5254", "5272", "5270", "5278", "5286", "5294", "5302", "5310", "5318", "5326", "5434", "5342", "5350", "5358", "5376", "5374", "5382", "5390", "5398", "5406", "5414", "5432", "5540", "5438", "5546", "5464", "5462", "5470", "5478", "5486", "5494", "6502", "6510", "6518", "6526", "6534", "6542", "6650", "6658", "6576", "6574", "6582", "6590", "6598", "9190", "9198", "9206", "9214", "9232", "9230", "9238", "9246", "9254", "9272", "9270", "9278", "9286", "9294", "9302", "9310", "9318", "9326", "9434", "9342", "9350", "9358", "9376", "9374", "9382", "9390", "9398", "9406", "9414", "9432", "9540", "9438", "9546", "9464", "9462", "9470", "9478", "9486", "9494", "9502", "9510", "9518", "9526", "9534", "9542", "9650", "9658", "9576", "9574", "9582", "9590", "9598", "9606", "9614", "9632", "9630", "9638", "9646", "9764", "9762", "9670", "9678", "9686", "9694", "0517", "0525", "0543", "0541", "0549", "0657", "0565", "0573", "0581", "0589", "0597", "0605", "0613", "0621", "0629", "0637", "0645", "0653", "0761", "0769", "0687", "0685", "0693", "0701", "0709", "0717", "0725", "0743", "0741", "0749", "0757", "0765", "0873", "0781", "0789", "0797", "0805", "0813", "0821", "0829", "0837", "0845", "0853", "0861", "0869", "0887", "0985", "0893", "0901", "0909", "0917", "0925", "0943", "0941", "0949", "0957", "0965", "0973", "0981", "0989", "1097", "1105", "1013", "1021", "4613", "4621", "4629", "4637", "4645", "4653", "4761", "4769", "4687", "4685", "4693", "4701", "4709", "4717", "4725", "4743", "4741", "4749", "4757", "4765", "4873", "4781", "4789", "4797", "4805", "4813", "4821", "4829", "4837", "4845", "4853", "4861", "4869", "4887", "4985", "4893", "4901", "4909", "4917", "4925", "4943", "4941", "4949", "4957", "4965", "4973", "4981", "4989", "5097", "5105", "5013", "5021", "5029", "5037", "5045", "5053", "5061", "5069", "5087", "5085", "5093", "5101", "5109", "5217", "8709", "8717", "8725", "8743", "8741", "8749", "8757", "8765", "8873", "8781", "8789", "8797", "9805", "9813", "9821", "9829", "9837", "9845", "9853", "9861", "9869", "9887", "9985", "9893", "8901", "8909", "8917", "8925", "8943", "8941", "8949", "8957", "8965", "8973", "8981", "8989", "9097", "9105", "9013", "9021", "9029", "9037", "9045", "9053", "9061", "9069", "9087", "9085", "9093", "9101", "9109", "9217", "9125", "9143", "9141", "9149", "9157", "9165", "9173", "9181", "9189", "9197", "9205", "9213", "2806", "2814", "2832", "2840", "2838", "2846", "2854", "2862", "2980", "2878", "2986", "2894", "2902", "2910", "2918", "2926", "2934", "2942", "2950", "2958", "2976", "2974", "2982", "3090", "3098", "3106", "3014", "3032", "3040", "3038", "3046", "3054", "3062", "3080", "3078", "3086", "3094", "3102", "3210", "3218", "3126", "3134", "3142", "3150", "3158", "3176", "3174", "3182", "3190", "3198", "3206", "3214", "3232", "3230", "3238", "3246", "3254", "3272", "3270", "3278", "3286", "3294", "4302", "4310", "6902", "6910", "6918", "6926", "6934", "6942", "6950", "6958", "6976", "6974", "6982", "7090", "7098", "7106", "7014", "7032", "7040", "7038", "7046", "7054", "7062", "7080", "7078", "7086", "7094", "7102", "7210", "7218", "7126", "7134", "7142", "7150", "7158", "7176", "7174", "7182", "7190", "7198", "7206", "7214", "7232", "7230", "7238", "7246", "7254", "7272", "7270", "7278", "7286", "7294", "7302", "7310", "7318", "7326", "7434", "7342", "7350", "7358", "7376", "7374", "7382", "7390", "7398", "7406", "1109", "1107", "1015", "1023", "1031", "1039", "1047", "1065", "1063", "1071", "1079", "1087", "1095", "2103", "2121", "2219", "2127", "2135", "2143", "2161", "2159", "2167", "2175", "2183", "2201", "2209", "1207", "1215", "1323", "1231", "1239", "1247", "1265", "1263", "1271", "1279", "1287", "1295", "1303", "1321", "1319", "1327", "1435", "1343", "1361", "1359", "1367", "1375", "1383", "1401", "1409", "1407", "1415", "1423", "1431", "1439", "1547", "1465", "1463", "1471", "1479", "1487", "1495", "1503", "5095", "5103", "5121", "5219", "5127", "5135", "5143", "5161", "5159", "5167", "5175", "5183", "5201", "5209", "5207", "5215", "5323", "5231", "5239", "5247", "5265", "5263", "5271", "5279", "5287", "5295", "5303", "5321", "5319", "5327", "5435", "5343", "5361", "5359", "5367", "5375", "5383", "5401", "5409", "5407", "5415", "5423", "5431", "5439", "5547", "5465", "5463", "5471", "5479", "5487", "5495", "6503", "6521", "6519", "6527", "6535", "6543", "6651", "6659", "6567", "6585", "6583", "6601", "6609", "9201", "9209", "9207", "9215", "9323", "9231", "9239", "9247", "9265", "9263", "9271", "9279", "9287", "9295", "9303", "9321", "9319", "9327", "9435", "9343", "9361", "9359", "9367", "9375", "9383", "9401", "9409", "9407", "9415", "9423", "9431", "9439", "9547", "9465", "9463", "9471", "9479", "9487", "9495", "9503", "9521", "9519", "9527", "9535", "9543", "9651", "9659", "9567", "9585", "9583", "9601", "9609", "9607", "9615", "9623", "9631", "9639", "9647", "9665", "9763", "9671", "9679", "9687", "9695", "0518", "0526", "0534", "0542", "0650", "0658", "0576", "0574", "0582", "0590", "0598", "0606", "0614", "0632", "0630", "0638", "0646", "0764", "0762", "0670", "0678", "0686", "0694", "0702", "0710", "0718", "0726", "0734", "0742", "0750", "0758", "0876", "0874", "0782", "0790", "0798", "0806", "0814", "0832", "0840", "0838", "0846", "0854", "0862", "0980", "0878", "0986", "0894", "0902", "0910", "0918", "0926", "0934", "0942", "0950", "0958", "0976", "0974", "0982", "1090", "1098", "1106", "1014", "1032", "4614", "4632", "4630", "4638", "4646", "4764", "4762", "4670", "4678", "4686", "4694", "4702", "4710", "4718", "4726", "4734", "4742", "4750", "4758", "4876", "4874", "4782", "4790", "4798", "4806", "4814", "4832", "4840", "4838", "4846", "4854", "4862", "4980", "4878", "4986", "4894", "4902", "4910", "4918", "4926", "4934", "4942", "4950", "4958", "4976", "4974", "4982", "5090", "5098", "5106", "5014", "5032", "5040", "5038", "5046", "5054", "5062", "5080", "5078", "5086", "5094", "5102", "5210", "5218", "8710", "8718", "8726", "8734", "8742", "8750", "8758", "8876", "8874", "8782", "8790", "8798", "9806", "9814", "9832", "9840", "9838", "9846", "9854", "9862", "9980", "9878", "9986", "9894", "8902", "8910", "8918", "8926", "8934", "8942", "8950", "8958", "8976", "8974", "8982", "9090", "9098", "9106", "9014", "9032", "9040", "9038", "9046", "9054", "9062", "9080", "9078", "9086", "9094", "9102", "9210", "9218", "9126", "9134", "9142", "9150", "9158", "9176", "9174", "9182", "9190", "9198", "9206", "9214", "2807", "2815", "2823", "2831", "2839", "2847", "2865", "2863", "2871", "2879", "2987", "2895", "2903", "2921", "2919", "2927", "2935", "2943", "2961", "2959", "2967", "2975", "2983", "3091", "3109", "3107", "3015", "3023", "3031", "3039", "3047", "3065", "3063", "3071", "3079", "3087", "3095", "3103", "3121", "3219", "3127", "3135", "3143", "3161", "3159", "3167", "3175", "3183", "3201", "3209", "3207", "3215", "3323", "3231", "3239", "3247", "3265", "3263", "3271", "3279", "3287", "3295", "4303", "4321", "6903", "6921", "6919", "6927", "6935", "6943", "6961", "6959", "6967", "6975", "6983", "7091", "7109", "7107", "7015", "7023", "7031", "7039", "7047", "7065", "7063", "7071", "7079", "7087", "7095", "7103", "7121", "7219", "7127", "7135", "7143", "7161", "7159", "7167", "7175", "7183", "7201", "7209", "7207", "7215", "7323", "7231", "7239", "7247", "7265", "7263", "7271", "7279", "7287", "7295", "7303", "7321", "7319", "7327", "7435", "7343", "7361", "7359", "7367", "7375", "7383", "7401", "7409", "7407", "1010", "1108", "1016", "1024", "1032", "1050", "1048", "1056", "1064", "1072", "1090", "1098", "1096", "2104", "2212", "2130", "2128", "2136", "2154", "2152", "2170", "2168", "2176", "2184", "2192", "1210", "1208", "1216", "1324", "1242", "1250", "1248", "1256", "1264", "1282", "1290", "1298", "1296", "1304", "1312", "1320", "1328", "1436", "1354", "1352", "1360", "1368", "1376", "1384", "1392", "1410", "1408", "1416", "1424", "1432", "1540", "1548", "1456", "1474", "1472", "1490", "1498", "1496", "1504", "5096", "5104", "5212", "5130", "5128", "5136", "5154", "5152", "5170", "5168", "5176", "5184", "5192", "5210", "5208", "5216", "5324", "5242", "5250", "5248", "5256", "5264", "5282", "5290", "5298", "5296", "5304", "5312", "5320", "5328", "5436", "5354", "5352", "5360", "5368", "5376", "5384", "5392", "5410", "5408", "5416", "5424", "5432", "5540", "5548", "5456", "5474", "5472", "5490", "5498", "5496", "6504", "6512", "6530", "6528", "6536", "6654", "6652", "6560", "6568", "6576", "6584", "6592", "5610", "9192", "9210", "9208", "9216", "9324", "9242", "9250", "9248", "9256", "9264", "9282", "9290", "9298", "9296", "9304", "9312", "9320", "9328", "9436", "9354", "9352", "9360", "9368", "9376", "9384", "9392", "9410", "9408", "9416", "9424", "9432", "9540", "9548", "9456", "9474", "9472", "9490", "9498", "9496", "9504", "9512", "9530", "9528", "9536", "9654", "9652", "9560", "9568", "9576", "9584", "9592", "9610", "9608", "9616", "9624", "9642", "9640", "9648", "9656", "9764", "9672", "9690", "9698", "9696", "0519", "0527", "0535", "0543", "0651", "0659", "0567", "0585", "0583", "0601", "0609", "0607", "0615", "0623", "0631", "0639", "0647", "0665", "0763", "0671", "0679", "0687", "0695", "0703", "0721", "0719", "0727", "0735", "0743", "0751", "0759", "0767", "0875", "0783", "0801", "0809", "0807", "0815", "0823", "0831", "0839", "0847", "0865", "0863", "0871", "0879", "0987", "0895", "0903", "0921", "0919", "0927", "0935", "0943", "0961", "0959", "0967", "0975", "0983", "1091", "1109", "1107", "1015", "1023", "4615", "4623", "4631", "4639", "4647", "4665", "4763", "4671", "4679", "4687", "4695", "4703", "4721", "4719", "4727", "4735", "4743", "4751", "4759", "4767", "4875", "4783", "4801", "4809", "4807", "4815", "4823", "4831", "4839", "4847", "4865", "4863", "4871", "4879", "4987", "4895", "4903", "4921", "4919", "4927", "4935", "4943", "4961", "4959", "4967", "4975", "4983", "5091", "5109", "5107", "5015", "5023", "5031", "5039", "5047", "5065", "5063", "5071", "5079", "5087", "5095", "5103", "5121", "5219", "8721", "8719", "8727", "8735", "8743", "8751", "8759", "8767", "8875", "8783", "8801", "8809", "9807", "9815", "9823", "9831", "9839", "9847", "9865", "9863", "9871", "9879", "9987", "9895", "8903", "8921", "8919", "8927", "8935", "8943", "8961", "8959", "8967", "8975", "8983", "9091", "9109", "9107", "9015", "9023", "9031", "9039", "9047", "9065", "9063", "9071", "9079", "9087", "9095", "9103", "9121", "9219", "9127", "9135", "9143", "9161", "9159", "9167", "9175", "9183", "9201", "9209", "9207", "9215", "2808", "2816", "2824", "2832", "2850", "2848", "2856", "2864", "2872", "2980", "2898", "2896", "2904", "2912", "2930", "2928", "2936", "2954", "2952", "2970", "2968", "2976", "2984", "3092", "3010", "3108", "3016", "3024", "3032", "3050", "3048", "3056", "3064", "3072", "3090", "3098", "3096", "3104", "3212", "3130", "3128", "3136", "3154", "3152", "3170", "3168", "3176", "3184", "3192", "3210", "3208", "3216", "3324", "3242", "3250", "3248", "3256", "3264", "3282", "3290", "3298", "3296", "4304", "4312", "6904", "6912", "6930", "6928", "6936", "6954", "6952", "6970", "6968", "6976", "6984", "7092", "7010", "7108", "7016", "7024", "7032", "7050", "7048", "7056", "7064", "7072", "7090", "7098", "7096", "7104", "7212", "7130", "7128", "7136", "7154", "7152", "7170", "7168", "7176", "7184", "7192", "7210", "7208", "7216", "7324", "7242", "7250", "7248", "7256", "7264", "7282", "7290", "7298", "7296", "7304", "7312", "7320", "7328", "7436", "7354", "7352", "7360", "7368", "7376", "7384", "7392", "7410", "7408", "1101", "1109", "1017", "1025", "1043", "1041", "1049", "1057", "1065", "1073", "1081", "1089", "1097", "2105", "2213", "2121", "2129", "2137", "2145", "2153", "2161", "2169", "2187", "2185", "2193", "1201", "1209", "1217", "1325", "1243", "1241", "1249", "1257", "1265", "1273", "1281", "1289", "1297", "1305", "1313", "1321", "1329", "1437", "1345", "1353", "1361", "1369", "1387", "1385", "1393", "1401", "1409", "1417", "1425", "1443", "1541", "1549", "1457", "1465", "1473", "1481", "1489", "1497", "1505", "5097", "5105", "5213", "5121", "5129", "5137", "5145", "5153", "5161", "5169", "5187", "5185", "5193", "5201", "5209", "5217", "5325", "5243", "5241", "5249", "5257", "5265", "5273", "5281", "5289", "5297", "5305", "5313", "5321", "5329", "5437", "5345", "5353", "5361", "5369", "5387", "5385", "5393", "5401", "5409", "5417", "5425", "5443", "5541", "5549", "5457", "5465", "5473", "5481", "5489", "5497", "6505", "6513", "6521", "6529", "6537", "6545", "6653", "6561", "6569", "6587", "6585", "6593", "5601", "9193", "9201", "9209", "9217", "9325", "9243", "9241", "9249", "9257", "9265", "9273", "9281", "9289", "9297", "9305", "9313", "9321", "9329", "9437", "9345", "9353", "9361", "9369", "9387", "9385", "9393", "9401", "9409", "9417", "9425", "9443", "9541", "9549", "9457", "9465", "9473", "9481", "9489", "9497", "9505", "9513", "9521", "9529", "9537", "9545", "9653", "9561", "9569", "9587", "9585", "9593", "9601", "9609", "9617", "9625", "9643", "9641", "9649", "9657", "9765", "9673", "9681", "9689", "9697", "1024", "1032", "1050", "1048", "1056", "1064", "1072", "1090", "1098", "1096", "2104", "2212", "2130", "2128", "2136", "2154", "2152", "2170", "2168", "2176", "2184", "2192", "1210", "1208", "1216", "1324", "1242", "1250", "1248", "1256", "1264", "1282", "1290", "1298", "1296", "1304", "1312", "1320", "1328", "1436", "1354", "1352", "1360", "1368", "1376", "1384", "1392", "1410", "1408", "1416", "1424", "1432", "1540", "1548", "1456", "1474", "1472", "1490", "1498", "1496", "1504", "1512", "1530", "1528", "5130", "5128", "5136", "5154", "5152", "5170", "5168", "5176", "5184", "5192", "5210", "5208", "5216", "5324", "5242", "5250", "5248", "5256", "5264", "5282", "5290", "5298", "5296", "5304", "5312", "5320", "5328", "5436", "5354", "5352", "5360", "5368", "5376", "5384", "5392", "5410", "5408", "5416", "5424", "5432", "5540", "5548", "5456", "5474", "5472", "5490", "5498", "5496", "6504", "6512", "6530", "6528", "6536", "6654", "6652", "6560", "6568", "6576", "6584", "6592", "5610", "5608", "5616", "5624", "9216", "9324", "9242", "9250", "9258", "9256", "9264", "9282", "9290", "9298", "9296", "9304", "9312", "9320", "9328", "9436", "9354", "9352", "9360", "9368", "9376", "9384", "9392", "9410", "9408", "9416", "9424", "9432", "9540", "9548", "9456", "9474", "9472", "9490", "9498", "9496", "9504", "9512", "9530", "9528", "9536", "9654", "9652", "9560", "9568", "9576", "9584", "9592", "9610", "9608", "9616", "9624", "9642", "9640", "9648", "9656", "9764", "9672", "9690", "9698", "9696", "9704", "9712", "9720", "4313", "4321", "4329", "4437", "4345", "4353", "4361", "4369", "4387", "4385", "4393", "3401", "3409", "3417", "3425", "3443", "3541", "3549", "3457", "3465", "3473", "3481", "3489", "3497", "3505", "3513", "3521", "3529", "3537", "3545", "3653", "3561", "3569", "3587", "3585", "3593", "3601", "3609", "3617", "3625", "3643", "3641", "3649", "3657", "3765", "3673", "3681", "3689", "3697", "3705", "3713", "3721", "3729", "3737", "3745", "3753", "3761", "3769", "3787", "3785", "3793", "3801", "3809", "3817", "7409", "7417", "7425", "7443", "7541", "7549", "7457", "7465", "7473", "7481", "7489", "7497", "7505", "7513", "7521", "7529", "7537", "7545", "7653", "7561", "7569", "7587", "7585", "7593", "7601", "7609", "7617", "7625", "7643", "7641", "7649", "7657", "7765", "7673", "7681", "7689", "7697", "8705", "8713", "8721", "8729", "8737", "8745", "8753", "8761", "8769", "8787", "8785", "8793", "7801", "7809", "7817", "7825", "7843", "7841", "7849", "7857", "7865", "7873", "7981", "7989", "7897", "7905", "7913", "1506", "1514", "1532", "1530", "1538", "1546", "1654", "1562", "1570", "1578", "1586", "1594", "1602", "1620", "1618", "1626", "1634", "1642", "1650", "1658", "1676", "1674", "1682", "1710", "1698", "1706", "1714", "1732", "1730", "1738", "1746", "1754", "1762", "1870", "1878", "1786", "1794", "1802", "1820", "1818", "1826", "1834", "1842", "1860", "1858", "1876", "1874", "1982", "1890", "1908", "1906", "1914", "1932", "1930", "1938", "1946", "1954", "1962", "1970", "1978", "1986", "2094", "2102", "2020", "5602", "5620", "5618", "5626", "5634", "5642", "5650", "5658", "5676", "5674", "5682", "5710", "5698", "5706", "5714", "5732", "5730", "5738", "5746", "5754", "5762", "5870", "5878", "5786", "5794", "5802", "5820", "5818", "5826", "5834", "5842", "5860", "5858", "5876", "5874", "5982", "5890", "5908", "5906", "5914", "5932", "5930", "5938", "5946", "5954", "5962", "5970", "5978", "5986", "6094", "6102", "6020", "6018", "6026", "6034", "6042", "6060", "6058", "6076", "6074", "6082", "6110", "6098", "6106", "9698", "9706", "9714", "9732", "9730", "9738", "9746", "9754", "9762", "9870", "9878", "9786", "9794", "9802", "9820", "9818", "9826", "9834", "9842", "9860", "9858", "9876", "9874", "9982", "9890", "9908", "9906", "9914", "9922", "9930", "9938", "9946", "9954", "9962", "9970", "9978", "9986", "9994", "1103", "1021", "1019", "1027", "1035", "1043", "1051", "1059", "1067", "1075", "1083", "1091", "1109", "0107", "0215", "0123", "0141", "0139", "0147", "0165", "0163", "0181", "0179", "0187", "0195", "0203", "1025", "1043", "1041", "1049", "1057", "1065", "1073", "1081", "1089", "1097", "2105", "2213", "2121", "2129", "2137", "2145", "2153", "2161", "2169", "2187", "2185", "2193", "1201", "1209", "1217", "1325", "1243", "1241", "1249", "1257", "1265", "1273", "1281", "1289", "1297", "1305", "1313", "1321", "1329", "1437", "1345", "1353", "1361", "1369", "1387", "1385", "1393", "1401", "1409", "1417", "1425", "1443", "1541", "1549", "1457", "1465", "1473", "1481", "1489", "1497", "1505", "1513", "1521", "1529", "5121", "5129", "5137", "5145", "5153", "5161", "5169", "5187", "5185", "5193", "5201", "5209", "5217", "5325", "5243", "5241", "5249", "5257", "5265", "5273", "5281", "5289", "5297", "5305", "5313", "5321", "5329", "5437", "5345", "5353", "5361", "5369", "5387", "5385", "5393", "5401", "5409", "5417", "5425", "5443", "5541", "5949", "5457", "5465", "5473", "5481", "5489", "5497", "6505", "6513", "6521", "6529", "6537", "6545", "6653", "6561", "6569", "6587", "6585", "6593", "5601", "5609", "5617", "5625", "9217", "9325", "9243", "9241", "9249", "9257", "9265", "9273", "9281", "9289", "9297", "9305", "9313", "9321", "9329", "9437", "9345", "9353", "9361", "9369", "9387", "9385", "9393", "9401", "9409", "9417", "9425", "9433", "9541", "9549", "9457", "9465", "9473", "9481", "9489", "9497", "9505", "9513", "9521", "9529", "9537", "9545", "9653", "9561", "9569", "9587", "9585", "9593", "9601", "9609", "9617", "9625", "9643", "9641", "9649", "9657", "9765", "9673", "9681", "9689", "9697", "9705", "9713", "9721", "4314", "4432", "4430", "4438", "4346", "4354", "4372", "4370", "4378", "4386", "4394", "3402", "3420", "3418", "3426", "3434", "3542", "3450", "3458", "3476", "3484", "3482", "3510", "3498", "3506", "3514", "3532", "3530", "3538", "3546", "3654", "3562", "3570", "3578", "3586", "3594", "3602", "3620", "3618", "3626", "3634", "3642", "3650", "3658", "3676", "3674", "3682", "3710", "3698", "3706", "3714", "3732", "3730", "3738", "3746", "3754", "3762", "3870", "3878", "3786", "3794", "3802", "3820", "3818", "7420", "7418", "7426", "7434", "7542", "7450", "7458", "7476", "7484", "7482", "7510", "7498", "7506", "7514", "7532", "7530", "7538", "7546", "7654", "7562", "7570", "7578", "7586", "7594", "7602", "7620", "7618", "7626", "7634", "7642", "7650", "7658", "7676", "7674", "7682", "7710", "7698", "8706", "8714", "8732", "8730", "8738", "8746", "8754", "8762", "8870", "8878", "8786", "8794", "7802", "7820", "7818", "7826", "7834", "7842", "7860", "7858", "7876", "7874", "7982", "7890", "7908", "7906", "7914", "1507", "1515", "1523", "1541", "1539", "1547", "1565", "1563", "1571", "1579", "1587", "1595", "1603", "1621", "1619", "1627", "1635", "1643", "1651", "1659", "1767", "1675", "1683", "1691", "1709", "1707", "1715", "1723", "1731", "1739", "1747", "1765", "1763", "1871", "1879", "1787", "1795", "1803", "1821", "1819", "1827", "1835", "1843", "1851", "1859", "1867", "1875", "1983", "1891", "1909", "1907", "1915", "1923", "1941", "1939", "1947", "1965", "1963", "1981", "1979", "1987", "2095", "2103", "2021", "5603", "5621", "5619", "5627", "5635", "5643", "5651", "5659", "5767", "5675", "5683", "5691", "5709", "5707", "5715", "5723", "5731", "5739", "5747", "5765", "5763", "5871", "5879", "5787", "5795", "5803", "5821", "5819", "5827", "5835", "5843", "5851", "5859", "5867", "5875", "5983", "5891", "5909", "5907", "5915", "5923", "5941", "5939", "5947", "5965", "5963", "5981", "5979", "5987", "6095", "6103", "6021", "6019", "6027", "6035", "6043", "6051", "6059", "6067", "6075", "6083", "6091", "6109", "6107", "9709", "9707", "9715", "9723", "9731", "9739", "9747", "9765", "9763", "9871", "9879", "9787", "9795", "9803", "9821", "9819", "9827", "9835", "9843", "9851", "9859", "9867", "9875", "9983", "9891", "9909", "9907", "9915", "9923", "9941", "9939", "9947", "9965", "9963", "9971", "9979", "9987", "9995", "1104", "1012", "1030", "1028", "1036", "1054", "1052", "1070", "1068", "1076", "1084", "1092", "0210", "0108", "0216", "0124", "0132", "0150", "0148", "0156", "0164", "0172", "0190", "0198", "0196", "0204", "1026", "1034", "1042", "1060", "1058", "1076", "1074", "1082", "1110", "1098", "2106", "2214", "2132", "2130", "2138", "2146", "2154", "2162", "2170", "2178", "2186", "2194", "1202", "1320", "1218", "1326", "1234", "1242", "1260", "1258", "1276", "1274", "1282", "1310", "1298", "1306", "1314", "1432", "1430", "1438", "1346", "1354", "1372", "1370", "1378", "1386", "1394", "1402", "1420", "1418", "1426", "1434", "1542", "1450", "1458", "1476", "1484", "1482", "1510", "1498", "1506", "1514", "1532", "1530", "5132", "5130", "5138", "5146", "5154", "5162", "5170", "5178", "5186", "5194", "5202", "5320", "5218", "5326", "5234", "5242", "5260", "5258", "5276", "5274", "5282", "5310", "5298", "5306", "5314", "5432", "5430", "5438", "5346", "5354", "5372", "5370", "5378", "5386", "5394", "5402", "5420", "5418", "5426", "5434", "5542", "5450", "5458", "5476", "5484", "5482", "5510", "5498", "6506", "6514", "6532", "6530", "6538", "6546", "6654", "6562", "6570", "6578", "6586", "6594", "5602", "5620", "5618", "5626", "9218", "9326", "9234", "9242", "9260", "9258", "9276", "9274", "9282", "9310", "9298", "9306", "9314", "9432", "9430", "9438", "9346", "9354", "9372", "9370", "9378", "9386", "9394", "9402", "9420", "9418", "9426", "9434", "9542", "9450", "9458", "9476", "9484", "9482", "9510", "9498", "9506", "9514", "9532", "9530", "9538", "9546", "9654", "9562", "9570", "9578", "9586", "9594", "9602", "9620", "9618", "9626", "9634", "9642", "9650", "9658", "9676", "9674", "9682", "9710", "9698", "9706", "9714", "9732", "4315", "4323", "4431", "4439", "4347", "4365", "4363", "4371", "4379", "4387", "4395", "3403", "3421", "3419", "3427", "3435", "3543", "3451", "3459", "3467", "3475", "3483", "3491", "3509", "3507", "3515", "3523", "3541", "3539", "3547", "3565", "3563", "3571", "3579", "3587", "3595", "3603", "3621", "3619", "3627", "3635", "3643", "3651", "3659", "3767", "3675", "3683", "3691", "3709", "3707", "3715", "3723", "3731", "3739", "3747", "3765", "3763", "3871", "3879", "3787", "3795", "3803", "3821", "3819", "7421", "7419", "7427", "7435", "7543", "7451", "7459", "7467", "7475", "7483", "7491", "7509", "7507", "7515", "7523", "7541", "7539", "7547", "7565", "7563", "7571", "7579", "7587", "7595", "7603", "7621", "7619", "7627", "7635", "7643", "7651", "7659", "7767", "7675", "7683", "7691", "7709", "8707", "8715", "8723", "8731", "8739", "8747", "8765", "8763", "8871", "8879", "8787", "8795", "7803", "7821", "7819", "7827", "7835", "7843", "7851", "7859", "7867", "7875", "7983", "7891", "7909", "7907", "7915", "1508", "1516", "1524", "1532", "1540", "1548", "1656", "1574", "1572", "1590", "1598", "1596", "1604", "1612", "1620", "1628", "1636", "1654", "1652", "1760", "1768", "1686", "1684", "1692", "1710", "1708", "1716", "1724", "1732", "1740", "1748", "1756", "1764", "1872", "1790", "1798", "1796", "1804", "1812", "1830", "1828", "1836", "1854", "1852", "1870", "1868", "1876", "1984", "1892", "1910", "1908", "1916", "1924", "1932", "1950", "1948", "1956", "1964", "1972", "1980", "2098", "2096", "2104", "2012", "5604", "5612", "5620", "5628", "5636", "5654", "5652", "5760", "5768", "5686", "5684", "5692", "5710", "5708", "5716", "5724", "5732", "5740", "5748", "5756", "5764", "5872", "5790", "5798", "5796", "5804", "5812", "5830", "5828", "5836", "5854", "5852", "5870", "5868", "5876", "5984", "5892", "5910", "5908", "5916", "5924", "5932", "5950", "5948", "5956", "5964", "5972", "5980", "6098", "6096", "6104", "6012", "6030", "6028", "6036", "6054", "6052", "6070", "6068", "6076", "6084", "6092", "6210", "6108", "9710", "9708", "9716", "9724", "9732", "9740", "9748", "9756", "9764", "9872", "9790", "9798", "9796", "9804", "9812", "9830", "9828", "9836", "9854", "9852", "9870", "9868", "9876", "9984", "9892", "9910", "9908", "9916", "9924", "9932", "9950", "9948", "9956", "9964", "9972", "9980", "9998", "9996", "1105", "1013", "1021", "1029", "1037", "1045", "1053", "1061", "1069", "1087", "1085", "1093", "0101", "0109", "0217", "0125", "0143", "0141", "0149", "0157", "0165", "0173", "0181", "0189", "0197", "0205", "1027", "1035", "1043", "1051", "1059", "1067", "1075", "1083", "1091", "1109", "2107", "2115", "2123", "2141", "2139", "2147", "2165", "2163", "2181", "2179", "2187", "2195", "1203", "1221", "1219", "1327", "1235", "1243", "1251", "1259", "1267", "1275", "1283", "1291", "1309", "1307", "1315", "1323", "1431", "1439", "1347", "1365", "1363", "1371", "1379", "1387", "1395", "1403", "1421", "1419", "1427", "1435", "1543", "1451", "1459", "1467", "1475", "1483", "1491", "1509", "1507", "1515", "1523", "1541", "5123", "5141", "5139", "5147", "5165", "5163", "5181", "5179", "5187", "5195", "5203", "5221", "5219", "5327", "5235", "5243", "5251", "5259", "5267", "5275", "5283", "5291", "5309", "5307", "5315", "5323", "5431", "5439", "5347", "5365", "5363", "5371", "5379", "5387", "5395", "5403", "5421", "5419", "5427", "5435", "5543", "5451", "5459", "5467", "5475", "5483", "5491", "5509", "6507", "6515", "6523", "6541", "6539", "6547", "6565", "6563", "6571", "6579", "6587", "6595", "5603", "5621", "5619", "5627", "9219", "9327", "9235", "9243", "9251", "9259", "9267", "9275", "9283", "9291", "9309", "9307", "9315", "9323", "9431", "9439", "9347", "9365", "9363", "9371", "9379", "9387", "9395", "9403", "9421", "9419", "9427", "9435", "9543", "9451", "9459", "9467", "9475", "9483", "9491", "9509", "9507", "9515", "9523", "9541", "9539", "9547", "9565", "9563", "9571", "9579", "9587", "9595", "9603", "9621", "9619", "9627", "9635", "9643", "9651", "9659", "9767", "9675", "9683", "9691", "9709", "9707", "9715", "9723", "4316", "4324", "4432", "4350", "4348", "4356", "4364", "4372", "4390", "4398", "4396", "3404", "3412", "3430", "3428", "3436", "3454", "3452", "3460", "3468", "3476", "3484", "3492", "3510", "3508", "3516", "3524", "3532", "3540", "3548", "3656", "3574", "3572", "3590", "3598", "3596", "3604", "3612", "3620", "3628", "3636", "3654", "3652", "3760", "3768", "3686", "3684", "3692", "3710", "3708", "3716", "3724", "3732", "3740", "3748", "3756", "3764", "3872", "3790", "3798", "3796", "3804", "3812", "3830", "7412", "7430", "7428", "7436", "7454", "7452", "7460", "7468", "7476", "7484", "7492", "7510", "7508", "7516", "7524", "7532"]} \ No newline at end of file diff --git a/catalogs/radiocode-becker5.json b/catalogs/radiocode-becker5.json new file mode 100644 index 00000000..d0ed02d7 --- /dev/null +++ b/catalogs/radiocode-becker5.json @@ -0,0 +1 @@ +{"schema_version": 1, "brand": "Becker", "format": "5-digit", "description": "Becker 5-digit radio code lookup. Index = serial (0..9999); value = code.", "count": 10000, "codes": ["12111", "21118", "21116", "31124", "41132", "51141", "61148", "71156", "81164", "91172", "11181", "21188", "31196", "41114", "51112", "61121", "71128", "81136", "91144", "11152", "21161", "31168", "41176", "51184", "61192", "71211", "81218", "91216", "11224", "21232", "31241", "41248", "51256", "61264", "71272", "81281", "91288", "11296", "21314", "31312", "41321", "51328", "61336", "71344", "81352", "91361", "11368", "21376", "31384", "41392", "51411", "61418", "71416", "81424", "91432", "11441", "21448", "31456", "41464", "51472", "61481", "71488", "81496", "94611", "14196", "24114", "34112", "44121", "54128", "64136", "74144", "84152", "94161", "14168", "24176", "34184", "44192", "54211", "64218", "74216", "84224", "94232", "14241", "24248", "34256", "44264", "54272", "64281", "74288", "84296", "94314", "14312", "24321", "34328", "44336", "54344", "64352", "74361", "84368", "94376", "14384", "24362", "34411", "44418", "54416", "64424", "74432", "84441", "94448", "14456", "24464", "34472", "44481", "54488", "64496", "74514", "84512", "94521", "14528", "24536", "34544", "44552", "54561", "64568", "74576", "84584", "94592", "18696", "28192", "38211", "48218", "58216", "68224", "78232", "88241", "98248", "18256", "28264", "38272", "48281", "58288", "68296", "78314", "88312", "98321", "18328", "28336", "38344", "48352", "58361", "68368", "78376", "88384", "98392", "18411", "28418", "38416", "48424", "58432", "68441", "78448", "88456", "98464", "18472", "28481", "38488", "48496", "58514", "68512", "78521", "88528", "98536", "18544", "28552", "38561", "48568", "58576", "68584", "78592", "88611", "98618", "18616", "28624", "38632", "48641", "58648", "68656", "78664", "88672", "98681", "18688", "22792", "32288", "42296", "52314", "62312", "72321", "82328", "92336", "12344", "22352", "32361", "42368", "52376", "62384", "72392", "82411", "92418", "12416", "22424", "32432", "42441", "52448", "62456", "72464", "82472", "92481", "12488", "22496", "32514", "42512", "52521", "62528", "72536", "82544", "92552", "12561", "22568", "32576", "42584", "52592", "62611", "72618", "82616", "92624", "12632", "22641", "32648", "42656", "52664", "62672", "72681", "82688", "92696", "12714", "22712", "32721", "42728", "52736", "62744", "72752", "82761", "92768", "12776", "22784", "36888", "46384", "56392", "66411", "76418", "86416", "96424", "16432", "26441", "36448", "46456", "56464", "66472", "76481", "86488", "96496", "16514", "26512", "36521", "46528", "56536", "66544", "76552", "86561", "96568", "16576", "26584", "36592", "46611", "56618", "66616", "76624", "86632", "96641", "16648", "26656", "36664", "46672", "56681", "66688", "76696", "86714", "96712", "16721", "26728", "36736", "46744", "56752", "66761", "76768", "86776", "96784", "16792", "26811", "36818", "46816", "56824", "66832", "76841", "86848", "96856", "16864", "26872", "36881", "41984", "51481", "61488", "71496", "81514", "91512", "11521", "21528", "31536", "41544", "51552", "61561", "71568", "81576", "91584", "11592", "21611", "31618", "41616", "51624", "61632", "71641", "81648", "91656", "11664", "21672", "31681", "41688", "51696", "61714", "71712", "81721", "91728", "11736", "21744", "31752", "41761", "51768", "61776", "71784", "81792", "91811", "11818", "21816", "31824", "41832", "51841", "61848", "71856", "81864", "91872", "11881", "21888", "31896", "41914", "51912", "61921", "71928", "81936", "91944", "11952", "21961", "31968", "41976", "55181", "64576", "74584", "84592", "94611", "14618", "24616", "34624", "44632", "54641", "64648", "74656", "84664", "94672", "14681", "24688", "34696", "44714", "54712", "64721", "74728", "84736", "94744", "14752", "24761", "34768", "44776", "54784", "64792", "74811", "84818", "94816", "14824", "24832", "34841", "44848", "54856", "64864", "74872", "84881", "94888", "14896", "24914", "34912", "44921", "54928", "64936", "74944", "84952", "94961", "14968", "24976", "34984", "44992", "55111", "65118", "75116", "85124", "95132", "15141", "25148", "35156", "45164", "55172", "69176", "78672", "88681", "98688", "18696", "28714", "38712", "48721", "58728", "68736", "78744", "88752", "98761", "18768", "28776", "38784", "48792", "58811", "68818", "78816", "88824", "98832", "18841", "28848", "38856", "48864", "58872", "68881", "79888", "88896", "98914", "18912", "28921", "38928", "48936", "58944", "68952", "78961", "88968", "98976", "18984", "28992", "39111", "49118", "59116", "69124", "79132", "89141", "99148", "19156", "29164", "39172", "49181", "59188", "69196", "79114", "89112", "99121", "19128", "29136", "39144", "49152", "59161", "69168", "71514", "82111", "92119", "21117", "21125", "31133", "41141", "51149", "61157", "71165", "81173", "91181", "11189", "21197", "31115", "41113", "51121", "61129", "71137", "81145", "91153", "11161", "21169", "31177", "41185", "51193", "61211", "71219", "81217", "91225", "11233", "21241", "31249", "41257", "51265", "61273", "71281", "81289", "91217", "11315", "21313", "31321", "41329", "51337", "61345", "71353", "81361", "91369", "11377", "21385", "31393", "41411", "51419", "61417", "71425", "81433", "91441", "11449", "21457", "31465", "41473", "51481", "61489", "71497", "84611", "94197", "14115", "24113", "34121", "44129", "54137", "64145", "74153", "84161", "94169", "14177", "24185", "34193", "44211", "54219", "64217", "74225", "84233", "94241", "14249", "24257", "34265", "44273", "54281", "64289", "74297", "84315", "94313", "14321", "24329", "34337", "44345", "54353", "64361", "74369", "84377", "94385", "14393", "24411", "34419", "44417", "54425", "64433", "74441", "84449", "94457", "14465", "24473", "34481", "44489", "54497", "64515", "74513", "84521", "94529", "14537", "24545", "34553", "44561", "54569", "64577", "74585", "84593", "98697", "18193", "28211", "38219", "48217", "58225", "68233", "78241", "88249", "98257", "18265", "28273", "38281", "48289", "58297", "68315", "78313", "88321", "98329", "18337", "28345", "38353", "48361", "58369", "68377", "78385", "88393", "98411", "18419", "28417", "38425", "48433", "58441", "68449", "78457", "88465", "98473", "18481", "28489", "38497", "48515", "58513", "68521", "78529", "88537", "98545", "18553", "28561", "38569", "48577", "58585", "68593", "78611", "88619", "98617", "18625", "28633", "38641", "48649", "58657", "68665", "78673", "88681", "98689", "12793", "22289", "32297", "42315", "52313", "62329", "72321", "82337", "92345", "12353", "22369", "32361", "42377", "52385", "62393", "72411", "82419", "92417", "12425", "22433", "32441", "42449", "52457", "62465", "72473", "82481", "92489", "12497", "22515", "32513", "42521", "52529", "62537", "72545", "82553", "92561", "12569", "22577", "32585", "42593", "52611", "62619", "72617", "82625", "92633", "12641", "22649", "32657", "42665", "52673", "62681", "72689", "82697", "92715", "12713", "22721", "32729", "42737", "52745", "62753", "72761", "82769", "92777", "12785", "26889", "36385", "46393", "56411", "66419", "76417", "86425", "96433", "16441", "26449", "36457", "46465", "57473", "66481", "76489", "86497", "96515", "16513", "26521", "36529", "46537", "56545", "66553", "76561", "86569", "96577", "16585", "26593", "36611", "46619", "56617", "66625", "76633", "86641", "96649", "16657", "26665", "36673", "46681", "56689", "66697", "76715", "86713", "96721", "16721", "26737", "36745", "46753", "56761", "66769", "76777", "86785", "96793", "16811", "26819", "36817", "46825", "56833", "66841", "76849", "86857", "96865", "16873", "26881", "31985", "41481", "51489", "61497", "71515", "81513", "91521", "11529", "21537", "31545", "41553", "51561", "61569", "71577", "81585", "91513", "11611", "21619", "31617", "41625", "51633", "61641", "71649", "81657", "91665", "11673", "21681", "31689", "41697", "51715", "61713", "71721", "81729", "91737", "11745", "21753", "31761", "41769", "51777", "61785", "71793", "81811", "91819", "11817", "21825", "31833", "41841", "51849", "61857", "71865", "81873", "91881", "11889", "21897", "31915", "41913", "51921", "61929", "71937", "81945", "91953", "11961", "21969", "31977", "45181", "54577", "64585", "74593", "84611", "94619", "14617", "24625", "34633", "44641", "54649", "64657", "74665", "84673", "94681", "14689", "24697", "34715", "44713", "54721", "64729", "74737", "84745", "94753", "14761", "24769", "34777", "44785", "54793", "64811", "74819", "84817", "94825", "14833", "24841", "34849", "44857", "54865", "64873", "74881", "84889", "94897", "14915", "24913", "34921", "44929", "54937", "64945", "74953", "84961", "94969", "14977", "24985", "34993", "45111", "55119", "65117", "75125", "85133", "95141", "15149", "25157", "35165", "45173", "59177", "68673", "78681", "88689", "98697", "18715", "28713", "38721", "48729", "58737", "68745", "78753", "88761", "98769", "18777", "28785", "38793", "48811", "58819", "68817", "78825", "88833", "98841", "18849", "28857", "38865", "48873", "58881", "68889", "78897", "88915", "98913", "18921", "28929", "38937", "48945", "58953", "68961", "78969", "88977", "98985", "18993", "29111", "39119", "49117", "59125", "69133", "79141", "89149", "99157", "19165", "29173", "39181", "49189", "59197", "69115", "79113", "89121", "99129", "19137", "29145", "39153", "49161", "59169", "61515", "71112", "82111", "91118", "11126", "21134", "31142", "41151", "51158", "61166", "71174", "81182", "91191", "11198", "21116", "31114", "41122", "51131", "61138", "71146", "81154", "91162", "11171", "21178", "31186", "41194", "51212", "61211", "71218", "81226", "91234", "11242", "21251", "31258", "41266", "51274", "61282", "71291", "81298", "91316", "11314", "21322", "31331", "41338", "51346", "61354", "71362", "81371", "91378", "11386", "21394", "31412", "41411", "51418", "61426", "71434", "81442", "91451", "11458", "21466", "31474", "41482", "51491", "61498", "74612", "84198", "94116", "14114", "24122", "34131", "44138", "54146", "64154", "74162", "84171", "94178", "14186", "24194", "34212", "44211", "54218", "64226", "74234", "84242", "94251", "14258", "24266", "34274", "44282", "54291", "64298", "74316", "84314", "94322", "14331", "24338", "34346", "44354", "54362", "64371", "74378", "84386", "94394", "14412", "24411", "34418", "44426", "54434", "64442", "74451", "84458", "94466", "14474", "24482", "34491", "44498", "54516", "64514", "74522", "84531", "94538", "14546", "24554", "34562", "44571", "54578", "64586", "74594", "88698", "98194", "18212", "28211", "38218", "48226", "58234", "68242", "78251", "88258", "98266", "18274", "28282", "38291", "48298", "58316", "68314", "78322", "88331", "98338", "18346", "28354", "38362", "48371", "58378", "68386", "78394", "88412", "98411", "18418", "28426", "38434", "48442", "58451", "68458", "78466", "88474", "98482", "18491", "28498", "38516", "48514", "58522", "68531", "78538", "88546", "98554", "18562", "28571", "38578", "48586", "58594", "68612", "78611", "88618", "98626", "18634", "28642", "38651", "48658", "58666", "68674", "78682", "88691", "92794", "12291", "22298", "32316", "42314", "52322", "62331", "72338", "82346", "92354", "12362", "22371", "32378", "42386", "52394", "62412", "72411", "82418", "92426", "12434", "22442", "32451", "42458", "52466", "62474", "72482", "82491", "92498", "12516", "22514", "32522", "42531", "52538", "62546", "72554", "82562", "92571", "12578", "22586", "32594", "42612", "52611", "62618", "72626", "82634", "92642", "12651", "22658", "32666", "42674", "52682", "62691", "72698", "82716", "92714", "12722", "22731", "32738", "42746", "52754", "62762", "72771", "82778", "92786", "16891", "26386", "36394", "46412", "56411", "66418", "76426", "86434", "96442", "16451", "26458", "36466", "46474", "56482", "66491", "76418", "86516", "96514", "16522", "26531", "36538", "46546", "56554", "66562", "76571", "86578", "96586", "16594", "26612", "36611", "46618", "56626", "66634", "76642", "86651", "96658", "17666", "26674", "36682", "46691", "56698", "66716", "76714", "86722", "96731", "16738", "26746", "36754", "46762", "56771", "66778", "76786", "86794", "96812", "16811", "26818", "36826", "46834", "56842", "66851", "76858", "86866", "96874", "16882", "21986", "31482", "41491", "51498", "61516", "71514", "81522", "91531", "11538", "21546", "31554", "41562", "51571", "61578", "71586", "81594", "91612", "11611", "21618", "31626", "41634", "51642", "61651", "71658", "81666", "91674", "11682", "21691", "31698", "41716", "51714", "61722", "71731", "81738", "91746", "11754", "21762", "31771", "41778", "51786", "61794", "71812", "81811", "91818", "11826", "21834", "31842", "41851", "51858", "61866", "71874", "81882", "91891", "11898", "21916", "31914", "41922", "51931", "61938", "71946", "81954", "91962", "11971", "21978", "35182", "44578", "54586", "64594", "74612", "84611", "94618", "14626", "24634", "34642", "44651", "54658", "64666", "74674", "84682", "94691", "14698", "24716", "34714", "44722", "54731", "64738", "74746", "84754", "94762", "14771", "24778", "34786", "44794", "54812", "64811", "74818", "84826", "94834", "14842", "24851", "34858", "44866", "54874", "64882", "74891", "84898", "94916", "14914", "24922", "34931", "44938", "54946", "64954", "74962", "84971", "94978", "14986", "24994", "35112", "45111", "55118", "65126", "75134", "85142", "95151", "15158", "25166", "35174", "49178", "58674", "68682", "78691", "88698", "98716", "18714", "28722", "38731", "48738", "58746", "68754", "78762", "88771", "98778", "18786", "28794", "38812", "48811", "58818", "68826", "78834", "88842", "98851", "18858", "28866", "38874", "48882", "58891", "68898", "78916", "88914", "98922", "18931", "28938", "38946", "48954", "58962", "68971", "78978", "88986", "98994", "19112", "29111", "39118", "49126", "59134", "69142", "79151", "89158", "99166", "19174", "29182", "39191", "49198", "59116", "69114", "79122", "89131", "99138", "19146", "29154", "39162", "49171", "51178", "61113", "72111", "81119", "91127", "11135", "21143", "31151", "41159", "51167", "61175", "71183", "81191", "91199", "21117", "21115", "31123", "41131", "51139", "61147", "71155", "81163", "91171", "11179", "21187", "31195", "41213", "51211", "61219", "71227", "81235", "91243", "11251", "21259", "31267", "41275", "51283", "61291", "71299", "81317", "91315", "11323", "21331", "31339", "41347", "51355", "61363", "71371", "81379", "91387", "11395", "21413", "31411", "41419", "51427", "61435", "71443", "81451", "91459", "11467", "21475", "31483", "41491", "51499", "64613", "74199", "84117", "94115", "14123", "24131", "34139", "44147", "54155", "64163", "74171", "84179", "94187", "14195", "24213", "34211", "44219", "54227", "64235", "74243", "84251", "94259", "14267", "24275", "34283", "44291", "54299", "64317", "74315", "84323", "94331", "14339", "24347", "34355", "44363", "54371", "64379", "74387", "84395", "94413", "14411", "24419", "34427", "44435", "54443", "64451", "74459", "84467", "94475", "14483", "24491", "34499", "44517", "54515", "64523", "74531", "84539", "94547", "14555", "24563", "34571", "44579", "54587", "64595", "78699", "88195", "98213", "18211", "28219", "38227", "48235", "58243", "68251", "78259", "88267", "98275", "18283", "28291", "38299", "48317", "58315", "68323", "78331", "88339", "98347", "18355", "28363", "38371", "48379", "58387", "68395", "78413", "88411", "98419", "18427", "28435", "38443", "48451", "58459", "68467", "78475", "88483", "98491", "18499", "28517", "38515", "48523", "58531", "68539", "78547", "88555", "98563", "18571", "28579", "38587", "48595", "58613", "68611", "78619", "88627", "98635", "18643", "28651", "38659", "48667", "58675", "68683", "78691", "82795", "92291", "12299", "22317", "32315", "42323", "52331", "62339", "72347", "82355", "92363", "12371", "22379", "32387", "42395", "52413", "62411", "72419", "82427", "92435", "12443", "22451", "32459", "42467", "52475", "62483", "72491", "82499", "92517", "12515", "22523", "32531", "42539", "52547", "62555", "72563", "82571", "92579", "12587", "22595", "32613", "42611", "52619", "62627", "72635", "82643", "92651", "12659", "22667", "32675", "42683", "52691", "62699", "72717", "82715", "92723", "12731", "22739", "32747", "42755", "52763", "62771", "72779", "82787", "96891", "16387", "26395", "36413", "46411", "56419", "66427", "76435", "86443", "96451", "16459", "26467", "36475", "46483", "56491", "66499", "76517", "86515", "96523", "16531", "26539", "36547", "46555", "56563", "66571", "76579", "86587", "96595", "16613", "26611", "36619", "46627", "56635", "66643", "76651", "86659", "96667", "16675", "26683", "36691", "46699", "56717", "66715", "76723", "86731", "96739", "16747", "26755", "36763", "46771", "56779", "66787", "76795", "86813", "96811", "16819", "26827", "36835", "46843", "56851", "66859", "76867", "86875", "96883", "11987", "21483", "31491", "41499", "51517", "61515", "71523", "81531", "91539", "11547", "21555", "31563", "41571", "51579", "61587", "71595", "81613", "91611", "11619", "21627", "31635", "41643", "51651", "61659", "71667", "81675", "91683", "11691", "21699", "31717", "41715", "51723", "61731", "71739", "81747", "91755", "11763", "21771", "31779", "41787", "51795", "61813", "71811", "81819", "91827", "11835", "21843", "31851", "41859", "51867", "61875", "71883", "81891", "91899", "11917", "21915", "31923", "41931", "51939", "61947", "71955", "81963", "91971", "11979", "25183", "34579", "44587", "54595", "64613", "74611", "84619", "94627", "14635", "24643", "34651", "44659", "54667", "64675", "74683", "84691", "94699", "14717", "24715", "34723", "44731", "54739", "64747", "74755", "84763", "94771", "14779", "24787", "34795", "44813", "54811", "64819", "74827", "84835", "94843", "14851", "24859", "34867", "44875", "54883", "64891", "74899", "84917", "94915", "14923", "24931", "34939", "44947", "54955", "64963", "74971", "84979", "94987", "14995", "25113", "35111", "45119", "55127", "65135", "75143", "85151", "95159", "15167", "25175", "39179", "48675", "58683", "68691", "78699", "88717", "98715", "18723", "28731", "38739", "48747", "58755", "68763", "78771", "88779", "98787", "18795", "28813", "38811", "48819", "58827", "68835", "78843", "88851", "98859", "18867", "28875", "38883", "48891", "58899", "68917", "78915", "88923", "98931", "18939", "28947", "38955", "48963", "58971", "68979", "78987", "88995", "99113", "19111", "29119", "39127", "49135", "59143", "69151", "79159", "89167", "99175", "19183", "29191", "39199", "49117", "59115", "69123", "79131", "89139", "99147", "19155", "29163", "39171", "41517", "51114", "61112", "71121", "81128", "91136", "11144", "21152", "31161", "41168", "51176", "61184", "71192", "82111", "91118", "21116", "21124", "31132", "41141", "51148", "61156", "71164", "81172", "91181", "11188", "21196", "31214", "41212", "51221", "61228", "71236", "81244", "91252", "11261", "21268", "31276", "41284", "51292", "61311", "71318", "81316", "91324", "11332", "21341", "31348", "41356", "51364", "61372", "71381", "81388", "91396", "11414", "21412", "31421", "41428", "51436", "61444", "71452", "81461", "91468", "11476", "21484", "31492", "41511", "54614", "64111", "74118", "84116", "94124", "14132", "24141", "34148", "44156", "54164", "64172", "74181", "84188", "94196", "14214", "24212", "34221", "44228", "54236", "64244", "74252", "84261", "94268", "14276", "24284", "34292", "44311", "54318", "64316", "74324", "84332", "94341", "14348", "24356", "34364", "44372", "54381", "64388", "74396", "84414", "94412", "14421", "24428", "34436", "45444", "54452", "64461", "74468", "84476", "94484", "14492", "24511", "34518", "44516", "54524", "64532", "74541", "84548", "94556", "14564", "24572", "34581", "44588", "54596", "68711", "78196", "88214", "98212", "18221", "28228", "38236", "48244", "58252", "68261", "78268", "88276", "98284", "18292", "28311", "38318", "48316", "58324", "68332", "78341", "88348", "98356", "18364", "28372", "38381", "48388", "58396", "68414", "78412", "88421", "98428", "18436", "28444", "38452", "48461", "58468", "68476", "78484", "88492", "98511", "18518", "28516", "38524", "48532", "58541", "68548", "78556", "88564", "98572", "18581", "28588", "38596", "48614", "58612", "68621", "78628", "88636", "98644", "18652", "28661", "38668", "48676", "58684", "68692", "72796", "82292", "92311", "12318", "22316", "32324", "42332", "52341", "62348", "72356", "82364", "92372", "12381", "22388", "32396", "42414", "52412", "62421", "72428", "82436", "92444", "12452", "22461", "32468", "42476", "52484", "62492", "72511", "82518", "92516", "12524", "22532", "32541", "42548", "52556", "62564", "72572", "82581", "92588", "12596", "22614", "32612", "42621", "52628", "62636", "72644", "82652", "92661", "12668", "22676", "32684", "42612", "52711", "62718", "72716", "82724", "92732", "12741", "22748", "32756", "42764", "52772", "62781", "72788", "86892", "96388", "16396", "26414", "36412", "46421", "56428", "66436", "76444", "86452", "96461", "16468", "26476", "36484", "46492", "56511", "66518", "76516", "86524", "96532", "16541", "26548", "36556", "46564", "56572", "66581", "76588", "86596", "96614", "16612", "26621", "36628", "46636", "56644", "66652", "76661", "86668", "96676", "16684", "26692", "36711", "46718", "56716", "66724", "76732", "86741", "96748", "16756", "26764", "36772", "46781", "56788", "66796", "76814", "86812", "96821", "16828", "26836", "36844", "46852", "56861", "66868", "76876", "86884", "91988", "11484", "21492", "31511", "41518", "51516", "61524", "71532", "81541", "91548", "11556", "21564", "31572", "41581", "51588", "61596", "71614", "81612", "91621", "11628", "21636", "31644", "41652", "51661", "61668", "71676", "81684", "91692", "11711", "21718", "31716", "41724", "51732", "61741", "71748", "81756", "91764", "11772", "21781", "31788", "41796", "51814", "61812", "71821", "81828", "91836", "11844", "21852", "31861", "41868", "51876", "61884", "71892", "81911", "91918", "11916", "21924", "31932", "41941", "51948", "61956", "71964", "81972", "91981", "15184", "24581", "34588", "44596", "54614", "64612", "74621", "84628", "94636", "14644", "24652", "34661", "44668", "54676", "64684", "74692", "84711", "94718", "14716", "24724", "34732", "44741", "54748", "64756", "74764", "84772", "94781", "14788", "24796", "34814", "44812", "54821", "64828", "74836", "84844", "94852", "14861", "24868", "34876", "44884", "54892", "64911", "74918", "84916", "94924", "14932", "24941", "34948", "44956", "54964", "64972", "74981", "84988", "94996", "15114", "25112", "35121", "45128", "55136", "65144", "75152", "85161", "95168", "15176", "29181", "38676", "48684", "58692", "68711", "78718", "88716", "98724", "18732", "28741", "38748", "48756", "58764", "68772", "78781", "88788", "98716", "18814", "28812", "38821", "48828", "58836", "68844", "78852", "88861", "98868", "18876", "28884", "38892", "48911", "58918", "68916", "78924", "88932", "98941", "18948", "28956", "38964", "48972", "58981", "68988", "78996", "89114", "99112", "19121", "29128", "39136", "49144", "59152", "69161", "79168", "89176", "99184", "19192", "29111", "39118", "49116", "59124", "69132", "79141", "89148", "99156", "19164", "29172", "31181", "41115", "51113", "61121", "71129", "81137", "91145", "11153", "21161", "31169", "41177", "51185", "61193", "72111", "82119", "91117", "11125", "21133", "31141", "41149", "51157", "61165", "71173", "81181", "91189", "11197", "21215", "31213", "41221", "51229", "61237", "71245", "81253", "91261", "11269", "21277", "31285", "41293", "51311", "61319", "71317", "81325", "91333", "11341", "21349", "31357", "41365", "51373", "61381", "71389", "81397", "91415", "11413", "21421", "31429", "41437", "51445", "61453", "71461", "81469", "91477", "11485", "21493", "31511", "44615", "54111", "64119", "74117", "84125", "94133", "14141", "24149", "34157", "44165", "54173", "64181", "74189", "84197", "94215", "14213", "24221", "34229", "44237", "54245", "64253", "74261", "84269", "94277", "14285", "24293", "34311", "44319", "54317", "64325", "74333", "84341", "94349", "14357", "24365", "34373", "44381", "54389", "64397", "74415", "84413", "94421", "14429", "24437", "34445", "44453", "54461", "64469", "74477", "84485", "94493", "14511", "24519", "34517", "44525", "54533", "64541", "74549", "84557", "94565", "14573", "24581", "34589", "44597", "58711", "68197", "78215", "88213", "98221", "18229", "28237", "38245", "48253", "58261", "68269", "78277", "88285", "98293", "18311", "28319", "38317", "48325", "58333", "68341", "78349", "88357", "98365", "18373", "28381", "38389", "48397", "58415", "68413", "78421", "88429", "98437", "18445", "28453", "38461", "48469", "58477", "68485", "78493", "88511", "98519", "18517", "28525", "38533", "48541", "58549", "68557", "78565", "88573", "98581", "18589", "28597", "38615", "48613", "58621", "68629", "78637", "88645", "98653", "18661", "28669", "38677", "48685", "58693", "62797", "72293", "82311", "92319", "12317", "22325", "32333", "42341", "52349", "62357", "72365", "82373", "92381", "12389", "22397", "32415", "42413", "52421", "62429", "72437", "82445", "92453", "12461", "22469", "32477", "42485", "52493", "62511", "72519", "82517", "92525", "12533", "22541", "32549", "42557", "52565", "62573", "72581", "82589", "92597", "12615", "22613", "32621", "42629", "52637", "62645", "72653", "82661", "92669", "12677", "22685", "32693", "42711", "52719", "62717", "72725", "82733", "92741", "12749", "22757", "32765", "42773", "52781", "62789", "76893", "86389", "96397", "16415", "26413", "36421", "46429", "56437", "66445", "76453", "86461", "96469", "16477", "26485", "36493", "46511", "56519", "66517", "76525", "86533", "96541", "16549", "26557", "36565", "46573", "56581", "66589", "76597", "86615", "96613", "16621", "26629", "36637", "46645", "56653", "76661", "76669", "86677", "96685", "16693", "26711", "36719", "46717", "56725", "66733", "76741", "86749", "96757", "16765", "26773", "36781", "46789", "56797", "66815", "76813", "86821", "96829", "16837", "26845", "36853", "46861", "56869", "66877", "76885", "81989", "91485", "11493", "21511", "31519", "41517", "51525", "61533", "71541", "81549", "91557", "11565", "21573", "31581", "41589", "51597", "61615", "71613", "81621", "91629", "11637", "21645", "31653", "41661", "51669", "61677", "71685", "81693", "91711", "11719", "21717", "31725", "41733", "51741", "61749", "71757", "81765", "91773", "11781", "21789", "31797", "41815", "51813", "61821", "71829", "81837", "91845", "11853", "21861", "31869", "41877", "51885", "61893", "71911", "81919", "91917", "11925", "21933", "31941", "41949", "51957", "61965", "71973", "81981", "95185", "14581", "24589", "34597", "44615", "54613", "64621", "74629", "84637", "94645", "14653", "24661", "34669", "44677", "54685", "64693", "74711", "84719", "94717", "14725", "24733", "34741", "44749", "54757", "64765", "74773", "84781", "94789", "14797", "24815", "34813", "44821", "54829", "64837", "74845", "84853", "94861", "14869", "24877", "34885", "44893", "54911", "64919", "74917", "84925", "94933", "14941", "24949", "34957", "44965", "54973", "64981", "74989", "84997", "95115", "15113", "25121", "35129", "45137", "55145", "65153", "75161", "85169", "95177", "15185", "28677", "38685", "48693", "58711", "68719", "78717", "88725", "98733", "18741", "28749", "38757", "48765", "58773", "68781", "78789", "88797", "98815", "18813", "28821", "38829", "48837", "58845", "68853", "78861", "88869", "98877", "18885", "28893", "38911", "48919", "58917", "68925", "78933", "88941", "98949", "18957", "28965", "38973", "48981", "58989", "68997", "79115", "89113", "99121", "19129", "29137", "39145", "49153", "59161", "69169", "79177", "89185", "99193", "19111", "29119", "39117", "49125", "59133", "69141", "79149", "89157", "99165", "19173", "21519", "31116", "41114", "51122", "61131", "71138", "81146", "91154", "11162", "21171", "31178", "41186", "51194", "61112", "72111", "81118", "91126", "11134", "21142", "31151", "41158", "51166", "61174", "71182", "81191", "91198", "11216", "21214", "31222", "41231", "51238", "61246", "71254", "81262", "91271", "11278", "21286", "31294", "41312", "51311", "61318", "71326", "81334", "91342", "11351", "21358", "31366", "41374", "51382", "61391", "71398", "81416", "91414", "11422", "21431", "31438", "41446", "51454", "61462", "71471", "81478", "91486", "11494", "21512", "34616", "44112", "54111", "64118", "74126", "84134", "94142", "14151", "24158", "34166", "44174", "54182", "64191", "74198", "84216", "94214", "14222", "24231", "34238", "44246", "54254", "64262", "74271", "84278", "94286", "14294", "24312", "34311", "44318", "54326", "64334", "74342", "84351", "94358", "14366", "24374", "34382", "44391", "54398", "64416", "74414", "84422", "94431", "14438", "24446", "34454", "44462", "54471", "64478", "74486", "84494", "94512", "14511", "24518", "34526", "44534", "54542", "64551", "74558", "84566", "94574", "14582", "24591", "34598", "48712", "58198", "68216", "78214", "88222", "98231", "18238", "28246", "38254", "48262", "58271", "68278", "78286", "88294", "98312", "18311", "28318", "38326", "48334", "58342", "68351", "78358", "88366", "98374", "18382", "28391", "38398", "48416", "58414", "68422", "78431", "88438", "98446", "18454", "28462", "38471", "48478", "58486", "68494", "78512", "88511", "98518", "18526", "28534", "38542", "48551", "58558", "68566", "78574", "88582", "98591", "18598", "28616", "38614", "48622", "58631", "68638", "78646", "88654", "98662", "14671", "28678", "38686", "48694", "52798", "62294", "72312", "82311", "92318", "12326", "22334", "32342", "42351", "52358", "62366", "72374", "82382", "92391", "12398", "22416", "32414", "42422", "52431", "62438", "72446", "82454", "92462", "12471", "22478", "32486", "42494", "52512", "62511", "72518", "82526", "92534", "12542", "22551", "32558", "42566", "52574", "62582", "72591", "82598", "92616", "12614", "22622", "32631", "42638", "52646", "62654", "72662", "82671", "92678", "12686", "22694", "32712", "42711", "52718", "62726", "72734", "82742", "92751", "12758", "22766", "32774", "42782", "52791", "66894", "76391", "86398", "96416", "16414", "26422", "36431", "46438", "56446", "66454", "76462", "86471", "96478", "16486", "26494", "36512", "46511", "56518", "66526", "76534", "86542", "96551", "16558", "26566", "36574", "46582", "56591", "66598", "76616", "86614", "96622", "16631", "26638", "36646", "46654", "56662", "66671", "76678", "86686", "96694", "16712", "26711", "36718", "46726", "56734", "66742", "76751", "86758", "96766", "16774", "26782", "36791", "46798", "56816", "66814", "76822", "86831", "96838", "16846", "26854", "36862", "46871", "56878", "66886", "71991", "81486", "91494", "11512", "21511", "31518", "41526", "51534", "61542", "71551", "81558", "91566", "11574", "21582", "31591", "41598", "51616", "61614", "71622", "81631", "91638", "11646", "21654", "31662", "41671", "51678", "61686", "71694", "81712", "91711", "11718", "21726", "31734", "41742", "51751", "61758", "71766", "81774", "91782", "11791", "21798", "31816", "41814", "51822", "61831", "71838", "81846", "91854", "11862", "21871", "31878", "41886", "51894", "61912", "72911", "81918", "91926", "11934", "21942", "31951", "41958", "51966", "61974", "71982", "85186", "94582", "14591", "24598", "34616", "44614", "54622", "64631", "74638", "84646", "94654", "14662", "24671", "34678", "44686", "54694", "64712", "74711", "84718", "94726", "14734", "24742", "34751", "44758", "54766", "64774", "74782", "84791", "94798", "14816", "24814", "34822", "44831", "54838", "64846", "74854", "84862", "94871", "14878", "24886", "34894", "44912", "54911", "64918", "74926", "84934", "94942", "14951", "24958", "34966", "44974", "54982", "64991", "74998", "85116", "95114", "15122", "25131", "35138", "45146", "55154", "65162", "75171", "85178", "99182", "18678", "28686", "38694", "48712", "58711", "68718", "78726", "88734", "98742", "18751", "28758", "38766", "48774", "58782", "68791", "78798", "88816", "98814", "18822", "28831", "38838", "48846", "58854", "68862", "78871", "88878", "98886", "18894", "28912", "38911", "48918", "58926", "68934", "78942", "88951", "98958", "18966", "28974", "38982", "48991", "58998", "69116", "79114", "89122", "99131", "19138", "29146", "39154", "49162", "59171", "69178", "79186", "89194", "99112", "19111", "29118", "39126", "49134", "59142", "69151", "79158", "89166", "99174", "11511", "21117", "31115", "41123", "51131", "61139", "71147", "81155", "91163", "11171", "21179", "31187", "41195", "51113", "62111", "71119", "81127", "91135", "11143", "21151", "31159", "41167", "51175", "61183", "71191", "81199", "91217", "11215", "21223", "31231", "41239", "51247", "61255", "71263", "81271", "91279", "11287", "21295", "31313", "41311", "51319", "61327", "71335", "81343", "91351", "11359", "21367", "31375", "41383", "51391", "61399", "71417", "81415", "91423", "11431", "21439", "31447", "41455", "51463", "61471", "71479", "81487", "91495", "11513", "24617", "34113", "44111", "54119", "64127", "74135", "84143", "94151", "14159", "24167", "34175", "44183", "54191", "64199", "74217", "84215", "94223", "14231", "24239", "34247", "44255", "54263", "64271", "74279", "84287", "94295", "14313", "24311", "34319", "44327", "54335", "64343", "74351", "84359", "94367", "14375", "24383", "34391", "44399", "54417", "64415", "74423", "84431", "94439", "14447", "24455", "34463", "44471", "54479", "64487", "74495", "84513", "94511", "14519", "24527", "34535", "44543", "54551", "64559", "74567", "84575", "94583", "14591", "24599", "38713", "48199", "58217", "68215", "78223", "88231", "98239", "18247", "28255", "38263", "48271", "58279", "68287", "78295", "88313", "98311", "18319", "28327", "38335", "48343", "58351", "68359", "78367", "88375", "98383", "18391", "28399", "38417", "48415", "58423", "68431", "78439", "88447", "98455", "18463", "28471", "38479", "48487", "58495", "68513", "78511", "88519", "98527", "18535", "28543", "38551", "48559", "58567", "68575", "78583", "88591", "98599", "18617", "28615", "38623", "48631", "58639", "68647", "78655", "88663", "98671", "18679", "28687", "38695", "42799", "52295", "62313", "72311", "82319", "92327", "12335", "22343", "32351", "42359", "52367", "62375", "72383", "82391", "92399", "12417", "22415", "32423", "42431", "52439", "62447", "72455", "82463", "92471", "12179", "22487", "32495", "42513", "52511", "62519", "72527", "82535", "92543", "12551", "22559", "32567", "42575", "52583", "62591", "72599", "82617", "92615", "12623", "22631", "32639", "42647", "52655", "62663", "72671", "82679", "92687", "12695", "22713", "32711", "42719", "52727", "62735", "72743", "82751", "92759", "12767", "22775", "32783", "42791", "56895", "66391", "76399", "86417", "96415", "16423", "26431", "36439", "46447", "56455", "66463", "76471", "86479", "96487", "16495", "26513", "36511", "46519", "56527", "66535", "76543", "86551", "96559", "16567", "26575", "36583", "46591", "56599", "66617", "76615", "86623", "96631", "16639", "26647", "36655", "46663", "56671", "66679", "76687", "86695", "96713", "16711", "26719", "36727", "46735", "56743", "66751", "76759", "86767", "96775", "16783", "26791", "36799", "46817", "56815", "66823", "76831", "86839", "96847", "16855", "26863", "36871", "46879", "56887", "61991", "71487", "81495", "91513", "11511", "21519", "31527", "41535", "51543", "61551", "71559", "81567", "91575", "11583", "21591", "31599", "41617", "51615", "61623", "71631", "81639", "91647", "11655", "21663", "31671", "41679", "51687", "61695", "71713", "81711", "91719", "11727", "21735", "31743", "41751", "51759", "61767", "71775", "81783", "91791", "11799", "21817", "31815", "41823", "51831", "61839", "71847", "81855", "91863", "11871", "21879", "31887", "41895", "51913", "61911", "71919", "81927", "91935", "11943", "21951", "31959", "41967", "51975", "61983", "75187", "84583", "94591", "14599", "24617", "34615", "44623", "54631", "64639", "74647", "84655", "94663", "14671", "24679", "34687", "44695", "54713", "64711", "74719", "84727", "94735", "14743", "24751", "34759", "44767", "54775", "64783", "74791", "84799", "94817", "14815", "24823", "34831", "44839", "54847", "64855", "74863", "84871", "94879", "14887", "24895", "34913", "44911", "54919", "64927", "74935", "84943", "94951", "14959", "24967", "34975", "44983", "54991", "64999", "75117", "85115", "95123", "15131", "25139", "35147", "45155", "55163", "65171", "75179", "89183", "98679", "18687", "28695", "38713", "48711", "58719", "68727", "78735", "88743", "98751", "18759", "28767", "38775", "48783", "58791", "68799", "78817", "88815", "98823", "18831", "28831", "38847", "48855", "58863", "68871", "78879", "88887", "98895", "18913", "28911", "38919", "48927", "58935", "68943", "78951", "88959", "98967", "18175", "28183", "38991", "48999", "59117", "69115", "79123", "89131", "99139", "19147", "29155", "39163", "49171", "59179", "69187", "79195", "89113", "99111", "19119", "29127", "39135", "49143", "59151", "69159", "79167", "89175", "91511", "11512", "21521", "31528", "41536", "51544", "61552", "71561", "81568", "91576", "11584", "21592", "31611", "41618", "51616", "61624", "71632", "81641", "91648", "11656", "21664", "31672", "41681", "51688", "61696", "71714", "81712", "91721", "11728", "21736", "31744", "41752", "51761", "61768", "71776", "81784", "91792", "11811", "21818", "31816", "41824", "51832", "61841", "71848", "81856", "91864", "11872", "21881", "31888", "41896", "51914", "61912", "71921", "81928", "91936", "11944", "21952", "31961", "41968", "51976", "61984", "71992", "82111", "91118", "15112", "24618", "34616", "44624", "54632", "64641", "74648", "84656", "94664", "14672", "24681", "34688", "44696", "54714", "64712", "74721", "84728", "94332", "14744", "24752", "34761", "44768", "54776", "64784", "74792", "84811", "94818", "14816", "24824", "34832", "44841", "54848", "64856", "74864", "84872", "94881", "14888", "24896", "34914", "44912", "54921", "64928", "74936", "84944", "94952", "14961", "24968", "34976", "44984", "54992", "65111", "75118", "85116", "95124", "15132", "25141", "35148", "45156", "55164", "65172", "75181", "85188", "95196", "15114", "29218", "38714", "48712", "58721", "68728", "78736", "88744", "98752", "18761", "28768", "38776", "48784", "58792", "68811", "78818", "88816", "98824", "18832", "28841", "38848", "48856", "58864", "68872", "78881", "89888", "98896", "18914", "28912", "38921", "48928", "58936", "68944", "78952", "88961", "98968", "18976", "28984", "38992", "49111", "59118", "69116", "79124", "89132", "99141", "19148", "29156", "39164", "49172", "59181", "69188", "79196", "89114", "99112", "19121", "29128", "39136", "49144", "59152", "69161", "79168", "89176", "99184", "19192", "29211", "33314", "42811", "52818", "62816", "72824", "82832", "92841", "12848", "22856", "32864", "42872", "52881", "62888", "72896", "82914", "92912", "12921", "22928", "32936", "42944", "52952", "62961", "72968", "82976", "92984", "12992", "23111", "33118", "43116", "53124", "63132", "73141", "83148", "93156", "13164", "23172", "33181", "43188", "53196", "63114", "73112", "83121", "93128", "13136", "23144", "33152", "43161", "53168", "63176", "73184", "83192", "93211", "13218", "23216", "33224", "43232", "53241", "63248", "73256", "83264", "93272", "13281", "23288", "33296", "47411", "56896", "66914", "76912", "86921", "96928", "16936", "26944", "36952", "46961", "56968", "66976", "76984", "86992", "97111", "17118", "27116", "37124", "47132", "57141", "67148", "77156", "87164", "97172", "17181", "27188", "37196", "47114", "57112", "67121", "77128", "87136", "97144", "17152", "27161", "37168", "47176", "57184", "67192", "77211", "87218", "97216", "17224", "27232", "37241", "47248", "57256", "67264", "77272", "87281", "97288", "17296", "27314", "37312", "47321", "57328", "67336", "77344", "87352", "97361", "17368", "27376", "37384", "47392", "51496", "61992", "72111", "81118", "91116", "11124", "21132", "31141", "41148", "51156", "61164", "71172", "81181", "91188", "11196", "21114", "31112", "41121", "51128", "61136", "71144", "81152", "91161", "11168", "21176", "31184", "41192", "51211", "61218", "71216", "81224", "91232", "11241", "21248", "31256", "41264", "51272", "61281", "71288", "81296", "91314", "11312", "21321", "31328", "41336", "51344", "61352", "71361", "81368", "91376", "11384", "21392", "31411", "41418", "51416", "61424", "71432", "81441", "91448", "11456", "21464", "31472", "41481", "51488", "65592", "75188", "85196", "95114", "15112", "25121", "35128", "45136", "55144", "65152", "75161", "85168", "95176", "15184", "25192", "35211", "45218", "55216", "65224", "75232", "85241", "95248", "15256", "25264", "35272", "45281", "55288", "65296", "75314", "85312", "95321", "15328", "25336", "35344", "45352", "55361", "65368", "75376", "85384", "95392", "15411", "25418", "35416", "45424", "55432", "65441", "75448", "85456", "95464", "15472", "25481", "35488", "45496", "55514", "65512", "75521", "85528", "95536", "15544", "25552", "35561", "45568", "55576", "65584", "79688", "89184", "99192", "19211", "29218", "39216", "49224", "59232", "69241", "79248", "89256", "99264", "19272", "29281", "39288", "49296", "59314", "69312", "79321", "89328", "99336", "19344", "29352", "39361", "49368", "59376", "69384", "79392", "89411", "99418", "19416", "29424", "39432", "49441", "59448", "69456", "79464", "89472", "99481", "19488", "29496", "39514", "49512", "59521", "69528", "79536", "89544", "99552", "19561", "29568", "39576", "49584", "59592", "69611", "79618", "89616", "99624", "19632", "29641", "39648", "49656", "59664", "69672", "79681", "81116", "91513", "11521", "21529", "31537", "41545", "51553", "61561", "71569", "81577", "91585", "11593", "21611", "31619", "41617", "51625", "61633", "71641", "81649", "91657", "11665", "21673", "31681", "41689", "51697", "61715", "71713", "81721", "91729", "11737", "21745", "31753", "41761", "51769", "61777", "71785", "81793", "91811", "11819", "21817", "31825", "41833", "51841", "61849", "71857", "81865", "91873", "11881", "21889", "31897", "41915", "51913", "61921", "71921", "81937", "91945", "11953", "21961", "31969", "41977", "51985", "61993", "72111", "81119", "95113", "14619", "24617", "34625", "44633", "54641", "64649", "74657", "84665", "94673", "14681", "24689", "34697", "44715", "54713", "64721", "74729", "84737", "94745", "14753", "24761", "34769", "44777", "54785", "64793", "74811", "84819", "94817", "14825", "24833", "34841", "44849", "54857", "64865", "74873", "84881", "94889", "14897", "24915", "34913", "44921", "54929", "64937", "74945", "84953", "94961", "14969", "24977", "34985", "44993", "55111", "65119", "75117", "85125", "95133", "15141", "25149", "35157", "45165", "55173", "65181", "75189", "85197", "95115", "19219", "28715", "38713", "48721", "58729", "68737", "78745", "88753", "98761", "18769", "28777", "38785", "48793", "58811", "68819", "78817", "88825", "98833", "18841", "28849", "38857", "48865", "58873", "68881", "78889", "88897", "98915", "18913", "28921", "38929", "48937", "58945", "68953", "78961", "88969", "98977", "18985", "28993", "39111", "49119", "59117", "69125", "79133", "89141", "99149", "19157", "29165", "39173", "49181", "59189", "69197", "79115", "89113", "99121", "19129", "29137", "39145", "49153", "59161", "69169", "79177", "89185", "99193", "19211", "23315", "32811", "42819", "52817", "62825", "72833", "82841", "92849", "12857", "22865", "32873", "42881", "52889", "62897", "72915", "82913", "92921", "12929", "22937", "32945", "42953", "52961", "62969", "72977", "82985", "92993", "13111", "23119", "33117", "43125", "53133", "63141", "73149", "83157", "93165", "13173", "23181", "33189", "43197", "53115", "63113", "73121", "83129", "93137", "13145", "23153", "33161", "43169", "53177", "63185", "73193", "83211", "93219", "13217", "23225", "33233", "43241", "53249", "63257", "73265", "83273", "93281", "13289", "23297", "37411", "46897", "56915", "66913", "76921", "86929", "96937", "16945", "26953", "36961", "46969", "56977", "66985", "76993", "87111", "97119", "17117", "27125", "37133", "47141", "57149", "67157", "77165", "87173", "97181", "17189", "27197", "37115", "47113", "57121", "67129", "77137", "87145", "97153", "17161", "27169", "37177", "47185", "57193", "67211", "77219", "87217", "97225", "17233", "27241", "37249", "47257", "57265", "67273", "77281", "87289", "97297", "17315", "27313", "37321", "47329", "57337", "67345", "77353", "87361", "97369", "17377", "27385", "37393", "41497", "51993", "62111", "72119", "81117", "91125", "11133", "21141", "31149", "41157", "51165", "61173", "71181", "81189", "91197", "21115", "21113", "31121", "41129", "51137", "61145", "71153", "81161", "91169", "11177", "21185", "31193", "41211", "51219", "61217", "71225", "81233", "91241", "11249", "21257", "31265", "41273", "51281", "61289", "71297", "81315", "91313", "11321", "21329", "31337", "41345", "51353", "61361", "71369", "81377", "91385", "11393", "21411", "31419", "41417", "51425", "61433", "71441", "81449", "91457", "11465", "21473", "31481", "41489", "55593", "65189", "75197", "85115", "95113", "15121", "25129", "35137", "45145", "55153", "65161", "75169", "85177", "95185", "15193", "25211", "35219", "45217", "55225", "65233", "75241", "85249", "95257", "15265", "25273", "35281", "45289", "55297", "65315", "75313", "85321", "95329", "15337", "25345", "35353", "45361", "55369", "65377", "75385", "85393", "95411", "15419", "25417", "35425", "45433", "55441", "65449", "75457", "85465", "95473", "15481", "25489", "35497", "45515", "55513", "65521", "75529", "85537", "95545", "15553", "25561", "35569", "45577", "55585", "69689", "79185", "89193", "99211", "19219", "29217", "39225", "49233", "59241", "69249", "79257", "89265", "99273", "19281", "29289", "39297", "49315", "59313", "69321", "79329", "89337", "99345", "19353", "29361", "39369", "49377", "59385", "69313", "79411", "89419", "99417", "19425", "29433", "39441", "49449", "59457", "69465", "79473", "89481", "99489", "19497", "29515", "39513", "49521", "59529", "69537", "79545", "89553", "99561", "19569", "29577", "39585", "49593", "59611", "69619", "79617", "89625", "99633", "19641", "29649", "39657", "49665", "59673", "69681", "71117", "81514", "91522", "11531", "21538", "31546", "41554", "51562", "61571", "71578", "81586", "91514", "11612", "21611", "31618", "41626", "51634", "61642", "71651", "81658", "91666", "11674", "21682", "31691", "41698", "51716", "61714", "71722", "81731", "91738", "11746", "21754", "31762", "41771", "51778", "61786", "71794", "81812", "91811", "11818", "21826", "31834", "41842", "51851", "61858", "71866", "81874", "91882", "11891", "21898", "31916", "41914", "51922", "61931", "71938", "81946", "91954", "11962", "21971", "31978", "41986", "51994", "61112", "72111", "85114", "94611", "14618", "24626", "34634", "44642", "54651", "64658", "74666", "84674", "94682", "14691", "24698", "34716", "44714", "54722", "64731", "74738", "84746", "94754", "14762", "24771", "34778", "44786", "54794", "64812", "74811", "84818", "94826", "14834", "24842", "34851", "44858", "54866", "64874", "74882", "84891", "94898", "14916", "24914", "34922", "44931", "54938", "64946", "74954", "84962", "94971", "14978", "24986", "34994", "45112", "55111", "65118", "75126", "85134", "95142", "15151", "25158", "35166", "45174", "55182", "65191", "75198", "85116", "99211", "18716", "28714", "38722", "48731", "58738", "68746", "78754", "88762", "98771", "18778", "28786", "38794", "48812", "58811", "68818", "78826", "88834", "98842", "18851", "28858", "38866", "48874", "58882", "68891", "78898", "88916", "98114", "18922", "28931", "38938", "48946", "58954", "68962", "78971", "88978", "98986", "18994", "29112", "39111", "49118", "59126", "69134", "79142", "89151", "99158", "19166", "29174", "39182", "49191", "59198", "69116", "79114", "89122", "99131", "19138", "29146", "39154", "49162", "59171", "69178", "79186", "89194", "99212", "13316", "22812", "32811", "42818", "52826", "62834", "72842", "82851", "92858", "12866", "22874", "32882", "42891", "52898", "62916", "72914", "82922", "92931", "12938", "22946", "32954", "42962", "52971", "62978", "72986", "82994", "93112", "13111", "23118", "33126", "43134", "53142", "63151", "73158", "83166", "93174", "13182", "23191", "33198", "43116", "53114", "63122", "73131", "83138", "93146", "13154", "23162", "33171", "43178", "53186", "63194", "73212", "83211", "93218", "13226", "23234", "33242", "43251", "53258", "63266", "73274", "83282", "93291", "13298", "27412", "36898", "46916", "56914", "66922", "76931", "86938", "96946", "16954", "26962", "36971", "46978", "56986", "66994", "77112", "87111", "97118", "17126", "27134", "37142", "47151", "57158", "67166", "77174", "87182", "97191", "17198", "27116", "37114", "47122", "57131", "67138", "77146", "87154", "97162", "17171", "27178", "37186", "47194", "57212", "67211", "77218", "87226", "97234", "17242", "27251", "37258", "47266", "57274", "67282", "77291", "87298", "97316", "17314", "27322", "37331", "47338", "57346", "67354", "77362", "87371", "97378", "17386", "27394", "31498", "41994", "51112", "62111", "71118", "81126", "91134", "11142", "21151", "31158", "41166", "51174", "61182", "72191", "81198", "91116", "11114", "21122", "31131", "41138", "51146", "61154", "71162", "81171", "91178", "11186", "21194", "31212", "41211", "51218", "61226", "71234", "81242", "91251", "11258", "21266", "31274", "41282", "51291", "61298", "71316", "81314", "91322", "11331", "21338", "31346", "41354", "51362", "61371", "71378", "81386", "91394", "11412", "21411", "31418", "41426", "51434", "61442", "71451", "81458", "91466", "11474", "21482", "31491", "45594", "55191", "65198", "75116", "85114", "95122", "15131", "25138", "35146", "45154", "55162", "65171", "75178", "85186", "95194", "15212", "25211", "35218", "45226", "55234", "65242", "75251", "85258", "95266", "15274", "25282", "35291", "45298", "55316", "65314", "75322", "85331", "95338", "15346", "25354", "35362", "45371", "55378", "65386", "75394", "85412", "95411", "15418", "25426", "35434", "45442", "55451", "65458", "75466", "85474", "95482", "15491", "25498", "35516", "45514", "55522", "65531", "75538", "85546", "95554", "15562", "25571", "35578", "45586", "59691", "69186", "79194", "89212", "99211", "19218", "29226", "39234", "49242", "59251", "69258", "79266", "89274", "99282", "19291", "29298", "39316", "49314", "59322", "69331", "79338", "89346", "99354", "19362", "29371", "39378", "49386", "59394", "69412", "79411", "89418", "99426", "19434", "29442", "39451", "49458", "59466", "69474", "79482", "89491", "99498", "19516", "29514", "39522", "49531", "59538", "69546", "79554", "89562", "99571", "19578", "29586", "39594", "49612", "59611", "69618", "79626", "89634", "99642", "19651", "29658", "39666", "49674", "59682", "61118", "71515", "81523", "91531", "11539", "21547", "31555", "41563", "51571", "61579", "71587", "81595", "91613", "11611", "21619", "31627", "41635", "51643", "61651", "71659", "81667", "91675", "11683", "21691", "31699", "41717", "51715", "61723", "71731", "81739", "91747", "11755", "21763", "31771", "41779", "51787", "61795", "71813", "81811", "91819", "11827", "21835", "31843", "41851", "51859", "61867", "71875", "81883", "91891", "11899", "21917", "31915", "41923", "51931", "61939", "71947", "81955", "91963", "11971", "21979", "31987", "41995", "51113", "62111", "75115", "84611", "94619", "14627", "24635", "34643", "44651", "54651", "64667", "74675", "84683", "94691", "14699", "24717", "34715", "44723", "54731", "64739", "74747", "84755", "94763", "14771", "24779", "34787", "44795", "54813", "64811", "74819", "84827", "94835", "14843", "24851", "34859", "44867", "58475", "64883", "74891", "84899", "94917", "14915", "24923", "34931", "44939", "54947", "64955", "74963", "84971", "94979", "14987", "24995", "35113", "45111", "55119", "65127", "75135", "85143", "95151", "15159", "25167", "35175", "45183", "55191", "65199", "75117", "89211", "98717", "18715", "28723", "38731", "48739", "58747", "68755", "78763", "88771", "98779", "18787", "28795", "38813", "48811", "58819", "68827", "78835", "88843", "98851", "18859", "28867", "38875", "48883", "58891", "68899", "78917", "88915", "98923", "18931", "28939", "38947", "48955", "58963", "68971", "78979", "88987", "98995", "19113", "29111", "39119", "49127", "59135", "69143", "79151", "89159", "99167", "19175", "29183", "39191", "49199", "59117", "69115", "79123", "89131", "99139", "19147", "29155", "39163", "49171", "59179", "69187", "79195", "89213", "93317", "12813", "22811", "32819", "42827", "52835", "62843", "72851", "82859", "92867", "12875", "22883", "32891", "42899", "52917", "62915", "72923", "82931", "92939", "12947", "22955", "32963", "42971", "52979", "62987", "72995", "83113", "93111", "13119", "23127", "33135", "43143", "53151", "63159", "73167", "83175", "93183", "13191", "23199", "33117", "43115", "53123", "63131", "73139", "83147", "93155", "13163", "23171", "33179", "43187", "53195", "63213", "73211", "83219", "93227", "13235", "23243", "33251", "43259", "53267", "63275", "73283", "83291", "93299", "17413", "26899", "36917", "46915", "56923", "66931", "76939", "86947", "96955", "16963", "26971", "36979", "46987", "56995", "67113", "77111", "87119", "97127", "17135", "27143", "37151", "47159", "57167", "67175", "77183", "87191", "97199", "17117", "27115", "37123", "47131", "57139", "67147", "77155", "87163", "97171", "17179", "27187", "37195", "47213", "57211", "67219", "77227", "87235", "97243", "17251", "27259", "37267", "47275", "57283", "67291", "77299", "87317", "97315", "17323", "27331", "37339", "47347", "57355", "67363", "77371", "87379", "97387", "17395", "21499", "31995", "41113", "52111", "62119", "71127", "81135", "91143", "11151", "21159", "31167", "41175", "51183", "62191", "72199", "81117", "91115", "11123", "21131", "31139", "41147", "51155", "61163", "71171", "81179", "91187", "11195", "21213", "31211", "41219", "51227", "61235", "71243", "81251", "91259", "11267", "21275", "31283", "41291", "51299", "61317", "71315", "81323", "91331", "11339", "21347", "31355", "41363", "51371", "61379", "71387", "81395", "91413", "11411", "21419", "31427", "41435", "51443", "61451", "71459", "81467", "91475", "11483", "21491", "35595", "45191", "55199", "65117", "75115", "85123", "95131", "15139", "25147", "35155", "45163", "55171", "65179", "75187", "85195", "95213", "15211", "25219", "35227", "45235", "55243", "65251", "75259", "85267", "95275", "15283", "25291", "35299", "45317", "55315", "65323", "75331", "85339", "95347", "15355", "25363", "35371", "45379", "55387", "65395", "75413", "85411", "95419", "15427", "25435", "35443", "45451", "55459", "65467", "75475", "85483", "95491", "15499", "25517", "35517", "45523", "55531", "65539", "75547", "86555", "95563", "15571", "25579", "35587", "49691", "59187", "69195", "79213", "89211", "99219", "19227", "29235", "39243", "49251", "59259", "69267", "79275", "89283", "99291", "19299", "29317", "39315", "49323", "59331", "69339", "79347", "89355", "99363", "19371", "29379", "39387", "49395", "59413", "69411", "79419", "89427", "99435", "19443", "29451", "39459", "49467", "59475", "69483", "79491", "89499", "99517", "19515", "29523", "39531", "49539", "59547", "69555", "79563", "89571", "99579", "19587", "29595", "39613", "49611", "59619", "69627", "79635", "89643", "99651", "19659", "29667", "39675", "49683", "51119", "61516", "71524", "81532", "91541", "11548", "21556", "31564", "41572", "51581", "61588", "71596", "81614", "91612", "11621", "21628", "31636", "41644", "51652", "61661", "71668", "81676", "91684", "11692", "21711", "31718", "41716", "51724", "61732", "71741", "81748", "91756", "11764", "21772", "31781", "41788", "51796", "61814", "71812", "81821", "91828", "11836", "21844", "31852", "41861", "51868", "61876", "71884", "81892", "91911", "11918", "21916", "31924", "41932", "51941", "61948", "71956", "81964", "91972", "11981", "21988", "31996", "41114", "51112", "61121", "74612", "84621", "94628", "14636", "24644", "34652", "44661", "54668", "64676", "74684", "84692", "94711", "14718", "24716", "34724", "44732", "54741", "64748", "74756", "84764", "94772", "14781", "24788", "34796", "44814", "54812", "64821", "74828", "84836", "94844", "14852", "24861", "34868", "44876", "54884", "64892", "74911", "84918", "94916", "14924", "24932", "34941", "44948", "54956", "64964", "74972", "84981", "94988", "14996", "25114", "35112", "45121", "55128", "65136", "75144", "85152", "95161", "15168", "25176", "35184", "45192", "55111", "65118", "75116", "88718", "98716", "18724", "28732", "38741", "48748", "58756", "68764", "78772", "88781", "98788", "18796", "28814", "38812", "48821", "58828", "68836", "78844", "88852", "98861", "18868", "28876", "38884", "48892", "58911", "68918", "78916", "88924", "98932", "18941", "28948", "38956", "48964", "58972", "68981", "78988", "88996", "99114", "19112", "29121", "39128", "49136", "59144", "69152", "79161", "89168", "99176", "19184", "29192", "39111", "49118", "59116", "69124", "79132", "89141", "99148", "19156", "29164", "39172", "49181", "59188", "69196", "79214", "83318", "92814", "12812", "22821", "32828", "42836", "52844", "62852", "72861", "82868", "92876", "12884", "22892", "32911", "42918", "52916", "62924", "72932", "82941", "92948", "12956", "22964", "32972", "42981", "52988", "62996", "73114", "83112", "93121", "13128", "23136", "33144", "43152", "53161", "63168", "73176", "83184", "93192", "13111", "23118", "33116", "43124", "53132", "63141", "73148", "83156", "93164", "13172", "23181", "33188", "43196", "53214", "63212", "73221", "83228", "93236", "13244", "23252", "33261", "43268", "53276", "63284", "73292", "83311", "97414", "16911", "26918", "36916", "46924", "56932", "66941", "76948", "86956", "96964", "16972", "26981", "36988", "46996", "57114", "67112", "77121", "87128", "97136", "17144", "27152", "37161", "47168", "57176", "67184", "77192", "87111", "97118", "17116", "27124", "37132", "47114", "57148", "67156", "77164", "87172", "97181", "17188", "27196", "37214", "47212", "57221", "67228", "77236", "87244", "97252", "17261", "27268", "37276", "47284", "57292", "67311", "77318", "87316", "97324", "17332", "27341", "37348", "47356", "57364", "67372", "77381", "87388", "97396", "11511", "21996", "31114", "41112", "51121", "61128", "71136", "81144", "91152", "11161", "21168", "31176", "41184", "51112", "62111", "71118", "81116", "91124", "11132", "21141", "31148", "41156", "51164", "61172", "71181", "81188", "91196", "11214", "21212", "31221", "41228", "51236", "61244", "71252", "81261", "91268", "11276", "21284", "31292", "41311", "51318", "61316", "71324", "81332", "91341", "11348", "21356", "31364", "41372", "51381", "61388", "71396", "81414", "91412", "11421", "21428", "31436", "41444", "51452", "61461", "71468", "81476", "91484", "11492", "25596", "35192", "45111", "55118", "65116", "75124", "85132", "95141", "15148", "25156", "35164", "45172", "55181", "65188", "75196", "85214", "95212", "15221", "25228", "35236", "45244", "55252", "65261", "75268", "85276", "95284", "15292", "25311", "35318", "45316", "55324", "65332", "75341", "85348", "95356", "15364", "25372", "35381", "45388", "55396", "65414", "75412", "85421", "95428", "15436", "25444", "35452", "45461", "55468", "65476", "75484", "85492", "95511", "15518", "25516", "35524", "45532", "55541", "65548", "75556", "85564", "95572", "15581", "25588", "39692", "49188", "59196", "69214", "79212", "89221", "99228", "19236", "29244", "39252", "49261", "59268", "69276", "79284", "89292", "99311", "19318", "29316", "39324", "49332", "59341", "69348", "79356", "89364", "99372", "19381", "29388", "39396", "49414", "59412", "69421", "79428", "89436", "99444", "19452", "29461", "39468", "49476", "59484", "69492", "79511", "89518", "99516", "19524", "29532", "39541", "49548", "59556", "69564", "79572", "89581", "99588", "19596", "29614", "39612", "49621", "59628", "69636", "79644", "89652", "99661", "19668", "29676", "39684", "41121", "51517", "61525", "71533", "81541", "91549", "11557", "21565", "31573", "41581", "51589", "61597", "71615", "81613", "91621", "11629", "21637", "31645", "41653", "51661", "61669", "71677", "81685", "91693", "11711", "21719", "31717", "41725", "51733", "61741", "71749", "81757", "91765", "11773", "21781", "31789", "41797", "51815", "61813", "71821", "81829", "91837", "11845", "21853", "31861", "41869", "51877", "61885", "71893", "81911", "91919", "11917", "21925", "31933", "41941", "51949", "61957", "71965", "81973", "91981", "11989", "21997", "31115", "41113", "51121", "64613", "74621", "84629", "94637", "14645", "24653", "34661", "44669", "54677", "64685", "74693", "84711", "94719", "14717", "24725", "34733", "44741", "54749", "64757", "74765", "84773", "94781", "14789", "24797", "34815", "44813", "54821", "64829", "74837", "84845", "94853", "14861", "24869", "34877", "44885", "54893", "64911", "74919", "84917", "94925", "14933", "24941", "34949", "44957", "54965", "64973", "74981", "84989", "94997", "15115", "25113", "35121", "45129", "55137", "65145", "75153", "85161", "95169", "15177", "25185", "35193", "45111", "55119", "69213", "78719", "88717", "98725", "18733", "28741", "38749", "48757", "58765", "68773", "78781", "88789", "98797", "18815", "28813", "38821", "48829", "58837", "68845", "78853", "88861", "98869", "18877", "28885", "38893", "48911", "58919", "68917", "78925", "88933", "98941", "18949", "28957", "38965", "48973", "58981", "68989", "78997", "89115", "99113", "19121", "29129", "39137", "49145", "59153", "69161", "79169", "89177", "99185", "19193", "29111", "39119", "49117", "59125", "69133", "79141", "89149", "99157", "19165", "29173", "39181", "49189", "59197", "69215", "73319", "82815", "92813", "12821", "22829", "32837", "42845", "52853", "62861", "72869", "82877", "92885", "12893", "22911", "32919", "42917", "52925", "62933", "72941", "82949", "92957", "12965", "22973", "32981", "42989", "52997", "63115", "73113", "83121", "93129", "13137", "23145", "33153", "43161", "53169", "63177", "73185", "83193", "93111", "13119", "23117", "33125", "43133", "53141", "63149", "73157", "83165", "93173", "13181", "23189", "33197", "43215", "53213", "63221", "73229", "83237", "93245", "13253", "23261", "33269", "43277", "53285", "63293", "73311", "87415", "96911", "16919", "26917", "36925", "46933", "56941", "66949", "76957", "86965", "96973", "16981", "26989", "36997", "47115", "57113", "67121", "77129", "87137", "97145", "17153", "27161", "37169", "47177", "57185", "67193", "77111", "87119", "97117", "17125", "27133", "37141", "47149", "57157", "67165", "77173", "87181", "97189", "17197", "27215", "37213", "47221", "57229", "67237", "77245", "87253", "97261", "17269", "27277", "37285", "47293", "57311", "67319", "77317", "87325", "97333", "17341", "27349", "37357", "47365", "57373", "67381", "77389", "87397", "91511", "11997", "21115", "31113", "41121", "51129", "61137", "71145", "81153", "91161", "11169", "21177", "31185", "41193", "52111", "61119", "71117", "81125", "91133", "11141", "21149", "31157", "41165", "51173", "61181", "71189", "81197", "91215", "11213", "21221", "31229", "41237", "51245", "61253", "71261", "81269", "91277", "11285", "21293", "31311", "41319", "51317", "61325", "71333", "81341", "91349", "11357", "21365", "31373", "41381", "51389", "61397", "71415", "81413", "91421", "11429", "21437", "31445", "41453", "51461", "61469", "71477", "81485", "91493", "11597", "25193", "35111", "45119", "55117", "65125", "75133", "85141", "95149", "15157", "25165", "35173", "45181", "55189", "65197", "75215", "85213", "95221", "15229", "25237", "35245", "45253", "55261", "65269", "75277", "85285", "95293", "15311", "25319", "35317", "45325", "55333", "65341", "75349", "85357", "95365", "15373", "25381", "35389", "45397", "55415", "65413", "75421", "85429", "95437", "15445", "25453", "35461", "45469", "55477", "65485", "75493", "85511", "95519", "15517", "25525", "35533", "45541", "55549", "65557", "75565", "85573", "95581", "15589", "29693", "39189", "49197", "59215", "69213", "79221", "89229", "99237", "19245", "29253", "39261", "49269", "59277", "69285", "79293", "89311", "99319", "19317", "29325", "39333", "49341", "59349", "69357", "79365", "89373", "99381", "19389", "29397", "39415", "49413", "59421", "69429", "79437", "89445", "99453", "19461", "29469", "39477", "49485", "59493", "69511", "79591", "89517", "99525", "19533", "29541", "39549", "49557", "59565", "69573", "79581", "89589", "99597", "19615", "29613", "39621", "49629", "59637", "69645", "79653", "89661", "99669", "19677", "29685", "31121", "41518", "51526", "61534", "71542", "81551", "91558", "11566", "21574", "31582", "41591", "51598", "61616", "71614", "81622", "91631", "11638", "21646", "31654", "41662", "51671", "61678", "71686", "81694", "91712", "11711", "21718", "31726", "41734", "51742", "61751", "71758", "81766", "91774", "11782", "21791", "31798", "41816", "51814", "61822", "71831", "81838", "91846", "11854", "21862", "31871", "41878", "51886", "61894", "71912", "81911", "91918", "11926", "21934", "31942", "41951", "51958", "61966", "71974", "81982", "91991", "11998", "21116", "31114", "45118", "54614", "64622", "74631", "84638", "94646", "14654", "24662", "34671", "44678", "54686", "64694", "74712", "84711", "94718", "14726", "24734", "34742", "44751", "54758", "64766", "74774", "84782", "94791", "14798", "24816", "34814", "44822", "54831", "64838", "74846", "84854", "94862", "14871", "24878", "34886", "44894", "54912", "64911", "74918", "84926", "94934", "14942", "24951", "34958", "44966", "54974", "64982", "74991", "84998", "95116", "15114", "25122", "35131", "45138", "55146", "65154", "75162", "85171", "95178", "15186", "25194", "35112", "45111", "59214", "68711", "78718", "88726", "98734", "18742", "28751", "38758", "48766", "58774", "68782", "78791", "88798", "98816", "18814", "28822", "38831", "48838", "58846", "68854", "78862", "88871", "98878", "18886", "28894", "38912", "48911", "58918", "68926", "78934", "88942", "98951", "18958", "28966", "38974", "48982", "58991", "68998", "79116", "89114", "99122", "19131", "29138", "39146", "49154", "59162", "69171", "79178", "89186", "99194", "19112", "29111", "39118", "49126", "59134", "69142", "79151", "89158", "99166", "19174", "29182", "39191", "49198", "59216", "63311", "72816", "82814", "92822", "12831", "22838", "32846", "42854", "52862", "62871", "72878", "82886", "92894", "12912", "22911", "32918", "42926", "52934", "62942", "72951", "82958", "92966", "12974", "22982", "32991", "42998", "53116", "63114", "73122", "83131", "93138", "13146", "23154", "33162", "43171", "53178", "63186", "73194", "83112", "93111", "13118", "23126", "33134", "43142", "53151", "63158", "73166", "83174", "93182", "13191", "23198", "33216", "43214", "53222", "63231", "73238", "83246", "93254", "13262", "23271", "33278", "43286", "53294", "63312", "77416", "86912", "96911", "16918", "26926", "36934", "46942", "56951", "66958", "76966", "86974", "96982", "16991", "26998", "37116", "47114", "57122", "67131", "77138", "87146", "97154", "17162", "23131", "37178", "47186", "57194", "67112", "77111", "87118", "97126", "17134", "27142", "37151", "47158", "57166", "67174", "77182", "87191", "97198", "17216", "27214", "37222", "47231", "57238", "67246", "77254", "87262", "97271", "17278", "27286", "37294", "47312", "57311", "67318", "77326", "87334", "97342", "17351", "27358", "37366", "47374", "57382", "67391", "77398", "81512", "91998", "21116", "21114", "31122", "41131", "51138", "61146", "71154", "81162", "91171", "11178", "21186", "31194", "41112", "52111", "61118", "71126", "81134", "91142", "11151", "21158", "31166", "41174", "51182", "61191", "71198", "81216", "91214", "11222", "21231", "31238", "41246", "51254", "61262", "71271", "81278", "91286", "11294", "21312", "31311", "41318", "51326", "61334", "71342", "81351", "91358", "11366", "21374", "31382", "41391", "51398", "61416", "71414", "81422", "91431", "11438", "21446", "31454", "41462", "51471", "61478", "71486", "81494", "95598", "15194", "25112", "35111", "45118", "55126", "65134", "75142", "85151", "95158", "15166", "25174", "35182", "45191", "55198", "65216", "75214", "85222", "95231", "15238", "25246", "35254", "45262", "55271", "65278", "75286", "85294", "95312", "15311", "25318", "35326", "45334", "55342", "65351", "75358", "85366", "95374", "15382", "25391", "35398", "45416", "55414", "65422", "75431", "85438", "95446", "15454", "25462", "35471", "45478", "55486", "65494", "75512", "85511", "95518", "15526", "25534", "35542", "45551", "65558", "65566", "75574", "85582", "95591", "19694", "29191", "39198", "49216", "59214", "69222", "79231", "89238", "99246", "19254", "29262", "39271", "49278", "59286", "69294", "79312", "89311", "99318", "19326", "29334", "39342", "49351", "59358", "69366", "79374", "89382", "99391", "19318", "29416", "39414", "49422", "59431", "69438", "79446", "89454", "99462", "19471", "29478", "39486", "49494", "59512", "69511", "79518", "89526", "99534", "19542", "29551", "39558", "49566", "59574", "69582", "79591", "89598", "99616", "19614", "29622", "39631", "49234", "59646", "69654", "79662", "89671", "99678", "19686", "21122", "31519", "41527", "51535", "61543", "71551", "81559", "91567", "11575", "21583", "31591", "41599", "51617", "61615", "71623", "81631", "91639", "11647", "21655", "31663", "41671", "51679", "61687", "71695", "81713", "91711", "11719", "21727", "31735", "41743", "51751", "61759", "71767", "81775", "91783", "11791", "21799", "31817", "41815", "51823", "61831", "71839", "81847", "91855", "11863", "21871", "31879", "41887", "51895", "61913", "71911", "81919", "91927", "11935", "21943", "31951", "41959", "51967", "61975", "71983", "81991", "91999", "21117", "21115", "35119", "44615", "54623", "64631", "74639", "84647", "94655", "14663", "24671", "34679", "44687", "54695", "64713", "74711", "84719", "94727", "14735", "24743", "34751", "44759", "54767", "64775", "74783", "84791", "94799", "14817", "24815", "34823", "44831", "54839", "64847", "74855", "84863", "94871", "14879", "24887", "34895", "44913", "54911", "64919", "74927", "84935", "94943", "14951", "24959", "34967", "44975", "54983", "64991", "74999", "85117", "95115", "15123", "25131", "35139", "45147", "55155", "65163", "75171", "85179", "95187", "15195", "25113", "35111", "49215", "58711", "68719", "78727", "88735", "98743", "18751", "28759", "38767", "48775", "58783", "68791", "78799", "88817", "98815", "18823", "28831", "38839", "48847", "58855", "68863", "78871", "88879", "98887", "18895", "28913", "38911", "48919", "58927", "68935", "78943", "88951", "98959", "18967", "28975", "38983", "48991", "58999", "69117", "79115", "89123", "99131", "19139", "29147", "39155", "49163", "59171", "69179", "79187", "89195", "99113", "19111", "29119", "39127", "49135", "59143", "69151", "79159", "89167", "99175", "19183", "29191", "39199", "49217", "53311", "62817", "72815", "82823", "92831", "12839", "22847", "32855", "42863", "52871", "62879", "72887", "82895", "92113", "12911", "22919", "32927", "42935", "52943", "62951", "72959", "82967", "92975", "12983", "22991", "32999", "43117", "53115", "63123", "73131", "83139", "93147", "13155", "23163", "33171", "43179", "53187", "63195", "73113", "83111", "93119", "13127", "23135", "33143", "43151", "53159", "63167", "73175", "83183", "93191", "13199", "23217", "33215", "43223", "53231", "63239", "73247", "83255", "93263", "13271", "23279", "33287", "43295", "53313", "67417", "76193", "86911", "96919", "16927", "26935", "36943", "46951", "56959", "66967", "76975", "86983", "96991", "16999", "27117", "37115", "47123", "57131", "67139", "77147", "87155", "97163", "17171", "27179", "37187", "47195", "57113", "67111", "77119", "87127", "97135", "17143", "27151", "37159", "47167", "57175", "67183", "77191", "87199", "97217", "17215", "27223", "37231", "47239", "57247", "67255", "77263", "87271", "97279", "17287", "27215", "37313", "47311", "57319", "67327", "77335", "87343", "97351", "17359", "27367", "37375", "47383", "57391", "67399", "71513", "81999", "91117", "11115", "21123", "31131", "41139", "51147", "61155", "71163", "81171", "91179", "11187", "21195", "31113", "42111", "51119", "61127", "71135", "81143", "91151", "11159", "21167", "31175", "41183", "51191", "61199", "71217", "81215", "91223", "11231", "21239", "31247", "41255", "51263", "61271", "71279", "81287", "91295", "11313", "21311", "31319", "41327", "51335", "61343", "71351", "81359", "91367", "11375", "21383", "31391", "41399", "51417", "61415", "71423", "81431", "91439", "11447", "21455", "31463", "41471", "51479", "61487", "71495", "85599", "95195", "15113", "25111", "35119", "45127", "55135", "65143", "75151", "85159", "95167", "15175", "25183", "35191", "45199", "55217", "65215", "75223", "85231", "95239", "15247", "25255", "35263", "45271", "55279", "65287", "75295", "85313", "95311", "15319", "25327", "35335", "45343", "55351", "65359", "75367", "85375", "95383", "15391", "25399", "35417", "45415", "55423", "65431", "75439", "85447", "95455", "15463", "25471", "35479", "45487", "55495", "65513", "75511", "85519", "95527", "15535", "25543", "35551", "45559", "55567", "65575", "75583", "85591", "99695", "19191", "29199", "39217", "49215", "59223", "69231", "79239", "89247", "99255", "19263", "29271", "39279", "49287", "59295", "69313", "79311", "89319", "99327", "19335", "29343", "39351", "49359", "59367", "69375", "79383", "89391", "99399", "19417", "29415", "39423", "49431", "59439", "69447", "79455", "89463", "99471", "19479", "29487", "39495", "49513", "59511", "69519", "79527", "89535", "99543", "19551", "29559", "39567", "49575", "59583", "69591", "79599", "89617", "99615", "19623", "29631", "39639", "49647", "59655", "69663", "79671", "89679", "99687", "11123", "21124", "31132", "41141", "51148", "61156", "71164", "81172", "91181", "11188", "21196", "31114", "41112", "51121", "61128", "71136", "81144", "91152", "11161", "21168", "31176", "41184", "51192", "61211", "71218", "81216", "91224", "11232", "21241", "31248", "41256", "51264", "61272", "71281", "81288", "91296", "11314", "21312", "31321", "41328", "51336", "61344", "71352", "81361", "91368", "11376", "21384", "31392", "41411", "51418", "61416", "71424", "81432", "91441", "11448", "21456", "31464", "41472", "51481", "61488", "71496", "81514", "91512", "11521", "21528", "35121", "45128", "55136", "65144", "75152", "85161", "95168", "15176", "25184", "35192", "45211", "55218", "65216", "75224", "85232", "95241", "15248", "25256", "35264", "45272", "55281", "65288", "75296", "85314", "95312", "15321", "25328", "35336", "45344", "55352", "65361", "75368", "85376", "95384", "15392", "25411", "35418", "45416", "55424", "65432", "75441", "85448", "95456", "15464", "25472", "35481", "45488", "55496", "65514", "75512", "85521", "95528", "15536", "25544", "35552", "45561", "55568", "65576", "75584", "85592", "95611", "15618", "25616", "39721", "49216", "59224", "69232", "79241", "89248", "99256", "19264", "29272", "39281", "49288", "59296", "69314", "79312", "89321", "99328", "19336", "29344", "39352", "49361", "59368", "69376", "79384", "89392", "99411", "19418", "29416", "39424", "49432", "59441", "69448", "79456", "89464", "99472", "19481", "29488", "39496", "49514", "59512", "69521", "79528", "89536", "99544", "19552", "29561", "39568", "49576", "59584", "69592", "79611", "89618", "99616", "19624", "29632", "39641", "49648", "59656", "69664", "79672", "89681", "99688", "19696", "29714", "39712", "43816", "53312", "63321", "73328", "83336", "93344", "13352", "23361", "33368", "43376", "53384", "63392", "73411", "83418", "93416", "13424", "23432", "33441", "43448", "53456", "63464", "73472", "83481", "93488", "13496", "23514", "33512", "43521", "53528", "63536", "73544", "83552", "93561", "13568", "23576", "33584", "43592", "53611", "63618", "73616", "83624", "93632", "13641", "23648", "33656", "43664", "53672", "63681", "73688", "83696", "93714", "13712", "23721", "33728", "43736", "53744", "63752", "73761", "83768", "93776", "13784", "23792", "33811", "43818", "57912", "67418", "77416", "87424", "97432", "17441", "27448", "37456", "47464", "57472", "67481", "77488", "87496", "97514", "17512", "27521", "37528", "47536", "57544", "67552", "77561", "87568", "97576", "17584", "27592", "37611", "47618", "57616", "67624", "77632", "87641", "97648", "17656", "27664", "37672", "47681", "57688", "67696", "77714", "87712", "97721", "17728", "27736", "37744", "47752", "57761", "67768", "77776", "87784", "97792", "17811", "27818", "37816", "47824", "57832", "67841", "77848", "87856", "97864", "17872", "27881", "37888", "47896", "57914", "62118", "71514", "81512", "91521", "11528", "21536", "31544", "41552", "51561", "61568", "71576", "81584", "91592", "11611", "21618", "31616", "41624", "51632", "61641", "71648", "81656", "91664", "11672", "21681", "31688", "41696", "51714", "61712", "71721", "81728", "91736", "11744", "21752", "31761", "41768", "51776", "61784", "71792", "81811", "91818", "11816", "21824", "31832", "41841", "51848", "61856", "71864", "81872", "91881", "11888", "21896", "31914", "41912", "51921", "61928", "71936", "81944", "91952", "11961", "21968", "31976", "41984", "51992", "62111", "76114", "85611", "95618", "15616", "25624", "35632", "45641", "55648", "65656", "75664", "85672", "95681", "15688", "25696", "35714", "45712", "55721", "65728", "75736", "85744", "95752", "15761", "25768", "35776", "45784", "55712", "65811", "75818", "85816", "95824", "15832", "25841", "35848", "45856", "55864", "65872", "75881", "85888", "95896", "15914", "25912", "35921", "45928", "55936", "65944", "75952", "85961", "95968", "15976", "25984", "35992", "46111", "56118", "66116", "76124", "86132", "96141", "16148", "26156", "36164", "46172", "56181", "66188", "76196", "81211", "99696", "19714", "29712", "39721", "49728", "59736", "69744", "79752", "89761", "99768", "19776", "29784", "39792", "49811", "59818", "69816", "79824", "89832", "99841", "19848", "29856", "39864", "49872", "59881", "69888", "79896", "89914", "99912", "19921", "29928", "39936", "49944", "59952", "69961", "79968", "89976", "99984", "19992", "22111", "31118", "41116", "51124", "61132", "71141", "81148", "91156", "11164", "21172", "31181", "41188", "51196", "61114", "71112", "81121", "91128", "11136", "21144", "31152", "41161", "51168", "61176", "71184", "81192", "91528", "11125", "21133", "31141", "41149", "51157", "61165", "71173", "81181", "91189", "11197", "21115", "31113", "41121", "51129", "61137", "71145", "81153", "91161", "11169", "21177", "31185", "41193", "51211", "61211", "71217", "81225", "91233", "11241", "21249", "31257", "41265", "51273", "61281", "71289", "81297", "91315", "11313", "21321", "31329", "41337", "51345", "61353", "71361", "81369", "91377", "11385", "21393", "31411", "41419", "51417", "61425", "71433", "81441", "91449", "11457", "21465", "31473", "41481", "51489", "61497", "71515", "81513", "91521", "15625", "25121", "35129", "45137", "55145", "65153", "75161", "85169", "95177", "15185", "25193", "35211", "45219", "55217", "65225", "75233", "85241", "95249", "15257", "25265", "35273", "45281", "55289", "65297", "75315", "85313", "95321", "15329", "25337", "35345", "45353", "55361", "65369", "75377", "85385", "95393", "15411", "25419", "35417", "45425", "55433", "65441", "75449", "85457", "95465", "15473", "25481", "35489", "45497", "55515", "65513", "75521", "85529", "95537", "15545", "25553", "35561", "45569", "55577", "65585", "75593", "85611", "95619", "15617", "29721", "39217", "49225", "59233", "69241", "79249", "89257", "99265", "19273", "29281", "39289", "49297", "59315", "69313", "79321", "89329", "99337", "19345", "29353", "39361", "49369", "59377", "69385", "79393", "89411", "99419", "19417", "29425", "39433", "49441", "59449", "69457", "79465", "89473", "99481", "19489", "29497", "39515", "49513", "59521", "69529", "79537", "89545", "99553", "19561", "29569", "39577", "49585", "59593", "69611", "79619", "89617", "99625", "19633", "29641", "39649", "49657", "59665", "69673", "79681", "89689", "99697", "19715", "29713", "33817", "43313", "53321", "63329", "73337", "83345", "93353", "13361", "23369", "33377", "43385", "53393", "63411", "73419", "83417", "93425", "13433", "23441", "33449", "43457", "53465", "63473", "73481", "83489", "93497", "13515", "23513", "33521", "43529", "53537", "63545", "73553", "83561", "93569", "13577", "23585", "33593", "43611", "53619", "63617", "73625", "83633", "93641", "13649", "23657", "33665", "43673", "53681", "63689", "73697", "83715", "93713", "13721", "23729", "33737", "43745", "53753", "63761", "73769", "83777", "93785", "13793", "23811", "33819", "47913", "57419", "67417", "77425", "87433", "97441", "17449", "27457", "37465", "47473", "57481", "67489", "77497", "87515", "97513", "17521", "27529", "37537", "47545", "57553", "67561", "77569", "87577", "97585", "17593", "27611", "37619", "47617", "57625", "67633", "77641", "87649", "97657", "17665", "27673", "37681", "47689", "57697", "67715", "77713", "87721", "97729", "17737", "27745", "37753", "47761", "57769", "68777", "77785", "87793", "97811", "17819", "27817", "37825", "47833", "57841", "67849", "77857", "87865", "97873", "17881", "27889", "37897", "47915", "52119", "61515", "71513", "81521", "91529", "11537", "21545", "31553", "41561", "51569", "61577", "71585", "81593", "91611", "11619", "21617", "31625", "41633", "51641", "61649", "71657", "81665", "91673", "11681", "21689", "31697", "41715", "51713", "61721", "71729", "81737", "91745", "11753", "21761", "31769", "41777", "51785", "61793", "71811", "81819", "91817", "11825", "21833", "31841", "41849", "51857", "61865", "71873", "81881", "91889", "11897", "21915", "31913", "41921", "51929", "61937", "71945", "81953", "91961", "11969", "21977", "31985", "41993", "52111", "66115", "75611", "85619", "95617", "15625", "25633", "35641", "45649", "55657", "65665", "75673", "85681", "95689", "15697", "25715", "35713", "45721", "55729", "65737", "75745", "85753", "95761", "15769", "25777", "35785", "45793", "55811", "65819", "75817", "85825", "95833", "15841", "25849", "35857", "45865", "55873", "65881", "75889", "85897", "95915", "15913", "25921", "35929", "45937", "55945", "65953", "75961", "85969", "95977", "15985", "25993", "36111", "46119", "56117", "66125", "76133", "86141", "96149", "16157", "26165", "36173", "46181", "56189", "66197", "71211", "89697", "99715", "19713", "29721", "39729", "49737", "59745", "69753", "79761", "89769", "99777", "19785", "29793", "39811", "49819", "59817", "69825", "79833", "89841", "99849", "19857", "29865", "39873", "49881", "59889", "69897", "79915", "89913", "99921", "19929", "29937", "39945", "49953", "59961", "69969", "79977", "89985", "19993", "12111", "22119", "31117", "41125", "51133", "61141", "71149", "81157", "91165", "11173", "21181", "31189", "41197", "51115", "61113", "71121", "81129", "91137", "11145", "21153", "31161", "41169", "51177", "61185", "71193", "81529", "91126", "11134", "21142", "31151", "41158", "51166", "61174", "71182", "82191", "91198", "21116", "21114", "31122", "41131", "51138", "61146", "71154", "81162", "91171", "11178", "21186", "31194", "41212", "51211", "61218", "71226", "81234", "91242", "11251", "21258", "31266", "41274", "51282", "61291", "71298", "81316", "91314", "11322", "21331", "31338", "41346", "51354", "61362", "71371", "81378", "91386", "11394", "21412", "31411", "41418", "51426", "61434", "71442", "81451", "91458", "11466", "21474", "31482", "41491", "51498", "61516", "71514", "81522", "95626", "15122", "25131", "35138", "45146", "55154", "65162", "75171", "85178", "95186", "15194", "25212", "35211", "45218", "55226", "65234", "75242", "85251", "95258", "15266", "25274", "35282", "45291", "55298", "65316", "75314", "85322", "95331", "15338", "25346", "35354", "45362", "55371", "65378", "75386", "85394", "95412", "15411", "25418", "35426", "45434", "55442", "65451", "75458", "85466", "95474", "15482", "25491", "35498", "45516", "55514", "65522", "75531", "85538", "95546", "15554", "25562", "35571", "45578", "55586", "65594", "75612", "85611", "95618", "19722", "29218", "39226", "49234", "59242", "69251", "79258", "89266", "99274", "19282", "29291", "39298", "49316", "59314", "69322", "79331", "89338", "99346", "19354", "29362", "39371", "49378", "59386", "69394", "79412", "89411", "99418", "19426", "29434", "39442", "49451", "59458", "69466", "79474", "89482", "99491", "19498", "29516", "39514", "49522", "59531", "69538", "79546", "89554", "99562", "19571", "29578", "39586", "49594", "59612", "69611", "79618", "89626", "99634", "19642", "29651", "39658", "49666", "59674", "69682", "79691", "89698", "99716", "19714", "23818", "33314", "43322", "53331", "63338", "73346", "83354", "93362", "13371", "23378", "33386", "43394", "53412", "63411", "73418", "83426", "93434", "13442", "23451", "33458", "43466", "53474", "63482", "73491", "83498", "93516", "13514", "23522", "33531", "43538", "53546", "63554", "73562", "83571", "93578", "13586", "23594", "33612", "43611", "53618", "63626", "73634", "83642", "93651", "13658", "23666", "33674", "43682", "53691", "63698", "73716", "83714", "93722", "13731", "23738", "33746", "43754", "53762", "63771", "73778", "83786", "93794", "13812", "23811", "37914", "47411", "57418", "67426", "77434", "87442", "97451", "17458", "27466", "37474", "47482", "57491", "67498", "77516", "87514", "97522", "17531", "27538", "37546", "47554", "57562", "67571", "77578", "87586", "97594", "17612", "27611", "37618", "47626", "57634", "67642", "77651", "87658", "97666", "17674", "27682", "37691", "47698", "57716", "67714", "77722", "87731", "97738", "17746", "27754", "37762", "47771", "57778", "67786", "77794", "87812", "97811", "17818", "27826", "37834", "47842", "57851", "67858", "77866", "87874", "97882", "17891", "27898", "37916", "42111", "51516", "61514", "71522", "81531", "91538", "11546", "21554", "31562", "41571", "51578", "61586", "71594", "81612", "91611", "11618", "21626", "31634", "41642", "51651", "61658", "71666", "81674", "91682", "11691", "21698", "31716", "41714", "51722", "61731", "71738", "81746", "91754", "11762", "21771", "31778", "41786", "51794", "61812", "71811", "81818", "91826", "11834", "21842", "31851", "41858", "51866", "61874", "71882", "81891", "91898", "11916", "21914", "31922", "41931", "51938", "61946", "71954", "81962", "91971", "11978", "21986", "31994", "42912", "56116", "65612", "75611", "85618", "95626", "15634", "25642", "35651", "45658", "55666", "65674", "75682", "85691", "95698", "15716", "25714", "35722", "45731", "55738", "65746", "75754", "85762", "95771", "15778", "25786", "35794", "45812", "55811", "65818", "75826", "85834", "95842", "15851", "25858", "35866", "45874", "55882", "65891", "75898", "85916", "95914", "15922", "25931", "35938", "45946", "55954", "65962", "75971", "85978", "95986", "15994", "26112", "36111", "46118", "56126", "66134", "76142", "86151", "96158", "16166", "26174", "36182", "46191", "56198", "61212", "79698", "89716", "99714", "19722", "29731", "39738", "49746", "59754", "69762", "79771", "89778", "99786", "19794", "29812", "39811", "49818", "59826", "69834", "79842", "89851", "99858", "19866", "29874", "39882", "49891", "59898", "69916", "79914", "89922", "99931", "19938", "29946", "39954", "49962", "59971", "69978", "79986", "89994", "91112", "12111", "21118", "31126", "41134", "51142", "61151", "71158", "81166", "91174", "11182", "21191", "31198", "41116", "51114", "61122", "71131", "81138", "91146", "11154", "21162", "31171", "41178", "51186", "61194", "71212", "81127", "91135", "11143", "21151", "31159", "41167", "51175", "61183", "71191", "81199", "91117", "21115", "21123", "31131", "41139", "51147", "61155", "71163", "81171", "91179", "11187", "21195", "31213", "41211", "51219", "61227", "71235", "81243", "91251", "11259", "21267", "31275", "41283", "51291", "61299", "71317", "81315", "91323", "11331", "21339", "31347", "41355", "51363", "61371", "71379", "81387", "91395", "11413", "21411", "31419", "41427", "51435", "61443", "71451", "81459", "91467", "11475", "21483", "31491", "41499", "51517", "61515", "71523", "85627", "95123", "15131", "25139", "35147", "45155", "55163", "65171", "75179", "85187", "95115", "15213", "25211", "35219", "45227", "55235", "65243", "75251", "85259", "95267", "15275", "25283", "35291", "45299", "55317", "65315", "75323", "85331", "95339", "15347", "25355", "35363", "45371", "55379", "65387", "75395", "85413", "95411", "15419", "25427", "35435", "45443", "55451", "65459", "75467", "85475", "95483", "15491", "25499", "35517", "45515", "55523", "65531", "75539", "85547", "96555", "15563", "25571", "35579", "45587", "55595", "65613", "75611", "85619", "99723", "19219", "29227", "39235", "49243", "59251", "69259", "79267", "89275", "99283", "19291", "29299", "39317", "49315", "59323", "69331", "79339", "89347", "99355", "19363", "29371", "39379", "49387", "59395", "69413", "79411", "89419", "99427", "19435", "29443", "39451", "49459", "59467", "69475", "79483", "89491", "99499", "19517", "29515", "39523", "49531", "59539", "69547", "79555", "89563", "99571", "19579", "29587", "39595", "49613", "59611", "69619", "79627", "89635", "99643", "19651", "29659", "39667", "49675", "59683", "69691", "79699", "89717", "99715", "13819", "23315", "33323", "43331", "53339", "63347", "73355", "83363", "93371", "13379", "23387", "33395", "43413", "53411", "63419", "73427", "83435", "93443", "13451", "23459", "33467", "43475", "53483", "63491", "73499", "83517", "93515", "13523", "23531", "33539", "43547", "53555", "63563", "73571", "83579", "93587", "13595", "23613", "33611", "43619", "53627", "63635", "73643", "83651", "93659", "13667", "23675", "33683", "43691", "53699", "63717", "73715", "83723", "93731", "13739", "23747", "33755", "43763", "53771", "63779", "73787", "83795", "93813", "13811", "27915", "37411", "47419", "57427", "67435", "77443", "87451", "97459", "17467", "27475", "37483", "47491", "57499", "67517", "77515", "87523", "97531"]} \ No newline at end of file diff --git a/catalogs/radiocode-pending-brands.json b/catalogs/radiocode-pending-brands.json new file mode 100644 index 00000000..7537a725 --- /dev/null +++ b/catalogs/radiocode-pending-brands.json @@ -0,0 +1,46 @@ +{ + "schema_version": 1, + "description": "Data-pending radio-code brands. Each entry installs a stub calculator that raises EOBDRadioCodeDataMissing on Calculate, surfacing the data gap to UIs.", + "entries": [ + { + "brand_key": "pioneer", + "display_name": "Pioneer", + "data_notes": "Needs verified serial-to-code algorithm or lookup table for at least DEH/AVH/MVH model families. Commercial DBs cover ~30M units; community-published algorithms are partial and generation-specific." + }, + { + "brand_key": "kenwood", + "display_name": "Kenwood", + "data_notes": "Needs verified algorithm or lookup table for KDC/DDX/DNX/KMM model families. After the 2008 JVC-Kenwood merger some platforms share supply chain with JVC; an algorithm covering one may apply to the other." + }, + { + "brand_key": "jvc", + "display_name": "JVC", + "data_notes": "Needs verified algorithm or lookup table for KD/KW model families. Post-2008 platforms may share with Kenwood." + }, + { + "brand_key": "sony", + "display_name": "Sony", + "data_notes": "Needs verified algorithm or lookup table for CDX/WX/MEX after-market head units. Modern Sony OEM fitments are tied to VIN via the gateway and out of scope." + }, + { + "brand_key": "philips", + "display_name": "Philips", + "data_notes": "Needs the licensed serial-to-code database (Philips ships ~14M entries). EEPROM-extraction route is hardware-side and not implementable here." + }, + { + "brand_key": "grundig", + "display_name": "Grundig", + "data_notes": "Pre-2000 European OEM head units (WKC/EC series). Possibly recoverable from a specific generation via the same approach used for Becker4/Becker5; needs a leaked/published table." + }, + { + "brand_key": "panasonic", + "display_name": "Panasonic (Matsushita)", + "data_notes": "Needs CQ-series algorithm or lookup table; per-region variants common." + }, + { + "brand_key": "continental_vdo", + "display_name": "Continental / VDO", + "data_notes": "OEM head-unit supplier in VW / Mercedes / Ford. Often re-uses VAG variants but the specific mapping per part number is undocumented publicly." + } + ] +} \ No newline at end of file diff --git a/catalogs/service-routines.json b/catalogs/service-routines.json new file mode 100644 index 00000000..543649b4 --- /dev/null +++ b/catalogs/service-routines.json @@ -0,0 +1,402 @@ +{ + "schema_version": 1, + "description": "Workshop service routine catalog. Add OEM-specific routines here without recompiling.", + "categories": [ + "maintenance", + "steering_brakes", + "powertrain", + "comfort", + "battery_electrical", + "tpms", + "emissions" + ], + "safety_levels": [ + "none", + "engine_must_be_running", + "engine_must_be_off", + "vehicle_must_be_stationary", + "vehicle_may_move", + "battery_min_12v5", + "requires_workshop_login" + ], + "fields": { + "key": "Stable identifier, lower-case", + "display_name": "Shown in UIs", + "category": "One of the categories listed above", + "applicability": "Comma-separated OEM keys, or 'all'", + "routine_identifier": "UDS 0x31 RoutineControl Identifier (RID), 0xHEX", + "sub_function": "0x01=Start 0x02=Stop 0x03=ResultRead", + "option_record_hex": "Optional bytes appended after RID, '' if unused", + "required_session_type": "0x01=Default 0x02=Programming 0x03=Extended ...", + "safety": "One of the safety levels listed above", + "pre_conditions": "Free-form prose", + "post_conditions": "Free-form prose", + "citation": "Public reference (URL or document ID); never empty" + }, + "entries": [ + { + "key": "oil_reset_vag", + "display_name": "Oil Service Reset (VAG SRI)", + "category": "maintenance", + "applicability": "vw,audi,seat,skoda", + "routine_identifier": "0x0301", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "engine_must_be_off", + "pre_conditions": "Engine off, ignition on, doors closed.", + "post_conditions": "Verify SRI shows full distance to next service.", + "citation": "VW Service Manual + Ross-Tech wiki / SRI Reset." + }, + { + "key": "oil_reset_bmw", + "display_name": "Oil Service Reset (BMW CBS)", + "category": "maintenance", + "applicability": "bmw,mini", + "routine_identifier": "0xF062", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "engine_must_be_off", + "pre_conditions": "Engine off, ignition on, key in.", + "post_conditions": "CBS shows next service in km/months and oil-life 100%.", + "citation": "BMW TIS + BimmerCode/Carly public archives." + }, + { + "key": "oil_reset_mb", + "display_name": "Oil Service Reset (Mercedes ASSYST)", + "category": "maintenance", + "applicability": "mercedes", + "routine_identifier": "0x5028", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "engine_must_be_off", + "pre_conditions": "Engine off, ignition on, doors closed.", + "post_conditions": "ASSYST PLUS shows full service interval.", + "citation": "Mercedes WIS / ASSYST Plus reset procedure." + }, + { + "key": "oil_reset_ford", + "display_name": "Oil Life Reset (Ford OLM)", + "category": "maintenance", + "applicability": "ford,lincoln", + "routine_identifier": "0x0301", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "engine_must_be_off", + "pre_conditions": "Engine off, ignition on.", + "post_conditions": "Cluster shows OLM reset; remaining oil life 100%.", + "citation": "Ford TSB + FORScan archives." + }, + { + "key": "oil_reset_toyota", + "display_name": "Maintenance Reset (Toyota MAINT)", + "category": "maintenance", + "applicability": "toyota,lexus", + "routine_identifier": "0x0301", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "engine_must_be_off", + "pre_conditions": "Engine off, ignition on, odometer mode.", + "post_conditions": "Maintenance light off; cycle reset.", + "citation": "Toyota Repair Manual + Techstream service." + }, + { + "key": "adblue_level_reset", + "display_name": "AdBlue / DEF Level Reset", + "category": "maintenance", + "applicability": "vw,audi,bmw,mercedes,ford", + "routine_identifier": "0x0306", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "vehicle_must_be_stationary", + "pre_conditions": "Vehicle stationary; tank refilled.", + "post_conditions": "AdBlue range counter resets to full.", + "citation": "OEM diesel emission service docs." + }, + { + "key": "sas_zero", + "display_name": "Steering Angle Sensor Calibration", + "category": "steering_brakes", + "applicability": "vw,audi,bmw,mercedes,ford,toyota,honda,hyundai,kia", + "routine_identifier": "0x0301", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "vehicle_must_be_stationary", + "pre_conditions": "Wheels straight, vehicle stationary, ignition on.", + "post_conditions": "SAS reads 0.0 degrees; no DTC.", + "citation": "ISO 26262 + per-OEM TSBs (e.g. VW Self Study Programs)." + }, + { + "key": "epb_service_mode_open", + "display_name": "Electric Park Brake — Service Mode (Open)", + "category": "steering_brakes", + "applicability": "vw,audi,bmw,mercedes,ford,volvo", + "routine_identifier": "0x0307", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "vehicle_may_move", + "pre_conditions": "Vehicle stationary, transmission in P/N, hood open per OEM.", + "post_conditions": "Calipers retract; service indicator on cluster.", + "citation": "OEM service info + EPB unwind TSBs." + }, + { + "key": "epb_service_mode_close", + "display_name": "Electric Park Brake — Service Mode (Close)", + "category": "steering_brakes", + "applicability": "vw,audi,bmw,mercedes,ford,volvo", + "routine_identifier": "0x0307", + "sub_function": "0x02", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "vehicle_may_move", + "pre_conditions": "Brake pads installed, calipers ready.", + "post_conditions": "Calipers torque to pads; EPB ready.", + "citation": "OEM service info + EPB unwind TSBs." + }, + { + "key": "abs_bleed_4wheel", + "display_name": "ABS Hydraulic Bleed (4-wheel)", + "category": "steering_brakes", + "applicability": "vw,audi,bmw,mercedes,ford,toyota", + "routine_identifier": "0x0303", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "vehicle_must_be_stationary", + "pre_conditions": "Brake fluid topped, ignition on, scan tool sequencing wheels.", + "post_conditions": "No air in lines; pedal feel firm.", + "citation": "OEM service info + Bosch ABS docs." + }, + { + "key": "dpf_forced_regen", + "display_name": "DPF Forced Regeneration", + "category": "powertrain", + "applicability": "vw,audi,bmw,mercedes,ford,volvo,renault", + "routine_identifier": "0x0309", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "engine_must_be_running", + "pre_conditions": "Engine warm (>80C), fuel >25%, no DPF DTCs blocking, vehicle parked outdoors.", + "post_conditions": "Soot mass < threshold; differential pressure normal.", + "citation": "OEM diesel service info; DPF Forced Regen TSBs." + }, + { + "key": "throttle_body_adapt", + "display_name": "Throttle Body Adaptation", + "category": "powertrain", + "applicability": "vw,audi,seat,skoda", + "routine_identifier": "0x0335", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "engine_must_be_off", + "pre_conditions": "Engine off, ignition on, all loads off.", + "post_conditions": "Throttle adaptation values within range; idle stable after start.", + "citation": "Ross-Tech wiki / Throttle Body Alignment." + }, + { + "key": "idle_relearn", + "display_name": "Idle Air Volume Relearn", + "category": "powertrain", + "applicability": "nissan,infiniti", + "routine_identifier": "0x0317", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "engine_must_be_running", + "pre_conditions": "Engine warm, transmission in P/N, all loads off.", + "post_conditions": "Idle stabilises within spec.", + "citation": "Nissan FSM / NICOclub archives." + }, + { + "key": "window_pinch_learn_vag", + "display_name": "Window Pinch Protection Learn", + "category": "comfort", + "applicability": "vw,audi,seat,skoda", + "routine_identifier": "0x0341", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "vehicle_may_move", + "pre_conditions": "All windows closed; ignition on; door closed.", + "post_conditions": "One-touch up/down works; pinch protection re-armed.", + "citation": "Ross-Tech wiki / 09 Cent Elec / Window Adaptation." + }, + { + "key": "sunroof_calibration", + "display_name": "Sunroof Initialisation", + "category": "comfort", + "applicability": "vw,audi,bmw,mercedes", + "routine_identifier": "0x0342", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "vehicle_may_move", + "pre_conditions": "Sunroof at endpoint, ignition on.", + "post_conditions": "Sunroof learns end-stops; pinch protection armed.", + "citation": "OEM TSBs." + }, + { + "key": "seat_memory_reset", + "display_name": "Seat Memory Module Reset", + "category": "comfort", + "applicability": "mercedes,bmw,audi", + "routine_identifier": "0x0345", + "sub_function": "0x02", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "none", + "pre_conditions": "Vehicle stationary, ignition on.", + "post_conditions": "Seat memory cleared; relearn triggered on next save.", + "citation": "Mercedes WIS + BMW TIS archives." + }, + { + "key": "battery_register_bmw", + "display_name": "Battery Registration (BMW IBS)", + "category": "battery_electrical", + "applicability": "bmw,mini", + "routine_identifier": "0xF101", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "battery_min_12v5", + "pre_conditions": "Battery installed, ignition on for >30s, voltage >12.5V.", + "post_conditions": "IBS reports new SoH 100%; CBS resets battery counter.", + "citation": "BimmerCode / Carly public archives + BMW TIS." + }, + { + "key": "battery_register_mb", + "display_name": "Battery Registration (Mercedes IBS)", + "category": "battery_electrical", + "applicability": "mercedes", + "routine_identifier": "0xF101", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "battery_min_12v5", + "pre_conditions": "Battery installed, ignition on, IBS connected.", + "post_conditions": "IBS resets; SoH 100%.", + "citation": "Mercedes WIS battery-replacement procedure." + }, + { + "key": "battery_register_audi", + "display_name": "Battery Registration (Audi 12V)", + "category": "battery_electrical", + "applicability": "audi,vw", + "routine_identifier": "0xF102", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "battery_min_12v5", + "pre_conditions": "Battery installed, ignition on, doors closed.", + "post_conditions": "Cluster confirms battery write; energy management resets.", + "citation": "Ross-Tech wiki / 19 CAN Gateway / Battery coding." + }, + { + "key": "alternator_load_test", + "display_name": "Alternator Load Test", + "category": "battery_electrical", + "applicability": "vw,audi,bmw,mercedes", + "routine_identifier": "0xF103", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "engine_must_be_running", + "pre_conditions": "Engine running, electrical loads on per OEM script.", + "post_conditions": "Alternator output within spec.", + "citation": "Bosch alternator service info." + }, + { + "key": "tpms_relearn", + "display_name": "TPMS Sensor Relearn", + "category": "tpms", + "applicability": "vw,audi,bmw,mercedes,ford,toyota,honda,gm", + "routine_identifier": "0x0501", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "vehicle_must_be_stationary", + "pre_conditions": "Sensor IDs known per wheel; vehicle stationary.", + "post_conditions": "All four sensors report; no TPMS warning.", + "citation": "ISO 21750 + per-OEM TSBs." + }, + { + "key": "tpms_id_write", + "display_name": "TPMS Sensor ID Write (per wheel)", + "category": "tpms", + "applicability": "vw,audi,bmw,mercedes,ford,toyota,honda,gm", + "routine_identifier": "0x0502", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "vehicle_must_be_stationary", + "pre_conditions": "Wheel position selected; new sensor ID known.", + "post_conditions": "Position confirmed by re-reading the sensor ID DID.", + "citation": "ISO 21750 + per-OEM TSBs." + }, + { + "key": "readiness_clear", + "display_name": "Clear Readiness Monitors", + "category": "emissions", + "applicability": "all", + "routine_identifier": "0xFF00", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "none", + "pre_conditions": "Ignition on, no DTCs blocking.", + "post_conditions": "Readiness monitors re-arm; status incomplete on next start.", + "citation": "ISO 15031-5 + Service 04 supplement." + }, + { + "key": "emissions_drive_cycle_marker", + "display_name": "Emissions Drive-Cycle Marker", + "category": "emissions", + "applicability": "vw,audi,ford,toyota", + "routine_identifier": "0xFF01", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "engine_must_be_running", + "pre_conditions": "Engine running, no DTCs.", + "post_conditions": "Drive cycle armed; complete OEM-specific drive pattern.", + "citation": "OEM emission readiness procedure docs." + }, + { + "key": "brake_pad_change", + "display_name": "Brake Pad Change Service Position", + "category": "steering_brakes", + "applicability": "vw,audi,bmw,mercedes,volvo", + "routine_identifier": "0x0308", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "vehicle_may_move", + "pre_conditions": "Vehicle stationary, ignition on, EPB armed.", + "post_conditions": "Calipers retract; cluster shows pad-change mode.", + "citation": "OEM service info / brake pad replacement TSB." + }, + { + "key": "headlight_aim", + "display_name": "Headlight Beam Adaptation", + "category": "comfort", + "applicability": "vw,audi,bmw,mercedes", + "routine_identifier": "0x0411", + "sub_function": "0x01", + "option_record_hex": "", + "required_session_type": "0x03", + "safety": "vehicle_must_be_stationary", + "pre_conditions": "Vehicle on level surface, weights per spec, ignition on.", + "post_conditions": "Beam height stored; no headlight DTC.", + "citation": "OEM service info + ECE R48 alignment guidance." + } + ] +} \ No newline at end of file diff --git a/catalogs/uds-nrc.json b/catalogs/uds-nrc.json new file mode 100644 index 00000000..c42f188f --- /dev/null +++ b/catalogs/uds-nrc.json @@ -0,0 +1,375 @@ +{ + "schema_version": 1, + "spec": "ISO 14229-1", + "description": "UDS Negative Response Code catalog. Add or correct entries here without recompiling.", + "categories": [ + "general", + "security", + "request_data", + "condition", + "server", + "reserved" + ], + "entries": [ + { + "code": "0x00", + "short": "PR", + "description": "positiveResponse", + "category": "general" + }, + { + "code": "0x10", + "short": "GR", + "description": "generalReject", + "category": "general" + }, + { + "code": "0x11", + "short": "SNS", + "description": "serviceNotSupported", + "category": "general" + }, + { + "code": "0x12", + "short": "SFNS", + "description": "subFunctionNotSupported", + "category": "general" + }, + { + "code": "0x13", + "short": "IMLOIF", + "description": "incorrectMessageLengthOrInvalidFormat", + "category": "general" + }, + { + "code": "0x14", + "short": "RTL", + "description": "responseTooLong", + "category": "general" + }, + { + "code": "0x21", + "short": "BRR", + "description": "busyRepeatRequest", + "category": "condition" + }, + { + "code": "0x22", + "short": "CNC", + "description": "conditionsNotCorrect", + "category": "condition" + }, + { + "code": "0x24", + "short": "RSE", + "description": "requestSequenceError", + "category": "condition" + }, + { + "code": "0x25", + "short": "NRFSC", + "description": "noResponseFromSubnetComponent", + "category": "server" + }, + { + "code": "0x26", + "short": "FPEORA", + "description": "failurePreventsExecutionOfRequestedAction", + "category": "server" + }, + { + "code": "0x31", + "short": "ROOR", + "description": "requestOutOfRange", + "category": "request_data" + }, + { + "code": "0x33", + "short": "SAD", + "description": "securityAccessDenied", + "category": "security" + }, + { + "code": "0x34", + "short": "AR", + "description": "authenticationRequired", + "category": "security" + }, + { + "code": "0x35", + "short": "IK", + "description": "invalidKey", + "category": "security" + }, + { + "code": "0x36", + "short": "ENOA", + "description": "exceededNumberOfAttempts", + "category": "security" + }, + { + "code": "0x37", + "short": "RTDNE", + "description": "requiredTimeDelayNotExpired", + "category": "security" + }, + { + "code": "0x38", + "short": "SDTR", + "description": "secureDataTransmissionRequired", + "category": "security" + }, + { + "code": "0x39", + "short": "SDTNA", + "description": "secureDataTransmissionNotAllowed", + "category": "security" + }, + { + "code": "0x3A", + "short": "SDVF", + "description": "secureDataVerificationFailed", + "category": "security" + }, + { + "code": "0x50", + "short": "CVFITP", + "description": "certificateVerificationFailed_InvalidTimePeriod", + "category": "security" + }, + { + "code": "0x51", + "short": "CVFIS", + "description": "certificateVerificationFailed_InvalidSignature", + "category": "security" + }, + { + "code": "0x52", + "short": "CVFITC", + "description": "certificateVerificationFailed_InvalidChainOfTrust", + "category": "security" + }, + { + "code": "0x53", + "short": "CVFIT", + "description": "certificateVerificationFailed_InvalidType", + "category": "security" + }, + { + "code": "0x54", + "short": "CVFIF", + "description": "certificateVerificationFailed_InvalidFormat", + "category": "security" + }, + { + "code": "0x55", + "short": "CVFIC", + "description": "certificateVerificationFailed_InvalidContent", + "category": "security" + }, + { + "code": "0x56", + "short": "CVFIS2", + "description": "certificateVerificationFailed_InvalidScope", + "category": "security" + }, + { + "code": "0x57", + "short": "CVFIC2", + "description": "certificateVerificationFailed_InvalidCertificate", + "category": "security" + }, + { + "code": "0x58", + "short": "OVF", + "description": "ownershipVerificationFailed", + "category": "security" + }, + { + "code": "0x59", + "short": "CCF", + "description": "challengeCalculationFailed", + "category": "security" + }, + { + "code": "0x5A", + "short": "SARF", + "description": "settingAccessRightsFailed", + "category": "security" + }, + { + "code": "0x5B", + "short": "SKDF", + "description": "sessionKeyCreation/DerivationFailed", + "category": "security" + }, + { + "code": "0x5C", + "short": "CDUF", + "description": "configurationDataUsageFailed", + "category": "security" + }, + { + "code": "0x5D", + "short": "DVFAA", + "description": "deAuthenticationFailed", + "category": "security" + }, + { + "code": "0x70", + "short": "UDNA", + "description": "uploadDownloadNotAccepted", + "category": "server" + }, + { + "code": "0x71", + "short": "TDS", + "description": "transferDataSuspended", + "category": "server" + }, + { + "code": "0x72", + "short": "GPF", + "description": "generalProgrammingFailure", + "category": "server" + }, + { + "code": "0x73", + "short": "WBSC", + "description": "wrongBlockSequenceCounter", + "category": "server" + }, + { + "code": "0x78", + "short": "RCRRP", + "description": "requestCorrectlyReceived-ResponsePending", + "category": "condition" + }, + { + "code": "0x7E", + "short": "SFNSIAS", + "description": "subFunctionNotSupportedInActiveSession", + "category": "condition" + }, + { + "code": "0x7F", + "short": "SNSIAS", + "description": "serviceNotSupportedInActiveSession", + "category": "condition" + }, + { + "code": "0x81", + "short": "RPMTH", + "description": "rpmTooHigh", + "category": "condition" + }, + { + "code": "0x82", + "short": "RPMTL", + "description": "rpmTooLow", + "category": "condition" + }, + { + "code": "0x83", + "short": "EIR", + "description": "engineIsRunning", + "category": "condition" + }, + { + "code": "0x84", + "short": "EINR", + "description": "engineIsNotRunning", + "category": "condition" + }, + { + "code": "0x85", + "short": "ERTTL", + "description": "engineRunTimeTooLow", + "category": "condition" + }, + { + "code": "0x86", + "short": "TEMPTH", + "description": "temperatureTooHigh", + "category": "condition" + }, + { + "code": "0x87", + "short": "TEMPTL", + "description": "temperatureTooLow", + "category": "condition" + }, + { + "code": "0x88", + "short": "VSTH", + "description": "vehicleSpeedTooHigh", + "category": "condition" + }, + { + "code": "0x89", + "short": "VSTL", + "description": "vehicleSpeedTooLow", + "category": "condition" + }, + { + "code": "0x8A", + "short": "TPTH", + "description": "throttle/PedalTooHigh", + "category": "condition" + }, + { + "code": "0x8B", + "short": "TPTL", + "description": "throttle/PedalTooLow", + "category": "condition" + }, + { + "code": "0x8C", + "short": "TRNIN", + "description": "transmissionRangeNotInNeutral", + "category": "condition" + }, + { + "code": "0x8D", + "short": "TRNIG", + "description": "transmissionRangeNotInGear", + "category": "condition" + }, + { + "code": "0x8F", + "short": "BSNC", + "description": "brakeSwitch(es)NotClosed (Brake Pedal not pressed or not applied)", + "category": "condition" + }, + { + "code": "0x90", + "short": "SLNIP", + "description": "shifterLeverNotInPark", + "category": "condition" + }, + { + "code": "0x91", + "short": "TCCL", + "description": "torqueConverterClutchLocked", + "category": "condition" + }, + { + "code": "0x92", + "short": "VTH", + "description": "voltageTooHigh", + "category": "condition" + }, + { + "code": "0x93", + "short": "VTL", + "description": "voltageTooLow", + "category": "condition" + }, + { + "code": "0x94", + "short": "RTNT", + "description": "resourceTemporarilyNotAvailable", + "category": "server" + } + ] +} \ No newline at end of file diff --git a/catalogs/vin-countries.json b/catalogs/vin-countries.json new file mode 100644 index 00000000..d716cca2 --- /dev/null +++ b/catalogs/vin-countries.json @@ -0,0 +1,811 @@ +{ + "schema_version": 1, + "spec": "ISO 3779 / SAE J853", + "description": "VIN country ranges keyed by first two WMI characters.", + "entries": [ + { + "range_start": "AA", + "range_end": "AH", + "name": "South Africa", + "iso_code": "ZA" + }, + { + "range_start": "AJ", + "range_end": "AK", + "name": "Ivory Coast", + "iso_code": "CI" + }, + { + "range_start": "AL", + "range_end": "AM", + "name": "Lesotho", + "iso_code": "LS" + }, + { + "range_start": "AN", + "range_end": "AP", + "name": "Botswana", + "iso_code": "BW" + }, + { + "range_start": "AR", + "range_end": "AS", + "name": "Namibia", + "iso_code": "NA" + }, + { + "range_start": "AT", + "range_end": "AU", + "name": "Madagascar", + "iso_code": "MG" + }, + { + "range_start": "AV", + "range_end": "AW", + "name": "Mauritius", + "iso_code": "MU" + }, + { + "range_start": "AX", + "range_end": "AY", + "name": "Tunisia", + "iso_code": "TN" + }, + { + "range_start": "AZ", + "range_end": "A1", + "name": "Cyprus", + "iso_code": "CY" + }, + { + "range_start": "A2", + "range_end": "A3", + "name": "Zimbabwe", + "iso_code": "ZW" + }, + { + "range_start": "A4", + "range_end": "A5", + "name": "Mozambique", + "iso_code": "MZ" + }, + { + "range_start": "BA", + "range_end": "BB", + "name": "Angola", + "iso_code": "AO" + }, + { + "range_start": "BC", + "range_end": "BC", + "name": "Ethiopia", + "iso_code": "ET" + }, + { + "range_start": "BF", + "range_end": "BG", + "name": "Kenya", + "iso_code": "KE" + }, + { + "range_start": "BH", + "range_end": "BH", + "name": "Rwanda", + "iso_code": "RW" + }, + { + "range_start": "BL", + "range_end": "BL", + "name": "Nigeria", + "iso_code": "NG" + }, + { + "range_start": "BR", + "range_end": "BR", + "name": "Algeria", + "iso_code": "DZ" + }, + { + "range_start": "BT", + "range_end": "BT", + "name": "Swaziland", + "iso_code": "SZ" + }, + { + "range_start": "BU", + "range_end": "BU", + "name": "Uganda", + "iso_code": "UG" + }, + { + "range_start": "B3", + "range_end": "B4", + "name": "Libya", + "iso_code": "LY" + }, + { + "range_start": "CA", + "range_end": "CB", + "name": "Egypt", + "iso_code": "EG" + }, + { + "range_start": "CF", + "range_end": "CG", + "name": "Morocco", + "iso_code": "MA" + }, + { + "range_start": "CL", + "range_end": "CM", + "name": "Zambia", + "iso_code": "ZM" + }, + { + "range_start": "EA", + "range_end": "E0", + "name": "Russia", + "iso_code": "RU" + }, + { + "range_start": "HA", + "range_end": "H0", + "name": "China", + "iso_code": "CN" + }, + { + "range_start": "JA", + "range_end": "J0", + "name": "Japan", + "iso_code": "JP" + }, + { + "range_start": "KF", + "range_end": "KH", + "name": "Israel", + "iso_code": "IL" + }, + { + "range_start": "KL", + "range_end": "KR", + "name": "South Korea", + "iso_code": "KR" + }, + { + "range_start": "KS", + "range_end": "KT", + "name": "Jordan", + "iso_code": "JO" + }, + { + "range_start": "K1", + "range_end": "K3", + "name": "South Korea", + "iso_code": "KR" + }, + { + "range_start": "K5", + "range_end": "K5", + "name": "Kyrgyzstan", + "iso_code": "KG" + }, + { + "range_start": "LA", + "range_end": "L0", + "name": "China", + "iso_code": "CN" + }, + { + "range_start": "MA", + "range_end": "ME", + "name": "India", + "iso_code": "IN" + }, + { + "range_start": "MF", + "range_end": "MK", + "name": "Indonesia", + "iso_code": "ID" + }, + { + "range_start": "ML", + "range_end": "MR", + "name": "Thailand", + "iso_code": "TH" + }, + { + "range_start": "MS", + "range_end": "MS", + "name": "Myanmar", + "iso_code": "MM" + }, + { + "range_start": "MU", + "range_end": "MU", + "name": "Mongolia", + "iso_code": "MN" + }, + { + "range_start": "MX", + "range_end": "MX", + "name": "Kazakhstan", + "iso_code": "KZ" + }, + { + "range_start": "M1", + "range_end": "M0", + "name": "India", + "iso_code": "IN" + }, + { + "range_start": "NA", + "range_end": "NE", + "name": "Iran", + "iso_code": "IR" + }, + { + "range_start": "NF", + "range_end": "NG", + "name": "Pakistan", + "iso_code": "PK" + }, + { + "range_start": "NJ", + "range_end": "NJ", + "name": "Iraq", + "iso_code": "IQ" + }, + { + "range_start": "NL", + "range_end": "NR", + "name": "Turkey", + "iso_code": "TR" + }, + { + "range_start": "NS", + "range_end": "NT", + "name": "Uzbekistan", + "iso_code": "UZ" + }, + { + "range_start": "NV", + "range_end": "NV", + "name": "Azerbaijan", + "iso_code": "AZ" + }, + { + "range_start": "NX", + "range_end": "NX", + "name": "Tajikistan", + "iso_code": "TJ" + }, + { + "range_start": "NY", + "range_end": "NY", + "name": "Armenia", + "iso_code": "AM" + }, + { + "range_start": "N1", + "range_end": "N5", + "name": "Iran", + "iso_code": "IR" + }, + { + "range_start": "N7", + "range_end": "N8", + "name": "Turkey", + "iso_code": "TR" + }, + { + "range_start": "PA", + "range_end": "PC", + "name": "Philippines", + "iso_code": "PH" + }, + { + "range_start": "PF", + "range_end": "PG", + "name": "Singapore", + "iso_code": "SG" + }, + { + "range_start": "PL", + "range_end": "PR", + "name": "Malaysia", + "iso_code": "MY" + }, + { + "range_start": "PS", + "range_end": "PT", + "name": "Bangladesh", + "iso_code": "BD" + }, + { + "range_start": "P5", + "range_end": "P0", + "name": "India", + "iso_code": "IN" + }, + { + "range_start": "RA", + "range_end": "RB", + "name": "United Arab Emirates", + "iso_code": "AE" + }, + { + "range_start": "RF", + "range_end": "RK", + "name": "Taiwan", + "iso_code": "TW" + }, + { + "range_start": "RL", + "range_end": "RM", + "name": "Vietnam", + "iso_code": "VN" + }, + { + "range_start": "RP", + "range_end": "RP", + "name": "Laos", + "iso_code": "LA" + }, + { + "range_start": "RS", + "range_end": "RT", + "name": "Saudi Arabia", + "iso_code": "SA" + }, + { + "range_start": "RU", + "range_end": "RW", + "name": "Russia", + "iso_code": "RU" + }, + { + "range_start": "R1", + "range_end": "R7", + "name": "Hong Kong", + "iso_code": "HK" + }, + { + "range_start": "SA", + "range_end": "SM", + "name": "United Kingdom", + "iso_code": "GB" + }, + { + "range_start": "SN", + "range_end": "ST", + "name": "Germany", + "iso_code": "DE" + }, + { + "range_start": "SU", + "range_end": "SZ", + "name": "Poland", + "iso_code": "PL" + }, + { + "range_start": "S1", + "range_end": "S2", + "name": "Latvia", + "iso_code": "LV" + }, + { + "range_start": "S3", + "range_end": "S3", + "name": "Georgia", + "iso_code": "GE" + }, + { + "range_start": "S4", + "range_end": "S4", + "name": "Iceland", + "iso_code": "IS" + }, + { + "range_start": "TA", + "range_end": "TH", + "name": "Switzerland", + "iso_code": "CH" + }, + { + "range_start": "TJ", + "range_end": "TP", + "name": "Czech Republic", + "iso_code": "CZ" + }, + { + "range_start": "TR", + "range_end": "TV", + "name": "Hungary", + "iso_code": "HU" + }, + { + "range_start": "TW", + "range_end": "T1", + "name": "Portugal", + "iso_code": "PT" + }, + { + "range_start": "T3", + "range_end": "T5", + "name": "Republic of Serbia", + "iso_code": "RS" + }, + { + "range_start": "T6", + "range_end": "T6", + "name": "Andorra", + "iso_code": "AD" + }, + { + "range_start": "T7", + "range_end": "T8", + "name": "Netherlands", + "iso_code": "NL" + }, + { + "range_start": "UA", + "range_end": "UC", + "name": "Spain", + "iso_code": "ES" + }, + { + "range_start": "UH", + "range_end": "UM", + "name": "Denmark", + "iso_code": "DK" + }, + { + "range_start": "UN", + "range_end": "UR", + "name": "Ireland", + "iso_code": "IE" + }, + { + "range_start": "UU", + "range_end": "UX", + "name": "Romania", + "iso_code": "RO" + }, + { + "range_start": "U1", + "range_end": "U2", + "name": "Macedonia", + "iso_code": "MK" + }, + { + "range_start": "U5", + "range_end": "U7", + "name": "Slovakia", + "iso_code": "SK" + }, + { + "range_start": "U8", + "range_end": "U0", + "name": "Bosnia & Herzegovina", + "iso_code": "BA" + }, + { + "range_start": "VA", + "range_end": "VE", + "name": "Austria", + "iso_code": "AT" + }, + { + "range_start": "VF", + "range_end": "VR", + "name": "France", + "iso_code": "FR" + }, + { + "range_start": "VS", + "range_end": "VW", + "name": "Spain", + "iso_code": "ES" + }, + { + "range_start": "VX", + "range_end": "V2", + "name": "France", + "iso_code": "FR" + }, + { + "range_start": "V3", + "range_end": "V5", + "name": "Croatia", + "iso_code": "HR" + }, + { + "range_start": "V6", + "range_end": "V8", + "name": "Estonia", + "iso_code": "EE" + }, + { + "range_start": "WA", + "range_end": "W0", + "name": "Germany", + "iso_code": "DE" + }, + { + "range_start": "XA", + "range_end": "XC", + "name": "Bulgaria", + "iso_code": "BG" + }, + { + "range_start": "XD", + "range_end": "XE", + "name": "Russia", + "iso_code": "RU" + }, + { + "range_start": "XF", + "range_end": "XH", + "name": "Greece", + "iso_code": "GR" + }, + { + "range_start": "XJ", + "range_end": "XK", + "name": "Russia", + "iso_code": "RU" + }, + { + "range_start": "XL", + "range_end": "XR", + "name": "Netherlands", + "iso_code": "NL" + }, + { + "range_start": "XS", + "range_end": "XW", + "name": "Russia", + "iso_code": "RU" + }, + { + "range_start": "XX", + "range_end": "XY", + "name": "Luxembourg", + "iso_code": "LU" + }, + { + "range_start": "XZ", + "range_end": "X0", + "name": "Russia", + "iso_code": "RU" + }, + { + "range_start": "YA", + "range_end": "YE", + "name": "Belgium", + "iso_code": "BE" + }, + { + "range_start": "YF", + "range_end": "YK", + "name": "Finland", + "iso_code": "FI" + }, + { + "range_start": "YN", + "range_end": "YN", + "name": "Malta", + "iso_code": "MT" + }, + { + "range_start": "YS", + "range_end": "YW", + "name": "Sweden", + "iso_code": "SE" + }, + { + "range_start": "YX", + "range_end": "Y2", + "name": "Norway", + "iso_code": "NO" + }, + { + "range_start": "Y3", + "range_end": "Y5", + "name": "Belarus", + "iso_code": "BY" + }, + { + "range_start": "Y6", + "range_end": "Y8", + "name": "Ukraine", + "iso_code": "UA" + }, + { + "range_start": "ZA", + "range_end": "ZU", + "name": "Italy", + "iso_code": "IT" + }, + { + "range_start": "ZX", + "range_end": "ZZ", + "name": "Slovenia", + "iso_code": "SI" + }, + { + "range_start": "Z1", + "range_end": "Z1", + "name": "San Marino", + "iso_code": "SM" + }, + { + "range_start": "Z3", + "range_end": "Z5", + "name": "Lithuania", + "iso_code": "LT" + }, + { + "range_start": "Z6", + "range_end": "Z0", + "name": "Russia", + "iso_code": "RU" + }, + { + "range_start": "1A", + "range_end": "10", + "name": "United States", + "iso_code": "US" + }, + { + "range_start": "13", + "range_end": "13", + "name": "United States", + "iso_code": "US" + }, + { + "range_start": "2A", + "range_end": "25", + "name": "Canada", + "iso_code": "CA" + }, + { + "range_start": "3A", + "range_end": "3X", + "name": "Mexico", + "iso_code": "MX" + }, + { + "range_start": "34", + "range_end": "34", + "name": "Nicaragua", + "iso_code": "NI" + }, + { + "range_start": "35", + "range_end": "35", + "name": "Dominican Republic", + "iso_code": "DO" + }, + { + "range_start": "36", + "range_end": "36", + "name": "Honduras", + "iso_code": "HN" + }, + { + "range_start": "37", + "range_end": "37", + "name": "Panama", + "iso_code": "PA" + }, + { + "range_start": "38", + "range_end": "39", + "name": "Puerto Rico", + "iso_code": "PR" + }, + { + "range_start": "4A", + "range_end": "40", + "name": "United States", + "iso_code": "US" + }, + { + "range_start": "5A", + "range_end": "50", + "name": "United States", + "iso_code": "US" + }, + { + "range_start": "5G", + "range_end": "5G", + "name": "United States", + "iso_code": "US" + }, + { + "range_start": "6A", + "range_end": "6X", + "name": "Australia", + "iso_code": "AU" + }, + { + "range_start": "6Y", + "range_end": "61", + "name": "New Zealand", + "iso_code": "NZ" + }, + { + "range_start": "7A", + "range_end": "70", + "name": "United States", + "iso_code": "US" + }, + { + "range_start": "8A", + "range_end": "8E", + "name": "Argentina", + "iso_code": "AR" + }, + { + "range_start": "8F", + "range_end": "8G", + "name": "Chile", + "iso_code": "CL" + }, + { + "range_start": "8L", + "range_end": "8N", + "name": "Ecuador", + "iso_code": "EC" + }, + { + "range_start": "8S", + "range_end": "8T", + "name": "Peru", + "iso_code": "PE" + }, + { + "range_start": "8X", + "range_end": "8Z", + "name": "Venezuela", + "iso_code": "VE" + }, + { + "range_start": "82", + "range_end": "82", + "name": "Bolivia", + "iso_code": "BO" + }, + { + "range_start": "84", + "range_end": "84", + "name": "Costa Rica", + "iso_code": "CR" + }, + { + "range_start": "9A", + "range_end": "9E", + "name": "Brazil", + "iso_code": "BR" + }, + { + "range_start": "9F", + "range_end": "9G", + "name": "Colombia", + "iso_code": "CO" + }, + { + "range_start": "9S", + "range_end": "9V", + "name": "Uruguay", + "iso_code": "UY" + }, + { + "range_start": "91", + "range_end": "90", + "name": "Brazil", + "iso_code": "BR" + } + ] +} \ No newline at end of file diff --git a/catalogs/vin-plants.json b/catalogs/vin-plants.json new file mode 100644 index 00000000..aacf992d --- /dev/null +++ b/catalogs/vin-plants.json @@ -0,0 +1,146 @@ +{ + "schema_version": 1, + "description": "VIN plant-location map. Key = WMI(3) + plant character.", + "entries": [ + { + "key": "1FAA", + "code": "A", + "name": "Atlanta Assembly", + "city": "Hapeville", + "country": "USA" + }, + { + "key": "1FAD", + "code": "D", + "name": "Dearborn Assembly", + "city": "Dearborn", + "country": "USA" + }, + { + "key": "1FAF", + "code": "F", + "name": "Flat Rock Assembly", + "city": "Flat Rock", + "country": "USA" + }, + { + "key": "1FAK", + "code": "K", + "name": "Kansas City Assembly", + "city": "Claycomo", + "country": "USA" + }, + { + "key": "1FAP", + "code": "P", + "name": "Twin Cities Assembly", + "city": "St. Paul", + "country": "USA" + }, + { + "key": "1G1A", + "code": "A", + "name": "Lakewood Assembly", + "city": "Doraville", + "country": "USA" + }, + { + "key": "1G1D", + "code": "D", + "name": "Fairfax Assembly", + "city": "Kansas City", + "country": "USA" + }, + { + "key": "1G1F", + "code": "F", + "name": "Flint Assembly", + "city": "Flint", + "country": "USA" + }, + { + "key": "JT2A", + "code": "A", + "name": "Takaoka Plant", + "city": "Toyota", + "country": "Japan" + }, + { + "key": "JT2B", + "code": "B", + "name": "Tsutsumi Plant", + "city": "Toyota", + "country": "Japan" + }, + { + "key": "4T1K", + "code": "K", + "name": "Georgetown Plant", + "city": "Georgetown", + "country": "USA" + }, + { + "key": "1HGC", + "code": "C", + "name": "Marysville Auto Plant", + "city": "Marysville", + "country": "USA" + }, + { + "key": "1HGE", + "code": "E", + "name": "East Liberty Auto Plant", + "city": "East Liberty", + "country": "USA" + }, + { + "key": "JHMA", + "code": "A", + "name": "Suzuka Plant", + "city": "Suzuka", + "country": "Japan" + }, + { + "key": "WDBF", + "code": "F", + "name": "Sindelfingen Plant", + "city": "Sindelfingen", + "country": "Germany" + }, + { + "key": "WDBJ", + "code": "J", + "name": "Rastatt Plant", + "city": "Rastatt", + "country": "Germany" + }, + { + "key": "WBAA", + "code": "A", + "name": "Munich Plant", + "city": "Munich", + "country": "Germany" + }, + { + "key": "WBAC", + "code": "C", + "name": "Regensburg Plant", + "city": "Regensburg", + "country": "Germany" + }, + { + "key": "WVWW", + "code": "W", + "name": "Wolfsburg Plant", + "city": "Wolfsburg", + "country": "Germany" + }, + { + "key": "WVWZ", + "code": "Z", + "name": "Zwickau Plant", + "city": "Zwickau", + "country": "Germany" + } + ] +} \ No newline at end of file diff --git a/catalogs/vin-regions.json b/catalogs/vin-regions.json new file mode 100644 index 00000000..50c97904 --- /dev/null +++ b/catalogs/vin-regions.json @@ -0,0 +1,37 @@ +{ + "schema_version": 1, + "spec": "ISO 3779", + "description": "VIN region ranges keyed by first WMI character.", + "entries": [ + { + "range_start": "A", + "range_end": "C", + "name": "Africa" + }, + { + "range_start": "J", + "range_end": "R", + "name": "Asia" + }, + { + "range_start": "S", + "range_end": "Z", + "name": "Europe" + }, + { + "range_start": "1", + "range_end": "5", + "name": "North America" + }, + { + "range_start": "6", + "range_end": "7", + "name": "Oceania" + }, + { + "range_start": "8", + "range_end": "9", + "name": "South America" + } + ] +} \ No newline at end of file diff --git a/catalogs/vin-wmi-manufacturers.json b/catalogs/vin-wmi-manufacturers.json new file mode 100644 index 00000000..217b03f3 --- /dev/null +++ b/catalogs/vin-wmi-manufacturers.json @@ -0,0 +1,2219 @@ +{ + "schema_version": 1, + "spec": "ISO 3780 (WMI)", + "description": "World Manufacturer Identifier (3-char) to manufacturer name map.", + "entries": [ + { + "wmi": "AAV", + "name": "Volkswagen" + }, + { + "wmi": "AC5", + "name": "Hyundai" + }, + { + "wmi": "ADD", + "name": "Hyundai" + }, + { + "wmi": "AFA", + "name": "Ford" + }, + { + "wmi": "AHT", + "name": "Toyota" + }, + { + "wmi": "JA3", + "name": "Mitsubishi" + }, + { + "wmi": "JA4", + "name": "Mitsubishi" + }, + { + "wmi": "JA", + "name": "Isuzu" + }, + { + "wmi": "JD", + "name": "Daihatsu" + }, + { + "wmi": "JF", + "name": "Subaru" + }, + { + "wmi": "JHA", + "name": "Hino" + }, + { + "wmi": "JHB", + "name": "Hino" + }, + { + "wmi": "JHC", + "name": "Hino" + }, + { + "wmi": "JHD", + "name": "Hino" + }, + { + "wmi": "JHE", + "name": "Hino" + }, + { + "wmi": "JHF", + "name": "Honda" + }, + { + "wmi": "JHG", + "name": "Honda" + }, + { + "wmi": "JHL", + "name": "Honda" + }, + { + "wmi": "JHM", + "name": "Honda" + }, + { + "wmi": "JHN", + "name": "Honda" + }, + { + "wmi": "JHZ", + "name": "Honda" + }, + { + "wmi": "JH1", + "name": "Honda" + }, + { + "wmi": "JH2", + "name": "Honda" + }, + { + "wmi": "JH3", + "name": "Honda" + }, + { + "wmi": "JH4", + "name": "Honda" + }, + { + "wmi": "JH5", + "name": "Honda" + }, + { + "wmi": "JK", + "name": "Kawasaki" + }, + { + "wmi": "JL5", + "name": "Mitsubishi" + }, + { + "wmi": "JM1", + "name": "Mazda" + }, + { + "wmi": "JMB", + "name": "Mitsubishi" + }, + { + "wmi": "JMY", + "name": "Mitsubishi" + }, + { + "wmi": "JMZ", + "name": "Mazda" + }, + { + "wmi": "JN", + "name": "Infinity" + }, + { + "wmi": "JS", + "name": "Suzuki" + }, + { + "wmi": "JT3", + "name": "Toyota" + }, + { + "wmi": "JT", + "name": "Lexus" + }, + { + "wmi": "JY", + "name": "Yamaha" + }, + { + "wmi": "KL", + "name": "Daewoo" + }, + { + "wmi": "KM", + "name": "Hyundai" + }, + { + "wmi": "KMY", + "name": "Daelim" + }, + { + "wmi": "KM1", + "name": "Hyosung" + }, + { + "wmi": "KN", + "name": "Kia" + }, + { + "wmi": "KNM", + "name": "Renault" + }, + { + "wmi": "KPA", + "name": "SsangYong" + }, + { + "wmi": "KPT", + "name": "SsangYong" + }, + { + "wmi": "LAE", + "name": "Jinan Qingqi" + }, + { + "wmi": "LAL", + "name": "Honda" + }, + { + "wmi": "LAN", + "name": "Changzhou Yamasaki" + }, + { + "wmi": "LBB", + "name": "Keeway" + }, + { + "wmi": "LBE", + "name": "Beijing Hyundai" + }, + { + "wmi": "LBM", + "name": "Zongshen Piaggio" + }, + { + "wmi": "LBP", + "name": "Yamaha" + }, + { + "wmi": "LB2", + "name": "Geely" + }, + { + "wmi": "LCE", + "name": "Hangzhou Chunfeng" + }, + { + "wmi": "LDC", + "name": "Peugeot" + }, + { + "wmi": "LDD", + "name": "Dandong" + }, + { + "wmi": "LDF", + "name": "Dezhou Fulu" + }, + { + "wmi": "LDN", + "name": "SouEast" + }, + { + "wmi": "LDY", + "name": "Zhongtong Coach" + }, + { + "wmi": "LET", + "name": "Jiangling-Isuzu" + }, + { + "wmi": "LE4", + "name": "Beijing Benz" + }, + { + "wmi": "LFB", + "name": "FAW" + }, + { + "wmi": "LFG", + "name": "Taizhou Chuanl " + }, + { + "wmi": "LFP", + "name": "FAW" + }, + { + "wmi": "LFT", + "name": "FAW" + }, + { + "wmi": "LFV", + "name": "FAW" + }, + { + "wmi": "LFW", + "name": "FAW" + }, + { + "wmi": "LFY", + "name": "Changshu" + }, + { + "wmi": "LGB", + "name": "Dong Feng" + }, + { + "wmi": "LGH", + "name": "Qoros" + }, + { + "wmi": "LGX", + "name": "BYD" + }, + { + "wmi": "LHB", + "name": "Beijing Automotive Industry Holding" + }, + { + "wmi": "LH1", + "name": "FAW" + }, + { + "wmi": "LJC", + "name": "JAC" + }, + { + "wmi": "LJ1", + "name": "JAC" + }, + { + "wmi": "LKL", + "name": "Suzhou King Long" + }, + { + "wmi": "LL6", + "name": "Hunan Changfeng" + }, + { + "wmi": "LL8", + "name": "Linhai" + }, + { + "wmi": "LMC", + "name": "Suzuki" + }, + { + "wmi": "LPR", + "name": "Yamaha" + }, + { + "wmi": "LPS", + "name": "Polestar" + }, + { + "wmi": "LRW", + "name": "Tesla" + }, + { + "wmi": "LSG", + "name": "General Motors" + }, + { + "wmi": "LSJ", + "name": "MG" + }, + { + "wmi": "LSV", + "name": "Volkswagen" + }, + { + "wmi": "LSY", + "name": "Brilliance Zhonghua" + }, + { + "wmi": "LTP", + "name": "National Electric Vehicle Sweden AB" + }, + { + "wmi": "LTV", + "name": "Toyota" + }, + { + "wmi": "LUC", + "name": "Honda" + }, + { + "wmi": "LVS", + "name": "Ford" + }, + { + "wmi": "LVV", + "name": "Chery" + }, + { + "wmi": "LVZ", + "name": "Dong Feng Sokon Motor Company" + }, + { + "wmi": "LV3", + "name": "National Electric Vehicle Sweden AB" + }, + { + "wmi": "LZM", + "name": "MAN" + }, + { + "wmi": "LZE", + "name": "Isuzu" + }, + { + "wmi": "LZG", + "name": "Shaanxi" + }, + { + "wmi": "LZP", + "name": "Baotian" + }, + { + "wmi": "LZY", + "name": "Yutong Zhengzhou," + }, + { + "wmi": "LZZ", + "name": "Chongqing Shuangzing Mech & Elec" + }, + { + "wmi": "L4B", + "name": "Xingyue Group" + }, + { + "wmi": "L5C", + "name": "KangDi)" + }, + { + "wmi": "L5K", + "name": "Zhejiang Yongkang" + }, + { + "wmi": "L5N", + "name": "Zhejiang Taotao" + }, + { + "wmi": "L5Y", + "name": "Merato Motorcycle Taizhou Zhongneng" + }, + { + "wmi": "L85", + "name": "Zhejiang Yongkang Huabao Electric Appliance" + }, + { + "wmi": "L8X", + "name": "Zhejiang Summit Huawin Motorcycle" + }, + { + "wmi": "MAB", + "name": "Mahindra & Mahindra" + }, + { + "wmi": "MAC", + "name": "Mahindra & Mahindra" + }, + { + "wmi": "MAJ", + "name": "Ford" + }, + { + "wmi": "MAK", + "name": "Honda" + }, + { + "wmi": "MAL", + "name": "Hyundai " + }, + { + "wmi": "MAT", + "name": "Tata Motors" + }, + { + "wmi": "MA1", + "name": "Mahindra & Mahindra" + }, + { + "wmi": "MA3", + "name": "Suzuki" + }, + { + "wmi": "MA6", + "name": "GM" + }, + { + "wmi": "MA7", + "name": "Mitsubishi" + }, + { + "wmi": "MB8", + "name": "Suzuki" + }, + { + "wmi": "MBH", + "name": "Suzuki" + }, + { + "wmi": "MBJ", + "name": "Toyota" + }, + { + "wmi": "MBR", + "name": "Mercedes-Benz" + }, + { + "wmi": "MB1", + "name": "Ashok Leyland" + }, + { + "wmi": "MCA", + "name": "Fiat" + }, + { + "wmi": "MCB", + "name": "GM" + }, + { + "wmi": "MC2", + "name": "Volvo" + }, + { + "wmi": "MDH", + "name": "Nissan" + }, + { + "wmi": "MD2", + "name": "Bajaj" + }, + { + "wmi": "MD9", + "name": "Shuttle Cars" + }, + { + "wmi": "MEC", + "name": "Daimler" + }, + { + "wmi": "MEE", + "name": "Renault" + }, + { + "wmi": "MEX", + "name": "Volkswagen" + }, + { + "wmi": "MHF", + "name": "Toyota" + }, + { + "wmi": "MHR", + "name": "Honda" + }, + { + "wmi": "MLC", + "name": "Suzuki" + }, + { + "wmi": "NAA", + "name": "Peugeot" + }, + { + "wmi": "NAP", + "name": "Pars Khodro" + }, + { + "wmi": "MLH", + "name": "Honda" + }, + { + "wmi": "MMA", + "name": "Mitsubishi" + }, + { + "wmi": "MMB", + "name": "Mitsubishi" + }, + { + "wmi": "MMC", + "name": "Mitsubishi" + }, + { + "wmi": "MMM", + "name": "Chevrolet" + }, + { + "wmi": "MMS", + "name": "Suzuki" + }, + { + "wmi": "MMT", + "name": "Mitsubishi" + }, + { + "wmi": "MMU", + "name": "Holden" + }, + { + "wmi": "MM8", + "name": "Mazda" + }, + { + "wmi": "MNB", + "name": "Ford" + }, + { + "wmi": "MNT", + "name": "Nissan" + }, + { + "wmi": "MPA", + "name": "Isuzu" + }, + { + "wmi": "MP1", + "name": "Isuzu" + }, + { + "wmi": "MRH", + "name": "Honda" + }, + { + "wmi": "MR0", + "name": "Toyota" + }, + { + "wmi": "MS0", + "name": "SSS MOTORS" + }, + { + "wmi": "MS3", + "name": "Suzuki" + }, + { + "wmi": "NLA", + "name": "Honda " + }, + { + "wmi": "NLE", + "name": "Mercedes-Benz" + }, + { + "wmi": "NLH", + "name": "Hyundai" + }, + { + "wmi": "NLN", + "name": "Karsan" + }, + { + "wmi": "NLR", + "name": "OTOKAR" + }, + { + "wmi": "NLT", + "name": "TEMSA" + }, + { + "wmi": "NMB", + "name": "Mercedes-Benz" + }, + { + "wmi": "NMC", + "name": "BMC" + }, + { + "wmi": "NM0", + "name": "Ford" + }, + { + "wmi": "NM4", + "name": "Tofaş" + }, + { + "wmi": "NMT", + "name": "Toyota" + }, + { + "wmi": "NNA", + "name": "Isuzu" + }, + { + "wmi": "PE1", + "name": "Ford" + }, + { + "wmi": "PE3", + "name": "Mazda" + }, + { + "wmi": "PL1", + "name": "Proton," + }, + { + "wmi": "PNA", + "name": "Peugeot" + }, + { + "wmi": "R2P", + "name": "Evoke" + }, + { + "wmi": "RA1", + "name": "Steyr" + }, + { + "wmi": "RFB", + "name": "Kymco" + }, + { + "wmi": "RFG", + "name": "Sanyang SYM" + }, + { + "wmi": "RFL", + "name": "Adly" + }, + { + "wmi": "RFT", + "name": "CPI" + }, + { + "wmi": "RF3", + "name": "Aeon" + }, + { + "wmi": "SAB", + "name": "Optare" + }, + { + "wmi": "SAD", + "name": "Jaguar" + }, + { + "wmi": "SAL", + "name": "Land Rover" + }, + { + "wmi": "SAJ", + "name": "Jaguar" + }, + { + "wmi": "SAR", + "name": "Rover" + }, + { + "wmi": "SAX", + "name": "Austin-Rover" + }, + { + "wmi": "SA9", + "name": "OX Global" + }, + { + "wmi": "SB1", + "name": "Toyota" + }, + { + "wmi": "SBM", + "name": "McLaren" + }, + { + "wmi": "SCA", + "name": "Rolls Royce" + }, + { + "wmi": "SCB", + "name": "Bentley" + }, + { + "wmi": "SCC", + "name": "Lotus" + }, + { + "wmi": "SCE", + "name": "DeLorean" + }, + { + "wmi": "SCF", + "name": "Aston Martin" + }, + { + "wmi": "SCK", + "name": "iFor Williams" + }, + { + "wmi": "SDB", + "name": "Peugeot" + }, + { + "wmi": "SED", + "name": "General Motors" + }, + { + "wmi": "SEY", + "name": "LDV" + }, + { + "wmi": "SFA", + "name": "Ford" + }, + { + "wmi": "SFD", + "name": "Alexander Dennis" + }, + { + "wmi": "SHH", + "name": "Honda" + }, + { + "wmi": "SHS", + "name": "Honda" + }, + { + "wmi": "SJN", + "name": "Nissan" + }, + { + "wmi": "SKF", + "name": "Vauxhall" + }, + { + "wmi": "SLP", + "name": "JCB" + }, + { + "wmi": "SMT", + "name": "Triumph" + }, + { + "wmi": "SUF", + "name": "Fiat" + }, + { + "wmi": "SUL", + "name": "FSC" + }, + { + "wmi": "SUP", + "name": "FSO-Daewoo" + }, + { + "wmi": "SU9", + "name": "Solaris" + }, + { + "wmi": "SUU", + "name": "Solaris" + }, + { + "wmi": "SWV", + "name": "TA-NO " + }, + { + "wmi": "TCC", + "name": "Smart" + }, + { + "wmi": "TDM", + "name": "QUANTYA" + }, + { + "wmi": "TK9", + "name": "SOR" + }, + { + "wmi": "TMA", + "name": "Hyundai" + }, + { + "wmi": "TMB", + "name": "Škoda)" + }, + { + "wmi": "TMK", + "name": "Karosa" + }, + { + "wmi": "TMP", + "name": "Škoda" + }, + { + "wmi": "TMT", + "name": "Tatra" + }, + { + "wmi": "TM9", + "name": "Škoda" + }, + { + "wmi": "TNE", + "name": "TAZ" + }, + { + "wmi": "TN9", + "name": "Karosa" + }, + { + "wmi": "TRA", + "name": "Ikarus" + }, + { + "wmi": "TRU", + "name": "Audi" + }, + { + "wmi": "TSB", + "name": "Ikarus" + }, + { + "wmi": "TSE", + "name": "Ikarus" + }, + { + "wmi": "TSM", + "name": "Suzuki" + }, + { + "wmi": "TW1", + "name": "Toyota " + }, + { + "wmi": "TYA", + "name": "Mitsubishi" + }, + { + "wmi": "TYB", + "name": "Mitsubishi" + }, + { + "wmi": "UU1", + "name": "Dacia" + }, + { + "wmi": "UU2", + "name": "Oltcit" + }, + { + "wmi": "UU3", + "name": "ARO" + }, + { + "wmi": "UU4", + "name": "Roman SA" + }, + { + "wmi": "UU5", + "name": "Rocar" + }, + { + "wmi": "UU6", + "name": "Daewoo" + }, + { + "wmi": "UU7", + "name": "Euro Bus Diamond" + }, + { + "wmi": "UU9", + "name": "Astra" + }, + { + "wmi": "UV9", + "name": "ATP" + }, + { + "wmi": "UZT", + "name": "UTB" + }, + { + "wmi": "U5Y", + "name": "Kia" + }, + { + "wmi": "U6Y", + "name": "Kia" + }, + { + "wmi": "VAG", + "name": "Magna Steyr Puch" + }, + { + "wmi": "VAN", + "name": "MAN" + }, + { + "wmi": "VBK", + "name": "KTM" + }, + { + "wmi": "VF1", + "name": "Renault" + }, + { + "wmi": "VF2", + "name": "Renault" + }, + { + "wmi": "VF3", + "name": "Peugeot" + }, + { + "wmi": "VF4", + "name": "Talbot" + }, + { + "wmi": "VF6", + "name": "Renault" + }, + { + "wmi": "VF7", + "name": "Citroën" + }, + { + "wmi": "VF8", + "name": "Matra" + }, + { + "wmi": "VF9", + "name": "Bugatti" + }, + { + "wmi": "VG5", + "name": "MBK" + }, + { + "wmi": "VLU", + "name": "Scania" + }, + { + "wmi": "VN1", + "name": "SOVAB" + }, + { + "wmi": "VNE", + "name": "Irisbus" + }, + { + "wmi": "VNK", + "name": "Toyota" + }, + { + "wmi": "VNV", + "name": "Renault-Nissan" + }, + { + "wmi": "VSA", + "name": "Mercedes-Benz" + }, + { + "wmi": "VSE", + "name": "Suzuki" + }, + { + "wmi": "VSK", + "name": "Nissan" + }, + { + "wmi": "VSS", + "name": "SEAT" + }, + { + "wmi": "VSX", + "name": "Opel" + }, + { + "wmi": "VS6", + "name": "Ford" + }, + { + "wmi": "VS7", + "name": "Citroën" + }, + { + "wmi": "VS9", + "name": "Carrocerias Ayats" + }, + { + "wmi": "VTH", + "name": "Derbi" + }, + { + "wmi": "VTL", + "name": "Yamaha" + }, + { + "wmi": "VTT", + "name": "Suzuki" + }, + { + "wmi": "VV9", + "name": "TAURO" + }, + { + "wmi": "VWA", + "name": "Nissan" + }, + { + "wmi": "VWV", + "name": "Volkswagen" + }, + { + "wmi": "VX1", + "name": "Zastava / Yugo Serbia" + }, + { + "wmi": "WAG", + "name": "Neoplan" + }, + { + "wmi": "WAU", + "name": "Audi" + }, + { + "wmi": "WA1", + "name": "Audi" + }, + { + "wmi": "WBA", + "name": "BMW" + }, + { + "wmi": "WBS", + "name": "BMW" + }, + { + "wmi": "WBW", + "name": "BMW" + }, + { + "wmi": "WBY", + "name": "BMW" + }, + { + "wmi": "WB1", + "name": "BMW" + }, + { + "wmi": "WDA", + "name": "Daimler" + }, + { + "wmi": "WDB", + "name": "Mercedes-Benz" + }, + { + "wmi": "WDC", + "name": "DaimlerChrysler" + }, + { + "wmi": "WDD", + "name": "Mercedes-Benz" + }, + { + "wmi": "WDF", + "name": "Mercedes-Benz" + }, + { + "wmi": "WEB", + "name": "Evobus" + }, + { + "wmi": "WJM", + "name": "Iveco" + }, + { + "wmi": "WF0", + "name": "Ford" + }, + { + "wmi": "WKE", + "name": "Krone" + }, + { + "wmi": "WKK", + "name": "Kässbohrer/Setra" + }, + { + "wmi": "WMA", + "name": "MAN" + }, + { + "wmi": "WME", + "name": "Smart" + }, + { + "wmi": "WMW", + "name": "MINI" + }, + { + "wmi": "WMX", + "name": "Mercedes-AMG" + }, + { + "wmi": "WMZ", + "name": "MINI" + }, + { + "wmi": "WP0", + "name": "Porsche" + }, + { + "wmi": "WP1", + "name": "Porsche" + }, + { + "wmi": "WSM", + "name": "Schmitz-Cargobull" + }, + { + "wmi": "W09", + "name": "RUF" + }, + { + "wmi": "W0L", + "name": "Opel" + }, + { + "wmi": "W0V", + "name": "Opel" + }, + { + "wmi": "W1K", + "name": "Mercedes" + }, + { + "wmi": "W1N", + "name": "Mercedes" + }, + { + "wmi": "WAP", + "name": "BMW Alpine" + }, + { + "wmi": "WUA", + "name": "Audi" + }, + { + "wmi": "WVG", + "name": "Volkswagen" + }, + { + "wmi": "WVW", + "name": "Volkswagen" + }, + { + "wmi": "WV1", + "name": "Volkswagen" + }, + { + "wmi": "WV2", + "name": "Volkswagen" + }, + { + "wmi": "WV3", + "name": "Volkswagen " + }, + { + "wmi": "XLB", + "name": "Volvo" + }, + { + "wmi": "XLE", + "name": "Scania" + }, + { + "wmi": "XLR", + "name": "DAF" + }, + { + "wmi": "XL4", + "name": "Lightyear" + }, + { + "wmi": "XL9", + "name": "Spyker" + }, + { + "wmi": "XMC", + "name": "Mitsubishi" + }, + { + "wmi": "XMG", + "name": "VDL" + }, + { + "wmi": "XTA", + "name": "Lada/AvtoVAZ" + }, + { + "wmi": "XTC", + "name": "KAMAZ" + }, + { + "wmi": "XTH", + "name": "GAZ" + }, + { + "wmi": "XTT", + "name": "UAZ/Sollers" + }, + { + "wmi": "XTU", + "name": "Trolza" + }, + { + "wmi": "XTY", + "name": "LiAZ" + }, + { + "wmi": "XUF", + "name": "General Motors" + }, + { + "wmi": "XUU", + "name": "General Motors" + }, + { + "wmi": "XW8", + "name": "Volkswagen" + }, + { + "wmi": "XWB", + "name": "Daewoo" + }, + { + "wmi": "XWE", + "name": "Hyundai-Kia" + }, + { + "wmi": "X1M", + "name": "PAZ" + }, + { + "wmi": "X4X", + "name": "BMW" + }, + { + "wmi": "X7L", + "name": "Renault" + }, + { + "wmi": "X7M", + "name": "Hyundai" + }, + { + "wmi": "YAR", + "name": "Toyota" + }, + { + "wmi": "YBW", + "name": "Volkswagen" + }, + { + "wmi": "YB1", + "name": "Volvo" + }, + { + "wmi": "YCM", + "name": "Mazda" + }, + { + "wmi": "YE2", + "name": "Van Hool" + }, + { + "wmi": "YH2", + "name": "Lynx" + }, + { + "wmi": "YK1", + "name": "Saab-Valmet" + }, + { + "wmi": "YSC", + "name": "Cadillac" + }, + { + "wmi": "YS2", + "name": "Scania" + }, + { + "wmi": "YS3", + "name": "Saab" + }, + { + "wmi": "YS4", + "name": "Scania" + }, + { + "wmi": "YTN", + "name": "Saab" + }, + { + "wmi": "YT9", + "name": "Koenigsegg" + }, + { + "wmi": "007", + "name": "Koenigsegg" + }, + { + "wmi": "YT9", + "name": "Carvia" + }, + { + "wmi": "034", + "name": "Carvia" + }, + { + "wmi": "YU7", + "name": "Husaberg" + }, + { + "wmi": "YVV", + "name": "Polestar" + }, + { + "wmi": "YV1", + "name": "Volvo" + }, + { + "wmi": "YV4", + "name": "Volvo" + }, + { + "wmi": "YV2", + "name": "Volvo" + }, + { + "wmi": "YV3", + "name": "Volvo" + }, + { + "wmi": "Y3M", + "name": "MAZ" + }, + { + "wmi": "Y6D", + "name": "Zaporozhets" + }, + { + "wmi": "ZAA", + "name": "Autobianchi" + }, + { + "wmi": "ZAM", + "name": "Maserati" + }, + { + "wmi": "ZAP", + "name": "Piaggio/Vespa/Gilera" + }, + { + "wmi": "ZAR", + "name": "Alfa Romeo" + }, + { + "wmi": "ZA9", + "name": "Lamborghini" + }, + { + "wmi": "ZBN", + "name": "Benelli" + }, + { + "wmi": "ZCG", + "name": "Cagiva SpA / MV Agusta" + }, + { + "wmi": "ZCF", + "name": "Iveco" + }, + { + "wmi": "ZDC", + "name": "Honda" + }, + { + "wmi": "ZDM", + "name": "Ducati" + }, + { + "wmi": "ZDF", + "name": "Ferrari" + }, + { + "wmi": "ZD0", + "name": "Yamaha" + }, + { + "wmi": "ZD3", + "name": "Beta Motor" + }, + { + "wmi": "ZD4", + "name": "Aprilia" + }, + { + "wmi": "ZFA", + "name": "Fiat" + }, + { + "wmi": "ZFC", + "name": "Fiat" + }, + { + "wmi": "ZFF", + "name": "Ferrari" + }, + { + "wmi": "ZGU", + "name": "Moto Guzzi" + }, + { + "wmi": "ZHW", + "name": "Lamborghini" + }, + { + "wmi": "ZJM", + "name": "Malaguti" + }, + { + "wmi": "ZJN", + "name": "Innocenti" + }, + { + "wmi": "ZKH", + "name": "Husqvarna" + }, + { + "wmi": "ZLA", + "name": "Lancia" + }, + { + "wmi": "Z8M", + "name": "Marussia" + }, + { + "wmi": "137", + "name": "Hummer" + }, + { + "wmi": "1B3", + "name": "Dodge" + }, + { + "wmi": "1C3", + "name": "Chrysler" + }, + { + "wmi": "1C4", + "name": "Dodge" + }, + { + "wmi": "1C6", + "name": "Chrysler" + }, + { + "wmi": "1D3", + "name": "Dodge" + }, + { + "wmi": "1FA", + "name": "Ford" + }, + { + "wmi": "1FB", + "name": "Ford" + }, + { + "wmi": "1FC", + "name": "Ford" + }, + { + "wmi": "1FD", + "name": "Ford" + }, + { + "wmi": "1FM", + "name": "Ford" + }, + { + "wmi": "1FT", + "name": "Ford" + }, + { + "wmi": "1FU", + "name": "Freightliner" + }, + { + "wmi": "1FV", + "name": "Freightliner" + }, + { + "wmi": "1F9", + "name": "FWD." + }, + { + "wmi": "1G", + "name": "General Motors" + }, + { + "wmi": "1GC", + "name": "Chevrolet" + }, + { + "wmi": "1GT", + "name": "GMC" + }, + { + "wmi": "1G1", + "name": "Chevrolet" + }, + { + "wmi": "1G2", + "name": "Pontiac" + }, + { + "wmi": "1G3", + "name": "Oldsmobile" + }, + { + "wmi": "1G4", + "name": "Buick" + }, + { + "wmi": "1G6", + "name": "Cadillac" + }, + { + "wmi": "1G8", + "name": "Saturn" + }, + { + "wmi": "1GM", + "name": "Pontiac" + }, + { + "wmi": "1GN", + "name": "Chevrolet" + }, + { + "wmi": "1GY", + "name": "Cadillac" + }, + { + "wmi": "1H", + "name": "Honda" + }, + { + "wmi": "1HD", + "name": "Harley-Davidson" + }, + { + "wmi": "1HT", + "name": "International Truck and Engine Corp" + }, + { + "wmi": "1J4", + "name": "Jeep" + }, + { + "wmi": "1J8", + "name": "Jeep" + }, + { + "wmi": "1L", + "name": "Lincoln" + }, + { + "wmi": "1ME", + "name": "Mercury" + }, + { + "wmi": "1M1", + "name": "Mack" + }, + { + "wmi": "1M2", + "name": "Mack" + }, + { + "wmi": "1M3", + "name": "Mack" + }, + { + "wmi": "1M4", + "name": "Mack" + }, + { + "wmi": "1M9", + "name": "Mynatt" + }, + { + "wmi": "1N", + "name": "Nissan" + }, + { + "wmi": "1NX", + "name": "NUMMI" + }, + { + "wmi": "1P3", + "name": "Plymouth" + }, + { + "wmi": "1PY", + "name": "John Deere" + }, + { + "wmi": "1R9", + "name": "Roadrunner" + }, + { + "wmi": "1VW", + "name": "Volkswagen" + }, + { + "wmi": "1XK", + "name": "Kenworth" + }, + { + "wmi": "1XP", + "name": "Peterbilt" + }, + { + "wmi": "1YV", + "name": "Mazda" + }, + { + "wmi": "1ZV", + "name": "Ford" + }, + { + "wmi": "2A4", + "name": "Chrysler" + }, + { + "wmi": "2BP", + "name": "Bombardier" + }, + { + "wmi": "2B3", + "name": "Dodge" + }, + { + "wmi": "2B7", + "name": "Dodge" + }, + { + "wmi": "2C3", + "name": "Dodge" + }, + { + "wmi": "2CN", + "name": "Chevrolet" + }, + { + "wmi": "2D3", + "name": "Dodge" + }, + { + "wmi": "2FA", + "name": "Ford" + }, + { + "wmi": "2FB", + "name": "Ford" + }, + { + "wmi": "2FC", + "name": "Ford" + }, + { + "wmi": "2FM", + "name": "Ford" + }, + { + "wmi": "2FT", + "name": "Ford" + }, + { + "wmi": "2FU", + "name": "Freightliner" + }, + { + "wmi": "2FV", + "name": "Freightliner" + }, + { + "wmi": "2FZ", + "name": "Sterling" + }, + { + "wmi": "2Gx", + "name": "General Motors" + }, + { + "wmi": "2GC", + "name": "Chevrolet" + }, + { + "wmi": "2G1", + "name": "Chevrolet" + }, + { + "wmi": "2G2", + "name": "Pontiac" + }, + { + "wmi": "2G3", + "name": "Oldsmobile" + }, + { + "wmi": "2G4", + "name": "Buick" + }, + { + "wmi": "2HG", + "name": "Honda" + }, + { + "wmi": "2HK", + "name": "Honda" + }, + { + "wmi": "2HJ", + "name": "Honda" + }, + { + "wmi": "2HM", + "name": "Hyundai" + }, + { + "wmi": "2M", + "name": "Mercury" + }, + { + "wmi": "2NV", + "name": "Nova" + }, + { + "wmi": "2P3", + "name": "Plymouth" + }, + { + "wmi": "2T2", + "name": "Lexus" + }, + { + "wmi": "2T", + "name": "Toyota" + }, + { + "wmi": "2TP", + "name": "Triple E" + }, + { + "wmi": "2V4", + "name": "Volkswagen" + }, + { + "wmi": "2V8", + "name": "Volkswagen" + }, + { + "wmi": "2WK", + "name": "Western Star" + }, + { + "wmi": "2WL", + "name": "Western Star" + }, + { + "wmi": "2WM", + "name": "Western Star" + }, + { + "wmi": "363", + "name": "Spyker" + }, + { + "wmi": "3C4", + "name": "Chrysler" + }, + { + "wmi": "3C6", + "name": "RAM" + }, + { + "wmi": "3D3", + "name": "Dodge" + }, + { + "wmi": "3D4", + "name": "Dodge" + }, + { + "wmi": "3FA", + "name": "Ford" + }, + { + "wmi": "3FE", + "name": "Ford" + }, + { + "wmi": "3G", + "name": "General Motors" + }, + { + "wmi": "3H", + "name": "Honda" + }, + { + "wmi": "3JB", + "name": "BRP" + }, + { + "wmi": "3MD", + "name": "Mazda" + }, + { + "wmi": "3MZ", + "name": "Mazda" + }, + { + "wmi": "3N", + "name": "Nissan" + }, + { + "wmi": "3NS", + "name": "Polaris" + }, + { + "wmi": "3NE", + "name": "Polaris" + }, + { + "wmi": "3P3", + "name": "Plymouth" + }, + { + "wmi": "3VW", + "name": "Volkswagen" + }, + { + "wmi": "46J", + "name": "Federal Motors Inc" + }, + { + "wmi": "4EN", + "name": "Emergency One" + }, + { + "wmi": "4F", + "name": "Mazda" + }, + { + "wmi": "4JG", + "name": "Mercedes-Benz" + }, + { + "wmi": "4M", + "name": "Mercury" + }, + { + "wmi": "4P1", + "name": "Pierce Manufacturing Inc" + }, + { + "wmi": "4RK", + "name": "Nova" + }, + { + "wmi": "4S", + "name": "Subaru-Isuzu" + }, + { + "wmi": "4T", + "name": "Toyota" + }, + { + "wmi": "4T9", + "name": "Lumen Motors" + }, + { + "wmi": "4UF", + "name": "Arctic Cat Inc." + }, + { + "wmi": "4US", + "name": "BMW" + }, + { + "wmi": "4UZ", + "name": "Frt-Thomas" + }, + { + "wmi": "4V1", + "name": "Volvo" + }, + { + "wmi": "4V2", + "name": "Volvo" + }, + { + "wmi": "4V3", + "name": "Volvo" + }, + { + "wmi": "4V4", + "name": "Volvo" + }, + { + "wmi": "4V5", + "name": "Volvo" + }, + { + "wmi": "4V6", + "name": "Volvo" + }, + { + "wmi": "4VL", + "name": "Volvo" + }, + { + "wmi": "4VM", + "name": "Volvo" + }, + { + "wmi": "4VZ", + "name": "Volvo" + }, + { + "wmi": "538", + "name": "Zero" + }, + { + "wmi": "5F", + "name": "Honda" + }, + { + "wmi": "5G", + "name": "Hummer" + }, + { + "wmi": "5J", + "name": "Honda" + }, + { + "wmi": "5L", + "name": "Lincoln" + }, + { + "wmi": "5N1", + "name": "Infinity" + }, + { + "wmi": "5NP", + "name": "Hyundai" + }, + { + "wmi": "5T", + "name": "Toyota" + }, + { + "wmi": "5YJ", + "name": "Tesla" + }, + { + "wmi": "5XY", + "name": "Kia" + }, + { + "wmi": "5UX", + "name": "BMW" + }, + { + "wmi": "56K", + "name": "Indian" + }, + { + "wmi": "6AB", + "name": "MAN" + }, + { + "wmi": "6F4", + "name": "Nissan" + }, + { + "wmi": "6F5", + "name": "Kenworth" + }, + { + "wmi": "6FP", + "name": "Ford" + }, + { + "wmi": "6G1", + "name": "Holden" + }, + { + "wmi": "6G2", + "name": "Pontiac" + }, + { + "wmi": "6H8", + "name": "Holden" + }, + { + "wmi": "6MM", + "name": "Mitsubishi" + }, + { + "wmi": "6T1", + "name": "Toyota" + }, + { + "wmi": "6U9", + "name": "Privately Imported car in Australia" + }, + { + "wmi": "795", + "name": "Bugatti" + }, + { + "wmi": "8AD", + "name": "Peugeot" + }, + { + "wmi": "8AF", + "name": "Ford" + }, + { + "wmi": "8AG", + "name": "Chevrolet" + }, + { + "wmi": "8AJ", + "name": "Toyota" + }, + { + "wmi": "8AK", + "name": "Suzuki" + }, + { + "wmi": "8AP", + "name": "Fiat" + }, + { + "wmi": "8AW", + "name": "Volkswagen" + }, + { + "wmi": "8A1", + "name": "Renault" + }, + { + "wmi": "8GD", + "name": "Peugeot" + }, + { + "wmi": "8GG", + "name": "Chevrolet" + }, + { + "wmi": "8LD", + "name": "Chevrolet" + }, + { + "wmi": "935", + "name": "Citroën" + }, + { + "wmi": "936", + "name": "Peugeot" + }, + { + "wmi": "93H", + "name": "Honda" + }, + { + "wmi": "93R", + "name": "Toyota" + }, + { + "wmi": "93U", + "name": "Audi" + }, + { + "wmi": "93V", + "name": "Audi" + }, + { + "wmi": "93X", + "name": "Mitsubishi" + }, + { + "wmi": "93Y", + "name": "Renault" + }, + { + "wmi": "94D", + "name": "Nissan" + }, + { + "wmi": "9BF", + "name": "Ford" + }, + { + "wmi": "9BG", + "name": "Chevrolet" + }, + { + "wmi": "9BM", + "name": "Mercedes-Benz" + }, + { + "wmi": "9BR", + "name": "Toyota" + }, + { + "wmi": "9BS", + "name": "Scania" + }, + { + "wmi": "9BW", + "name": "Volkswagen" + }, + { + "wmi": "9FB", + "name": "Renault" + }, + { + "wmi": "9GA", + "name": "Chevrolet" + } + ] +} \ No newline at end of file diff --git a/catalogs/wwhobd-dids.json b/catalogs/wwhobd-dids.json new file mode 100644 index 00000000..f2fab76a --- /dev/null +++ b/catalogs/wwhobd-dids.json @@ -0,0 +1,117 @@ +{ + "schema_version": 1, + "spec": "ISO 27145-3 + UN GTR No.5 Annex A", + "description": "Universal WWH-OBD Data Identifiers. Add OEM-specific DIDs without recompiling.", + "entries": [ + { + "did": "0xF190", + "name": "VIN", + "description": "Vehicle Identification Number (17 ASCII)" + }, + { + "did": "0xF197", + "name": "VehicleFamilyId", + "description": "Emissions vehicle-family identifier" + }, + { + "did": "0xF198", + "name": "CalibrationID", + "description": "Calibration ID per ISO 15031-5" + }, + { + "did": "0xF199", + "name": "CVN", + "description": "Calibration Verification Number" + }, + { + "did": "0xF19A", + "name": "ECUName", + "description": "ECU name string" + }, + { + "did": "0xF184", + "name": "ProgrammingDate", + "description": "Last reprogramming date" + }, + { + "did": "0xF186", + "name": "ActiveDiagnosticSession", + "description": "Currently active UDS session" + }, + { + "did": "0xFD00", + "name": "WWHOBDProtocolVersion", + "description": "WWH-OBD protocol version" + }, + { + "did": "0xFD01", + "name": "OBDRequirement", + "description": "OBD certification requirement (e.g. EOBD, WWH-OBD)" + }, + { + "did": "0xFD02", + "name": "OBDMIDList", + "description": "List of supported OBDMIDs" + }, + { + "did": "0xFD03", + "name": "ActiveDTCs", + "description": "Stream of active DTCs in J1939-FMI form" + }, + { + "did": "0xFD04", + "name": "PermanentDTCs", + "description": "Permanent DTCs that survive cleared codes" + }, + { + "did": "0xFD05", + "name": "ReadinessStatus", + "description": "Monitor readiness bitmap" + }, + { + "did": "0xFD06", + "name": "LiveData", + "description": "WWH-OBD live data" + }, + { + "did": "0xFD07", + "name": "FreezeFrame", + "description": "Freeze frame for the DTC that triggered MIL" + }, + { + "did": "0xFD08", + "name": "VehicleMfrSoftwareName", + "description": "Manufacturer software identifier string" + }, + { + "did": "0xFD09", + "name": "VehicleMfrHardwareNumber", + "description": "Manufacturer hardware identifier string" + }, + { + "did": "0xFD0A", + "name": "DistanceWithMILOn", + "description": "km with MIL active" + }, + { + "did": "0xFD0B", + "name": "DistanceSinceDTCClear", + "description": "km since DTCs were cleared" + }, + { + "did": "0xFD0C", + "name": "TimeWithMILOn", + "description": "minutes with MIL active" + }, + { + "did": "0xFD0D", + "name": "TimeSinceDTCClear", + "description": "minutes since DTCs were cleared" + }, + { + "did": "0xFD0E", + "name": "NumberOfWarmups", + "description": "Warm-up cycles since DTC clear" + } + ] +} \ No newline at end of file diff --git a/docs/DATA_GAPS.md b/docs/DATA_GAPS.md new file mode 100644 index 00000000..7160a664 --- /dev/null +++ b/docs/DATA_GAPS.md @@ -0,0 +1,175 @@ +# Data Gaps + +Tracks features that ship as **framework + stubs** because the +required reference data is not publicly available. Each entry lists +the precise data needed; once the data is supplied the stub is +replaced and the entry moves to a `### Resolved` section with the +release tag that closed it. + +The honest path: rather than fabricate algorithms or test vectors that +*look* correct but produce wrong output, every data-pending feature +refuses to compute and raises a typed exception. Call sites can detect +this and surface a clear "data not available" message instead of +shipping a wrong code that could brick a head unit, mis-sign a +firmware image, or freeze-frame the wrong CAN message. + +## Open + +### v3.80 / 3.2 — Radio code calculator brands + +Eight new brands ship registered through `OBD.RadioCode.Registry` but +back the calls with `TOBDRadioCodePending`, which raises +`EOBDRadioCodeDataMissing` on `Calculate`. The framework is tested +against pre-existing brands (Becker4 / Becker5) so the slot is real. + +| Brand key | What's needed | Notes | +|---|---|---| +| `pioneer` | Verified serial → code algorithm or lookup table for at least the DEH/AVH/MVH model families. | Commercial DBs cover ~30M units; community-published algorithms are partial and generation-specific. | +| `kenwood` | Verified algorithm or lookup table for KDC/DDX/DNX/KMM model families. | Post-2008 JVC-Kenwood merger means an algorithm covering one may apply to the other. | +| `jvc` | Verified algorithm or lookup table for KD/KW model families. | Same merger note as Kenwood. | +| `sony` | Verified algorithm or lookup table for CDX/WX/MEX after-market head units. | Modern Sony OEM fitments are gateway-tied via VIN and out of scope. | +| `philips` | The licensed serial-to-code database (Philips ships ~14M entries). | EEPROM-extraction route is hardware-side and not implementable in this library. | +| `grundig` | A leaked or published lookup table for WKC/EC pre-2000 head units. | Possibly recoverable from a specific generation via the Becker4/Becker5 approach. | +| `panasonic` | CQ-series algorithm or lookup table; per-region variants common. | Matsushita-era OEM + after-market. | +| `continental_vdo` | Mapping from VDO part number to the underlying VAG variant. | OEM head-unit supplier in VW / Mercedes / Ford; often re-uses VAG variants but the per-PN mapping is undocumented publicly. | + +**How to drop in real data:** + +1. Replace the entry's factory in `OBD.RadioCode.Pending.pas` (or move + it to a new `OBD.RadioCode..Advanced.pas` unit) with a real + `TOBDRadioCode` subclass. +2. Set `DataAvailable := True` when registering with the registry. +3. Add at least one verified `serial → code` fixture in + `tests/Tests.RadioCode..pas`. +4. Move the row out of "Open" into `### Resolved` with a tag. + +### v3.80 / 4.4 — Coding encoders + +The Toyota / Honda / HMG / Stellantis units provide the byte / bit +shape (mirroring `OBD.OEM.Coding.VW`). Schema-aware bit-field +descriptions are loaded from per-OEM JSON catalogs; production-quality +catalogs need verified bit layouts captured from real ECUs. + +| Encoder | What's needed | Notes | +|---|---|---| +| `OBD.OEM.Coding.Toyota` (CUW) | Verified Customize bit map per ECU family (engine, body, BCM, A/C, security). | Some Toyota service-manual notes documented (e.g. wiper sensitivity, key-remote functions) but no consolidated public table. | +| `OBD.OEM.Coding.Honda` (HDS option-byte) | Verified option-byte layout per ECU family. | Some daytime-running-light / auto-lock options publicly known. | +| `OBD.OEM.Coding.HMG` (GDS variant-coding) | Verified variant-coding bit map. | Hyundai / Kia / Genesis share the GDS payload conventions. | +| `OBD.OEM.Coding.Stellantis` (Proxi) | **CRC polynomial** for the Proxi configuration map. | `ComputeChecksum` raises `EOBDStellantisProxi` until the polynomial is supplied. The wiTECH workflow itself is publicly documented (see FCA TSBs and NHTSA bulletin MC-10251789-9999) but the wire-level CRC algorithm is not. | + +**Sources reviewed (Stellantis Proxi):** FCA Proxi Tool documentation +(fcaproxitool.com), I-CAR CRN-1291 "Identifying FCA/Stellantis +Programming Differences", NHTSA TSB MC-10251789-9999. + +### v3.80 / 5.3 — SecOC CMAC-AES-128 (profiles 1 & 2) + +`OBD.Protocol.SecOC` ships profile 3 (HMAC-SHA-256) end-to-end using +`System.Hash.THashSHA2.GetHMAC`. Profiles 1 and 2 (CMAC-AES-128) are +the most common AUTOSAR baseline but require CMAC, which Delphi RTL +does not expose. `SecOCComputeAuthenticator` for profile 1 / 2 raises +`EOBDSecOCAlgorithmNotAvailable` until OpenSSL 3.x EVP_MAC is bound at +the same place existing OpenSSL bindings live +(`OBD.ECU.Signature.OpenSSL`). The freshness-value handling, MAC +truncation, PDU envelope, and constant-time verify path are all +shared, so the binding is a single ~30-line addition. + +What's needed: `EVP_MAC_fetch("CMAC")` + `EVP_MAC_init` + `EVP_MAC_update` ++ `EVP_MAC_final` against the AES-128-CBC cipher. Public AUTOSAR test +vectors will validate the binding the moment it lands. + +### v3.80 / 4.5 — Post-quantum signature OpenSSL binding + +`OBD.ECU.Signature.PQC` ships the envelope codec +(algorithm tag + key-id + signature length + signature) and the +verifier scaffolding plumbed into the existing +`IFirmwareSignatureVerifier` interface. The envelope codec is fully +tested. `Verify` raises `EOBDPQCNotAvailable` until OpenSSL 3.x EVP +is bound, because: + +1. No OEM has shipped a signed-PQC ECU as of 2026-05-09, so there's + no production wire format to validate against — fail-closed is + correct. +2. NIST FIPS 204 (ML-DSA) and FIPS 205 (SLH-DSA) finalised in 2024 + are the algorithm baselines. Once an OEM publishes a wire spec, + the OpenSSL EVP binding (using `EVP_PKEY_verify` with the right + OID) is a straightforward ~30-line addition. + +What's needed to close the gap: +- An OEM-published wire spec (envelope layout, key derivation, OID). +- OpenSSL 3.x linkage on every supported platform (Windows / macOS / + Linux / iOS / Android). For Windows we'd reuse the existing + `OBD.ECU.Signature.OpenSSL` library-load path. + +### v3.82 / B8 — Toyota / Lexus smart-key — certificate-locked platforms + +`OBD.OEM.KeyAdaptation.Toyota` ships the request/response framing +including the master-key vs PIN authentication mode toggle. +`FindToyotaPlatform` flags certificate-locked Techstream platforms +(post-MY2021 generally) as `tpaCertificateRequired`. Apps without a +licensed Techstream certificate get a clear "out of scope" signal +rather than a wire-level failure. + +### v3.82 / B7 — Ford PATS — gateway-locked platforms + +`OBD.OEM.KeyAdaptation.Ford` ships the framing + an applicability +table. Mustang Mach-E (CD542), F-150 Lightning (P708), and other +post-2018 FDRS-only platforms are flagged `fpaGatewayLocked` because +the gateway requires a licensed Ford IDS or FDRS connection. Open +platforms (P552, CD391, C520) work over plain OBD via the +documented PATS reset procedure. + +### v3.82 / B6 — HMG smart-key — dealer PIN derivation + +`OBD.OEM.KeyAdaptation.HMG` ships the request/response framing fully. +The 4–6 digit PIN that authorises the procedure is derived from the +dealer portal (Hyundai SST / Kia KDS Online). Open-procedure +platforms (Hyundai i20 RB, Elantra LD, Sonata JF, Kia Stonic QS) are +flagged `hpaOpenWithPIN`; E-GMP and Genesis platforms are +`hpaGatewayLockedPostMY2020` / `hpaCertificateRequired` to signal the +caller needs a licensed dealer tool instead. + +### v3.82 / B5 — Mercedes SCN central-server solver + +`OBD.OEM.SCN.Mercedes` ships the publicly documented version-fetch + +coding request/response envelopes. The actual SCN computation runs +on Daimler's central server; `IMBSCNSolver` decouples it. The +default `TMBSCNSolverNotAvailable` raises `EOBDMBSCNNoSolver` so any +caller without a wired solver fails closed. + +A real solver implementation either calls a dealer-portal client +(XENTRY / Vediamo) or replays captured (request, response) pairs +from a real bench session. + +### v3.82 / B4 — VAG Component Protection (SVM) solver + +`OBD.OEM.ComponentProtection.VAG` ships the publicly documented +challenge / activation envelopes (ECUType + ComponentSerial + VIN + +Nonce → Response + Signature). The host-portal `IVAGCPSolver` plug-in +that turns a challenge into an activation is dealer-portal proprietary +(SVM = Service Verification Manager). The default +`TVAGCPSolverNotAvailable` raises `EOBDVAGCPNoSolver` so any code that +calls `Solve` without wiring fails closed. + +A real solver would either (a) call the dealer-portal client the host +already has, or (b) replay captured (challenge, response) pairs. + +### v3.82 / B3 — BMW immobiliser ISN + EWS/CAS challenge-response + +`OBD.OEM.KeyAdaptation.BMW` ships the publicly documented data +structures (16-byte EWS slot, 16-byte CAS slot, 32-byte FEM-BDC slot) +and round-trip-correct encoders/decoders. The `IBMWKeyChallengeSolver` +interface decouples the proprietary parts: + +- **Per-ECU ISN derivation** for EWS, CAS, FEM-BDC modules. Documented + in NCSExpert / Carly only at a procedural level; the actual + cryptographic derivation is dealer-portal-proprietary. +- **EWS / CAS challenge-response encryption** (the AES-flavour key + exchange used to authorise key writes). + +Solver implementations would either (a) call out to a dealer-portal +client the host already has, or (b) consume captured (challenge, +response) pairs from a real bench session. + +## Resolved + +*(empty; populated as gaps close)* diff --git a/docs/EXTENSION_PLAN_v3.80.md b/docs/EXTENSION_PLAN_v3.80.md new file mode 100644 index 00000000..b5d1c5fe --- /dev/null +++ b/docs/EXTENSION_PLAN_v3.80.md @@ -0,0 +1,389 @@ +# Extension Plan — v3.80 + +**Status:** Active. Items ship in the order below, each as a separate +commit, all on branch `claude/review-docs-update-NvPaR`. When every +checkbox is ticked, this milestone tags as v3.80.0. + +**Scope ordering chosen by maintainer:** +3.2 → 3.3 → 4.1 → 4.2 → 4.3 → 4.4 → 4.5 → 4.6 → 5.1 → 5.2 → 5.3 → 5.4 → 5.5 → 8.2 → 8.3. + +**Data policy:** Real-world reference data is sourced from public spec +documents, OEM service-manual citations, and reputable community +reverse-engineering archives (Ross-Tech wiki, FORScan forum, OBDeleven +community DB, AUTOSAR public spec, ISO/SAE published wire formats). +When a public source genuinely doesn't exist for an item, that +implementation is shipped behind a `NOT FOR PRODUCTION` guard with a +clear `TODO(data)` marker and an entry in +[`docs/DATA_GAPS.md`](DATA_GAPS.md) so a maintainer with access to a +reference vehicle can drop the verified data in. + +Effort key: **S** ≤1 day · **M** 2–5 days · **L** 1–2 weeks · **XL** >2 weeks. +Priority key: 🔴 must-have for the milestone · 🟠 should-have · 🟢 nice-to-have. + +--- + +## 3.2 — Brand expansion for radio calculators 🟠 L + +Add eight new calculator brands using the existing `TOBDRadioCode` / +`IOBDRadioCode` pattern, each with at least one verified test fixture +from a public service-manual or community-archive source. + +| Brand | Likely source | Notes | +|---|---|---| +| Pioneer | DEH-series + AVH-series serial-derived algorithms (community-archive) | Multi-variant per generation | +| Kenwood | KDC / DDX / DNX series serial → code documented in Kenwood field-service notes | Multi-variant | +| JVC | KD-series + KW-series (Kenwood-JVC merged supply chain after 2008) | Some overlap with Kenwood | +| Sony | CDX / WX / MEX after-market head units | Older units only — modern Sony tied to VIN | +| Philips | RC-series + 22DC (1990s European OEM) | Very common Renault / PSA fitments | +| Grundig | WKC / EC-series (older European OEM) | Mostly pre-2000 | +| Panasonic (Matsushita) | CQ-series (Japanese OEM + after-market) | Per-region | +| Continental / VDO | OEM head units in VW / Mercedes / Ford as supplier | Often re-uses VAG variants | + +**Per-brand checklist:** + +- `src/RadioCode/OBD.RadioCode..Advanced.pas` — inherits + `TOBDRadioCode`, overrides `Validate` + `Calculate`. +- Registers in `Packages/RunTime.dpk` and `RunTime.dproj`. +- `tests/Tests.RadioCode..pas` — at minimum one `serial → code` + pair from a public reference, plus negative-input cases. +- Update `docs/RADIO_CALCULATORS.md` brand table. + +**Exit criterion:** Every new brand has an `Advanced` unit and a smoke +test that calculates a known good code from a real serial. Brands that +end up data-blocked are listed in `docs/DATA_GAPS.md` with the +specific reference still needed. + +--- + +## 3.3 — Variant rationalisation 🟠 M + +`OBD.RadioCode.Variants` already defines `TRadioCodeRegion` / +`TRadioCodeYearRange` / `TRadioCodeSecurityVersion` but the +calculators don't drive the right algorithm from VIN/year metadata. +Migrate the three biggest VIN-aware brands first; the rest follow the +same pattern. + +- **VW / Audi / SEAT / Skoda (VAG group)** — Concert/Symphony/RNS-E + versions; pre-2007 vs post-2007 algorithm split documented in the + Ross-Tech wiki. +- **Mercedes-Benz** — Becker BE / BE-2 / BE-Audio / Audio 50 APS + variants; selection by serial-number prefix + model year. +- **BMW** — Business / Professional / DSP head units across E-series + and F-series, selection by FA + I-Stufe (already decoded by the + `TOBDBMWFA` / `TOBDBMWIStufe` units). + +**Deliverables:** + +- `IOBDRadioCodeVariantResolver` interface — given VIN + year + serial, + return the correct `IOBDRadioCode` instance. +- `TOBDRadioCodeRegistry` — variant-aware lookup with fallback to the + `Advanced` calculator when no metadata is supplied (preserves + existing behaviour). +- Tests covering at least one boundary (e.g. VW Concert pre-2007 vs + post-2007 algorithm produces different codes from the same serial). + +**Exit criterion:** A caller with a VIN can ask the registry for "the +right calculator for this car" without knowing about variants. + +--- + +## 4.1 — Coding diff & dry-run 🔴 M + +Read the current coding block, present a structured diff against the +target, require explicit confirm before issuing the WriteByIdentifier. + +**Deliverables:** + +- `TOBDCodingDiff` — record of `(Field, Before, After, Description)` + tuples, with helpers to render as text/JSON. +- `TOBDCodingPlan` — wraps a target `TOBDCodingBlock` + a snapshot of + current values; exposes `Diff: TArray`, + `IsNoOp: Boolean`, `Apply(Confirmed: Boolean)`. +- `tests/Tests.OEM.Coding.Diff.pas` — VW long-coding before/after + fixture with a bit-level diff. + +**Exit criterion:** GUI flow can read → display diff → require Yes → +write, with the same call surface across VW / BMW / Mercedes / Ford +coding encoders. + +--- + +## 4.2 — Coding rollback log 🔴 M + +Every successful coding write appends a tamper-evident audit record so +a workshop can reverse-engineer a bricked coding session. + +**Deliverables:** + +- `TOBDCodingAuditRecord` — `(Timestamp, VIN, ECU, Block, BeforeBlob, + AfterBlob, Operator, Reason, Signature)`. Signature is HMAC-SHA256 + over the canonicalised record using a key from `TOBDSecureSettings`. +- `TOBDCodingAuditLog` — append-only file (one record per line, JSON); + `Verify(Path)` walks the chain and reports the first tamper + position. +- Wire it into `TOBDCodingPlan.Apply` so success → record, no manual + step. +- Tests covering tamper detection (flip a byte, expect Verify to flag + it). + +**Exit criterion:** A workshop can `obdctl coding-log verify +~/.obd/coding.log` and get a deterministic answer. + +--- + +## 4.3 — Resumable flashing 🟠 M + +`TOBDECUFlashing` does rollback on failure today; add resume so a power +loss or disconnect mid-flash isn't catastrophic. + +**Deliverables:** + +- Persist `TOBDFlashCheckpoint` (snapshot path + last-completed block + index + signature of the source firmware) to a sidecar file every + N blocks. +- `TOBDECUFlashing.Resume(SnapshotPath, FirmwarePath)` — verifies the + checkpoint matches the firmware signature, re-opens the snapshot, + and continues from the block after `LastCompletedBlock`. +- Tests covering: clean resume, tampered checkpoint (signature + mismatch), missing snapshot, mid-finalise resume. + +**Exit criterion:** Kill the flasher mid-write, restart, call +`Resume`, verify the ECU lands in the same final state as a +non-interrupted run. + +--- + +## 4.4 — More coding encoders 🟠 L + +Catalog-driven additions on top of the existing `OBD.OEM.Coding` +framework. Each encoder targets a specific OEM tool's wire format. + +| Encoder | Reference | Public source | +|---|---|---| +| Toyota CUW (Customize Utility) | Techstream service manual + community CUW.dll documentation | Public | +| Honda HDS option-byte coding | HDS service notes + community archives | Public | +| Hyundai/Kia GDS variant coding | GDS-Mobile service procedures | Public-partial | +| Stellantis wiTECH proxi alignment | wiTECH 2.0 service procedures + Mopar TSBs | Public-partial | + +**Per-encoder checklist:** + +- `src/Services/OBD.OEM.Coding..pas` — bit-field schema using the + existing `TOBDCodingBlock` infrastructure. +- Wire into the per-OEM extension's `BuildExtendedCatalog`. +- At least one round-trip test (encode → decode → encode produces + byte-identical output) using a publicly cited known coding string. +- Document any data gaps in `docs/DATA_GAPS.md`. + +**Exit criterion:** Calling `Ext.WriteCodingBlock(...)` against any of +the four OEMs uses the dedicated encoder and the round-trip test +passes. + +--- + +## 4.5 — PQC-ready signature verifier 🟢 M + +Placeholder is appropriate here — no OEM has shipped a signed-PQC ECU +yet. Build the framework so when test vectors arrive, they slot in. + +**Deliverables:** + +- `OBD.ECU.Signature.PQC.pas` — `TOBDPQCSignatureVerifier` + delegating to OpenSSL 3.x EVP for ML-DSA-65 (Dilithium-3 final) and + SLH-DSA-SHA2-128s (SPHINCS+). +- Algorithm identifiers from the NIST FIPS 204 / 205 final standards. +- Self-test against the NIST KAT (Known Answer Test) vectors that + are publicly available, fixture-driven. +- Marked `experimental` in the unit header until an OEM publishes a + spec'd PQC ECU. + +**Exit criterion:** NIST KAT vectors verify correctly through the +verifier; the rest of the flashing pipeline can swap to it via the +existing `IFirmwareSignatureVerifier` interface. + +--- + +## 4.6 — Programming-voltage / battery-saver gate 🔴 S + +Pre-flash check refuses to proceed if pack voltage is below the OEM +minimum. + +**Deliverables:** + +- `TOBDECUFlashing.MinimumProgrammingVoltage: Single` (default 12.5 V + per ISO 22900-2 informative annex; configurable per OEM). +- Pre-check stage reads `TOBDAdapter.GetVoltage` and aborts with a + typed `EOBDProgrammingVoltageTooLow` exception listing measured vs + required. +- `OBD.OEM.Voltage.pas` — per-OEM override map (e.g. some EVs need + high-voltage system in a specific state during flash). +- Tests covering pass / fail / adapter-doesn't-support-voltage. + +**Exit criterion:** Flashing with a 10 V battery refuses cleanly +instead of bricking the ECU. + +--- + +## 5.1 — DoIP UDP discovery + AliveCheck 🟠 M + +Today `OBD.Protocol.DoIP.Session.{Cross,TLS}` cover only the TCP +diagnostic-message path. Add the UDP-side discovery + AliveCheck +broadcast. + +**Deliverables:** + +- `OBD.Protocol.DoIP.Discovery.pas` — `TDoIPDiscovery`: + - `BroadcastVehicleIdentRequest` / `BroadcastVehicleIdentRequestEID` + / `BroadcastVehicleIdentRequestVIN` (ISO 13400-2 §5.4). + - Listens for `Vehicle Announcement` / `Vehicle Identification + Response` packets on UDP/13400. + - Returns `TArray` with logical address, VIN, GID, EID. +- `TDoIPAliveCheck` — periodic AliveCheck request → response timing. +- Self-loop test: `TFakeGateway` (UDP variant) responds to broadcast, + client decodes the response. + +**Exit criterion:** Plug an Ethernet-DoIP gateway into the bench +network, run a discovery scan, get a populated list of vehicles back. +On CI, the self-loop test passes. + +--- + +## 5.2 — CAN-FD adapter capability 🟠 M + +Capabilities flag + per-adapter feature gate so apps can detect +CAN-FD support and degrade gracefully. + +**Deliverables:** + +- `TOBDAdapterCapability = (acCAN, acCANFD, acISOTP, acDoIP, …)` set + on the base adapter. +- `OBD.Adapter.OBDLink` — sets `acCANFD` for STN2100 / STN2255 / EX. +- `OBD.Adapter.ELM327` — sets only `acCAN` (no FD). +- `OBD.Protocol.ISOTP` — picks 64-byte / 12-bit frame format when + `acCANFD` is available. +- Tests covering capability flag round-trip + ISO-TP frame-length + selection. + +**Exit criterion:** Connecting an OBDLink EX detects CAN-FD; ISO-TP +sends 64-byte frames; capabilities-aware code paths are exercised in +tests. + +--- + +## 5.3 — SecOC freshness-value handling 🟢 L + +Increasingly common on 2024+ premium models. AUTOSAR SecOC spec is +fully public; per-OEM freshness counters are partially documented. + +**Deliverables:** + +- `OBD.Protocol.SecOC.pas` — `TSecOCContext` with: + - Freshness-value generation (truncated value, increment policy). + - MAC computation (CMAC-AES-128 default; spec-allowed + HMAC-SHA-256). + - Authentication-vector verification on inbound messages. +- Per-OEM freshness-counter strategies (VW, BMW, Mercedes, GM if + publicly documented; placeholder + DATA_GAPS.md otherwise). +- AUTOSAR-spec round-trip tests using the spec's reference vectors. + +**Exit criterion:** SecOC-protected UDS exchange round-trips +correctly against an in-process simulator. + +--- + +## 5.4 — ISO-TP timing audit 🟠 M + +Capture STmin/BS handling against a CAN-bus simulator, fix any drift. + +**Deliverables:** + +- `tests/Tests.Protocol.IsoTp.Timing.pas` — drives the encoder against + a known-good simulator (`CANalyzer` capture or open-source + equivalent) and asserts STmin compliance to ±1 ms. +- Fix any timing drift discovered; document threshold in + `docs/PROTOCOLS.md`. + +**Exit criterion:** Simulator log shows ISO-TP transmits at the +declared STmin; no drift > spec tolerance. + +--- + +## 5.5 — J2534-2 (2018 expansion) 🟢 L + +`OBD.Adapter.PassThrough` targets J2534-1; J2534-2 adds ISO 15765 +timing parameters and mixed-mode. Spec is published by SAE; some +sections are paywalled but the API surface is in the public J2534-2 +header definitions distributed by major tool vendors. + +**Deliverables:** + +- Extend the `TOBDAdapterPassThrough` IOCTL surface to cover + `SET_CONFIG` extended parameters introduced in J2534-2. +- Mixed-mode (CAN + CAN-FD on the same channel) selection. +- Per-vendor compatibility notes in `docs/PROTOCOLS.md`. + +**Exit criterion:** Driver loads against a J2534-2 vendor DLL on a +bench setup. Tests cover the new IOCTL surface against a mock DLL. + +--- + +## 8.2 — EV helpers 🟠 L + +Battery SoH, cell-imbalance detector, charging-session decoder. The +catalog data is already shipped (per-cell voltages, temperatures, pack +SoC/SoH); what's missing is the high-level API. + +**Deliverables:** + +- `OBD.EV.BatteryHealth.pas`: + - `TOBDBatterySoH` — derives state-of-health from per-cell voltages, + pack capacity DIDs, and historical fast-charge counters. + - `TOBDCellImbalance` — computes spread / std-dev / outlier + detection across the per-cell voltage array. + - `TOBDChargingSession` — decodes charging-session telemetry + (start SoC, end SoC, energy delivered, peak power, average + temperature). +- Per-OEM resolvers that map the high-level API onto the existing + per-cell DIDs (VW MEB, Tesla, BMW i-series, HMG E-GMP at minimum). +- Tests using the v3.79 capture-replay infra against synthetic + charge-session captures. + +**Exit criterion:** `TOBDBatteryHealth.Capture(VIN)` returns a +populated SoH / imbalance / session record across at least four +EV platforms. + +--- + +## 8.3 — Tachograph DDD signature verification 🟢 M + +Extend `examples/tachograph` with EU smartcard cert chain validation. + +**Deliverables:** + +- `OBD.Tachograph.Signature.pas` — verifies DDD file signatures + against the EU root CA cert chain (ERCA → MSCA → card cert). +- Reuses `OBD.ECU.Signature.OpenSSL` for the underlying crypto + primitives (RSA-PSS / ECDSA). +- Bundle the public EU root CA cert as a fixture (it's published by + the EU Commission). +- Tests covering: valid DDD, tampered DDD, expired card cert, wrong + chain. + +**Exit criterion:** A real DDD download (or a publicly available test +fixture) verifies; tampering one byte fails verification. + +--- + +## Out-of-band housekeeping + +- Update [`ROADMAP.md`](ROADMAP.md) status table at each item completion. +- Append a v3.80 entry to [`CHANGELOG/v3.md`](../CHANGELOG/v3.md) per item, + not as one mega-commit. +- Track unresolved data gaps in [`DATA_GAPS.md`](DATA_GAPS.md) (created + when first needed). + +## Definition of done for v3.80 + +- All 15 items above are either ✅ shipped, or 🟡 partially shipped with + a clearly tracked entry in `DATA_GAPS.md`. +- CI passes. +- ROADMAP table reflects v3.80 as a tagged milestone. diff --git a/docs/EXTENSION_PLAN_v3.81.md b/docs/EXTENSION_PLAN_v3.81.md new file mode 100644 index 00000000..c8b8dcc6 --- /dev/null +++ b/docs/EXTENSION_PLAN_v3.81.md @@ -0,0 +1,169 @@ +# Extension Plan — v3.81 + +**Status:** Active. Items ship in the order below, each as a separate +commit on branch `claude/review-docs-update-NvPaR`. When every checkbox +is ticked, this milestone tags as v3.81.0. + +**Scope chosen by maintainer:** A1 → A2 → A3 → A4 → A5 → A6. + +**Theme:** Standards-public coverage. Every item below is fully +specified in a public document (ISO, SAE, UN ECE, EU regulation, or +publicly distributed OEM service info). No DATA_GAPS entries expected +for this milestone. + +Effort key: **S** ≤1 day · **M** 2–5 days · **L** 1–2 weeks · **XL** >2 weeks. +Priority key: 🔴 must-have · 🟠 should-have · 🟢 nice-to-have. + +--- + +## A1 — Service Routines Library 🔴 L + +A `TOBDServiceRoutine` record + a registry of 30–50 publicly +documented workshop procedures. Each routine carries: + +- Stable key + display name +- Applicable OEMs +- UDS 0x31 RoutineControl identifier (RID) +- Pre-conditions (engine state, gear, ignition, voltage) +- OptionRecord layout (encoded payload) when applicable +- Post-conditions (what to verify after) +- Safety warnings (when destructive or vehicle-moving) +- Citation (TSB, service manual, or community archive URL) + +**Coverage targets:** + +| Group | Routines | Source | +|---|---|---| +| Maintenance | Oil reset, service-interval reset, brake-pad change service, AdBlue reset | OEM service info / TSBs | +| Steering & brakes | SAS zero, EPB service mode, ABS bleed | ISO 26262 + TSBs | +| Powertrain | DPF forced regen, throttle body adapt, idle relearn | OEM service info | +| Comfort | Window/sunroof teach-in, seat-memory reset | TSBs | +| Battery / electrical | BMW IBS battery registration, Mercedes IBS, Audi 12V battery write | Public community archives | +| TPMS | Sensor relearn, ID write per wheel | ISO 21750 + TSBs | + +**Deliverables:** + +- `src/Services/OBD.OEM.ServiceRoutines.pas` — record type + registry + + RoutineControl frame builder. +- `Tests.OEM.ServiceRoutines` — round-trip a representative subset. +- `docs/SERVICE_ROUTINES.md` — citation table + per-routine notes. + +**Exit criterion:** At least 30 routines registered, each with a +citation; the 0x31 frame builder produces spec-correct bytes for a +selected fixture set. + +--- + +## A2 — Tachograph Workshop Operations 🔴 M + +Extends the v3.80 / 8.3 DDD signature work with the workshop-card +operations spec'd in EU 2016/799 Annex 1C Appendix 7. + +**Deliverables:** + +- `src/Services/OBD.Tachograph.Workshop.pas`: + - `TTachoUTCSync` — request body + signature requirements + - `TTachoKLWFactors` — encode/decode the speed-source coefficients + (k, l, w factors per Annex 1B definitions) + - `TTachoTyreSize` — tyre-size update record + - `TTachoVINUpdate` — VIN write through the workshop-card auth path + - `TTachoSpeedSource` — pulses-per-revolution + - `TTachoSealedActivation` — sealed-state trigger with mandatory + timestamp + workshop-card ID +- Tests using the existing controllable verifier so the chain walks + without an OpenSSL binding. + +**Exit criterion:** Each workshop op encodes to bytes that decode +back to the same record; signature requirements documented per op. + +--- + +## A3 — OBD-II Mode 06 (On-Board Monitoring) 🔴 M + +ISO 15031-5:2015 specifies the complete Mode 06 (Service $06) wire +format with standardised Test ID / Component ID / Unit-and-Scaling ID +tables. Pro scan tools rely on this for diagnosing monitors that pass +but read close to a threshold. + +**Deliverables:** + +- `src/Services/OBD.Service06.Mode06.pas`: + - Request encoder (mode + OBDMID — On-Board Diagnostic Monitor ID) + - Response decoder producing `TArray` with + Test ID, Unit ID, Test Value, Min Limit, Max Limit + - `MODE06_TEST_IDS` table (ISO 15031-5 Table B.2 — ~30 standardised + test IDs) + - `MODE06_COMPONENT_IDS` table (ISO 15031-5 Table B.4) + - `MODE06_UNITS` table with scale + offset per unit ID +- Tests: encode / decode round-trip + a couple of real-world payloads + from public ISO 15031-5 Annex examples. + +**Exit criterion:** Mode 06 round-trip + standardised test-ID lookup +work end-to-end from a captured byte stream. + +--- + +## A4 — WWH-OBD (UN GTR No.5 / ISO 27145) 🟠 M + +The HD / next-gen OBD-II message set. Public spec covers the protocol +extension (DID-based identifiers replacing PIDs), J1939-FMI DTC +formatting, and the expanded readiness-monitor set. + +**Deliverables:** + +- `src/Protocol/OBD.Protocol.WWHOBD.pas`: + - WWH-OBD DID set (DM2, DM5, DM12, DM23, etc. equivalent) + - J1939-FMI DTC packing (SPN + FMI + occurrence count + conversion + method) per ISO 15031-5 §7 + - WWH-OBD readiness monitor mapping +- Tests covering DTC encode/decode + DID lookup. + +**Exit criterion:** A WWH-OBD-targeted vehicle's DTC stream parses +correctly through the new unit; readiness status maps to the same +high-level helpers as classic OBD-II. + +--- + +## A5 — J1939-71/73/75 PGN Library 🟠 M + +The full named-PGN catalog from SAE J1939-71 (Application Layer), +J1939-73 (Diagnostics), J1939-75 (Generator Sets). ~250 PGNs covering +powertrain, body, brakes, transmission, gen-sets. + +**Deliverables:** + +- `src/Protocol/OBD.J1939.PGNs.pas`: + - `J1939_PGNS: array of TJ1939PGNDescriptor` with PGN ID, name, + transmission rate, length, default priority, source spec section + - `FindPGN(PGN: UInt32): TJ1939PGNDescriptor` lookup +- Tests covering the lookup table integrity (no duplicate IDs, + every entry has a non-empty name, length matches J1939-71 §5). + +**Exit criterion:** All publicly published J1939-71/73/75 PGNs are in +the table; the lookup is O(log n) sorted by PGN. + +--- + +## A6 — UDS NRC Catalog (ISO 14229-1) 🟢 S + +Negative response code descriptions for UDS 0x10–0x9F. + +**Deliverables:** + +- `src/Services/OBD.UDS.NRC.pas`: + - `NRC_DESCRIPTIONS: array[$10..$9F] of string` with ISO 14229-1 + §A.1 Table A.1 entries + - `DescribeNRC(Byte): string` helper +- Tests covering a sampled subset against the spec text. + +**Exit criterion:** Every 0x10–0x9F NRC has a one-line description; +the helper is the canonical formatter across the codebase. + +--- + +## Out-of-band housekeeping + +- Append a v3.81 entry to `CHANGELOG/v3.md` per item (not a mega-commit). +- Update `docs/index.md` with the new docs / subsystem references. +- No DATA_GAPS entries expected — ping me if any item turns out to + need them and I'll narrow scope. diff --git a/docs/EXTENSION_PLAN_v3.82.md b/docs/EXTENSION_PLAN_v3.82.md new file mode 100644 index 00000000..ef7b33c5 --- /dev/null +++ b/docs/EXTENSION_PLAN_v3.82.md @@ -0,0 +1,206 @@ +# Extension Plan — v3.82 + +**Status:** Active. Items ship in the order below, each as a separate +commit on branch `claude/review-docs-update-NvPaR`. Tags as v3.82.0 +when complete. + +**Scope chosen by maintainer:** B1 → B2 → B3 → B4 → B5 → B6 → B7 → B8. + +**Theme:** Two final standards-public additions (B1, B2) followed by +the **B-tier OEM specifics** flagged earlier. The OEM items split +cleanly: the **wire-protocol framing** for each procedure is +documented in service-info / community archives, the **algorithm or +challenge-response key** is dealer-portal-proprietary. Each OEM unit +ships the framing fully and tracks the proprietary bits in +`docs/DATA_GAPS.md`. + +Effort key: **S** ≤1 day · **M** 2–5 days · **L** 1–2 weeks · **XL** >2 weeks. +Priority key: 🔴 must-have · 🟠 should-have · 🟢 nice-to-have. + +--- + +## B1 — ISO 27145-3 Readiness Monitor Decoder 🔴 M + +The WWH-OBD readiness DID (FD05, shipped in v3.81 / A4) carries a +bit-packed monitor-status payload. ISO 27145-3 §6.4 specifies the +exact bit layout (continuous-monitor group + non-continuous-monitor +group, supported / completed bits per monitor). + +**Deliverables:** + +- `src/Protocol/OBD.Protocol.WWHOBD.Readiness.pas`: + - `TWWHOBDReadinessSet` — record exposing per-monitor (Supported, + Complete) booleans for the 18 ISO-spec'd monitors (catalyst, heated + catalyst, evap, secondary air, A/C refrigerant, O2 sensor, O2 heater, + EGR/VVT, NMHC catalyst, NOx aftertreatment, boost pressure, exhaust + gas sensor, PM filter, EGR system, plus the four continuous monitors). + - `DecodeWWHOBDReadiness(Bytes)` and the inverse encoder. +- Tests covering at least one published-spec example payload + bit-flip + detection. + +**Exit criterion:** Round-trip a real-world readiness response, +correctly identify which monitors are pending. + +--- + +## B2 — CalID / CVN Sweep + Drive-Cycle Advisor 🟠 M + +Service 09 PIDs $04 (Calibration ID, ASCII) and $06 (CVN, 4-byte +hex). Useful for fleet inventory (which firmware is loaded?) and +emissions verification (does CVN match the expected approval value?). + +**Deliverables:** + +- `src/Services/OBD.Service09.Calibration.pas`: + - `TOBDCalibrationID` — ASCII CalID per ECU + - `TOBDCalibrationVerification` — 4-byte CVN per CalID + - `EncodeCalIDRequest` / `EncodeCVNRequest` (Service 09 payload) + - `DecodeCalIDResponse` / `DecodeCVNResponse` + - `TCalibrationSweep` orchestrator that walks every responding ECU, + pairs CalID with CVN, returns an array of `(ECU, CalID, CVN)`. +- `src/Services/OBD.DriveCycle.Advisor.pas`: + - Given the readiness state from B1 + an optional OEM key, return a + per-monitor advisor record with the spec-public OBD-II generic + drive-cycle steps still required (cold start, warm-up to closed-loop, + cruise X km/h Y minutes, idle, deceleration). Per-OEM nuances + documented inline; falls back to ISO 15031-7 generic cycle when no + OEM-specific info is registered. +- Tests covering ASCII round-trip, CVN endianness, sweep with multiple + ECUs, advisor emits the right next step for partial readiness. + +**Exit criterion:** A single call returns the firmware inventory of +every ECU; a partial-readiness state produces an actionable next-step +list a tech can follow. + +--- + +## B3 — BMW Key Adaptation 🟠 L + +EWS (E-series), CAS (E-series later), FEM-BDC (F/G-series) key data +structures. Public archives: BimmerCode, Carly forums, NCSExpert +documentation. Frame the request/response; Individual Serial Number +(ISN) calculation per-ECU stays a DATA_GAPS item. + +**Deliverables:** + +- `src/Services/OBD.OEM.KeyAdaptation.BMW.pas`: + - `TBMWKeyDataE` (EWS), `TBMWKeyDataCas` (CAS), `TBMWKeyDataFem` (FEM-BDC) + - `EncodeKeyDataE / Cas / Fem` and decoders + - `TBMWKeyMemorySlot` — slot index 0..9 (E/F-series) or 0..7 (G-series) +- Tests cover slot bounds + the publicly documented byte layouts. + +**Exit criterion:** Encode a key-data record for each of the three +generations into bytes that decode back to the same record. + +--- + +## B4 — VAG Component Protection 🟠 M + +Component Protection (CP) is the VAG dealer-activation flow for +radios, clusters, and AC/HVAC modules. The **request/response framing +through SVM (Service Verification Manager)** is documented in +Ross-Tech's wiki and ODIS public docs. The **challenge-response +algorithm** is dealer-portal-proprietary. + +**Deliverables:** + +- `src/Services/OBD.OEM.ComponentProtection.VAG.pas`: + - `TVAGCPRequest` — challenge envelope (component s/n, ECU type, VIN) + - `TVAGCPResponse` — activation envelope (response payload + signature) + - `TVAGCPSolver` interface — host plugs in their dealer-portal client; + a `TVAGCPSolverNotAvailable` default raises `EOBDVAGCPNoSolver` so + code that calls it without wiring fails closed. +- DATA_GAPS entry for the SVM solver. + +**Exit criterion:** The full request/response round-trip encodes; +solver is pluggable. + +--- + +## B5 — Mercedes SCN Coding Flow 🟠 M + +SCN (Software Calibration Number) is Mercedes' coding flow handled by +XENTRY/Vediamo. The wire framing (request, version, response, SCN +write-back) is public; the actual SCN computation is central-server +proprietary. + +**Deliverables:** + +- `src/Services/OBD.OEM.SCN.Mercedes.pas`: + - `TMBSCNVersionRequest` — fetch current SCN version per ECU + - `TMBSCNCodingRequest` — request SCN coding for a target + (variant + accessory list) + - `TMBSCNResponse` — server response decoder + - `TMBSCNApplyToECU` — write the returned SCN back to the ECU + - Solver interface mirroring the VAG CP one. +- DATA_GAPS entry for the central-server lookup. + +**Exit criterion:** Wire framing round-trips; solver is pluggable. + +--- + +## B6 — Hyundai / Kia / Genesis Smart-Key Registration 🟠 M + +GDS / KDS smart-key procedure: PIN-required, documented per platform. +The frame is public; the PIN comes from the dealer portal. + +**Deliverables:** + +- `src/Services/OBD.OEM.KeyAdaptation.HMG.pas`: + - `THMGKeyRegisterRequest` — request envelope (VIN, PIN, key index) + - `THMGKeyRegisterResponse` — confirmation + - Per-platform applicability table (which platforms accept the + procedure without PIN; which require PIN; which are gateway-locked). +- DATA_GAPS entry for the dealer-PIN derivation. + +**Exit criterion:** Encode/decode the framing; the applicability +table accurately reflects which platforms are open vs gateway-locked. + +--- + +## B7 — Ford PATS 🟠 M + +Passive Anti-Theft System initialise + add-key. Many Ford platforms +(pre-2018) are documented; 2018+ MyKey-integrated platforms are +gateway-locked. + +**Deliverables:** + +- `src/Services/OBD.OEM.KeyAdaptation.Ford.pas`: + - `TFordPATSInitialise` — clear all keys (programmer present) + - `TFordPATSAddKey` — add a new key to the next free slot + - `TFordPATSStatus` — current key count + lockout state + - Per-platform applicability table. +- DATA_GAPS entry for the gateway-locked platforms. + +**Exit criterion:** Open-platform procedures encode/decode correctly; +lockout platforms are clearly flagged as such in the applicability +table. + +--- + +## B8 — Toyota / Lexus Key Registration 🟠 M + +Smart-key learning sequence — some platforms publicly documented +(timing dance via OBD with master key in the slot), others +gateway-locked behind Techstream certificates. + +**Deliverables:** + +- `src/Services/OBD.OEM.KeyAdaptation.Toyota.pas`: + - `TToyotaKeyRegisterRequest` / `Response` framing + - `TToyotaKeyMode` (`tkmAddKey`, `tkmEraseAll`, `tkmReadCount`) + - Per-platform applicability table. +- DATA_GAPS entry for the certificate-locked Techstream platforms. + +**Exit criterion:** Open-platform procedures encode/decode; locked +platforms are explicitly listed in the applicability table. + +--- + +## Out-of-band housekeeping + +- Append a v3.82 entry to `CHANGELOG/v3.md` per item (not a mega-commit). +- DATA_GAPS entries expected for each B-tier item (the proprietary + algorithm / PIN / certificate piece, never the framing). +- Update `docs/index.md` linking the B-tier OEM units. diff --git a/docs/EXTENSION_PLAN_v3.83.md b/docs/EXTENSION_PLAN_v3.83.md new file mode 100644 index 00000000..5f9129fc --- /dev/null +++ b/docs/EXTENSION_PLAN_v3.83.md @@ -0,0 +1,78 @@ +# Extension Plan — v3.83 + +**Status:** Active. Two items, in order: C1 → C2. + +**Theme:** Tie the v3.81/A1 service-routine library and the v3.82/B1+B2 +WWH-OBD readiness + drive-cycle pieces into one-call workshop helpers +backed by spec-public per-OEM drive cycles. + +Effort key: **S** ≤1 day · **M** 2–5 days · **L** 1–2 weeks. + +--- + +## C1 — OEM Session Helpers 🔴 M + +`TOBDDiagSession` already exposes `BeginSession` / `EndSession` / +`StartRoutine` / `StopRoutine` / `RequestRoutineResults`. The +`TOBDServiceRoutine` records from v3.81/A1 carry RID + sub-function + +OptionRecord + required-session + safety class + pre/post conditions. +What's missing is the one-call wrapper that turns a routine record +into a complete "open extended session, run routine, verify, close" +flow with the right error reporting. + +**Deliverables:** + +- `src/Services/OBD.OEM.SessionHelper.pas`: + - `TOBDOEMSessionHelper.RunServiceRoutine(Session, Routine, [Voltage]): + TOBDRoutineExecutionResult` — one-call execution. + - `TOBDRoutineExecutionResult` — `(Success, RoutineKey, ExecutedSteps, + NRC, ErrorMessage, MeasuredVolts, AbortStage)`. + - `TOBDRoutineExecutionStage` enum — `(reseSessionOpen, reseVoltageGate, + reseRoutineStart, reseRoutineWait, reseResultRead, + reseSessionClose)` — names the abort point precisely. + - Voltage gate (v3.80/4.6) optionally consulted when + `Routine.Safety = srsBatteryMin12V5`. + - NRC catalog (v3.81/A6) integration so failures carry the human- + readable description. +- Tests using a mock `TOBDDiagSession` simulator that tracks the call + sequence, covering: success path, voltage-gate fail-fast, session- + open fail, routine-start NRC, result-read NRC, voltage-not-checked- + for-non-battery-routines. + +**Exit criterion:** A workshop UI can call +`Helper.RunServiceRoutine(Session, OilResetBMW)` and get back a typed +result that's ready to render to the operator. + +--- + +## C2 — Per-OEM Drive-Cycle Resolvers 🟠 M + +Populate `RegisterDriveCycleResolver` for VW, BMW, Mercedes, Ford, +and Toyota with their factory-published drive cycles. Each is +documented in OEM service info / SSP / TIS / WIS / Toyota repair +manuals (publicly distributed). + +**Deliverables:** + +- `src/Services/OBD.DriveCycle.Resolvers.pas` registers per-OEM + resolvers at unit init. Each resolver returns more specific + `TDriveCycleStep` records than the ISO 15031-7 generic baseline + for monitors where the OEM publishes a tighter procedure. +- Per-OEM key strings: `'vw'`, `'bmw'`, `'mercedes'`, `'ford'`, + `'toyota'` (consistent with the rest of the codebase's OEM keys). +- Tests covering: per-OEM resolver registers at init, per-OEM step + for Catalyst differs from the generic baseline, per-OEM step for + EVAP differs, fall-through to generic for monitors the OEM + doesn't override. + +**Exit criterion:** Asking the advisor for a VW catalyst drive-cycle +returns the VW SSP procedure verbatim instead of the ISO 15031-7 +generic; same for the other four OEMs. + +--- + +## Out-of-band housekeeping + +- Append a v3.83 entry to `CHANGELOG/v3.md` per item. +- Update `docs/index.md` linking the new units. +- No DATA_GAPS expected. diff --git a/docs/EXTENSION_PLAN_v3.84.md b/docs/EXTENSION_PLAN_v3.84.md new file mode 100644 index 00000000..280ecf61 --- /dev/null +++ b/docs/EXTENSION_PLAN_v3.84.md @@ -0,0 +1,172 @@ +# Extension Plan — v3.84 (Style + Architecture Alignment) + +**Status:** Active. Five sequential passes, each as a separate commit +on `claude/review-docs-update-NvPaR`. Tags as v3.84.0 when complete. + +**Theme:** Bring v3.80–v3.83 code into alignment with the v2-era house +style and the repo's "JSON catalogs + pure-logic Pascal" architecture. +Audit input: full code-style review across 35+ new units against ~17 +v2-era reference units. + +--- + +## S1 — Header Blocks + COPYRIGHT 🔴 M + +**Drift:** v3.80+ unit headers drifted to narrative CONTENTS (10–31 +lines of spec citations / algorithm notes), with COMPATIBILITY and +RELEASE DATE often dropped. COPYRIGHT line is inconsistent across +both old and new code. + +**Action:** Restore the v2 canonical 8-field block on every unit and +test: + +```pascal +//------------------------------------------------------------------------------ +// UNIT : OBD..pas +// CONTENTS : +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows 7, 8/8.1, 10, 11 +// RELEASE DATE : DD/MM/YYYY +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +``` + +**Strict v2 format** — drop the rationale and spec-note prose +entirely. The design notes are already preserved in `docs/DATA_GAPS.md`, +exception messages, and `CHANGELOG/v3.md`. + +**Cross-platform note:** units that genuinely target macOS/Linux/iOS/ +Android (e.g. `OBD.Protocol.DoIP.Session.Cross`, `OBD.RadioCode.*` if +applicable) get a wider COMPATIBILITY string. + +**Backfill old code:** retrofit COPYRIGHT into pre-v2.5 units that +were missed when the line was introduced. + +**Scope:** ~50 files (35+ new src units + ~28 test units + ~10 +backfilled old units). + +--- + +## S2 — Restore Section Banners 🟠 M + +**Drift:** new units omit `//---- SECTION_NAME ----` separators that +old units use to mark INTERFACES / TYPES / CLASSES / CONSTANTS / +specific implementation groups. + +**Action:** add banners using the exact v2 format +(`//----` + 78 dashes total, blank-line-separated, UPPERCASE name). + +**Scope:** ~22 v3.80+ src units. + +--- + +## S3 — XML Doc Realignment 🟠 M + +**Drift:** +- Record types have field-level `/// ` but no type-level + summary. +- Some new functions have one-line summaries; v2 norm is 2–3. +- Test methods carry zero XML. + +**Action:** +- Add type-level summary to every record / class / interface that + doesn't have one. +- Expand under-doc'd public methods to the v2 depth (1–3 lines). +- Add a one-line summary to every public `[Test]` method. + +**Scope:** ~15 src + ~28 test units. + +--- + +## S4 — Constants Placement + Visibility Cleanup 🟢 S + +**Drift:** several new units place `const` blocks at bottom-of- +interface or in implementation; one unit uses `strict private` +where the codebase uses plain `private`. + +**Action:** +- Move all `const` blocks to top-of-unit (after `uses`, before + `type`) with a `//---- CONSTANTS ----` banner. +- Replace `strict private` with `private` in + `OBD.OEM.Coding.Toyota.pas`. +- Visibility ordering audit: `private` → `protected` → `public` → + `published`. + +**Scope:** ~6 src units. + +--- + +## S5 — Move Hardcoded Seed Tables to JSON 🔴 L + +**Architectural drift:** the repo's established pattern (per +`OBD.OEM.VW.pas` and the v3.31 JSON-only refactor) is **JSON +catalogs in `catalogs/` + pure-logic Pascal**. Several v3.81–v3.83 +units violate this by hardcoding seed tables in Pascal `SeedDefault` +procedures or `case` statements. + +**Action:** for each unit listed below, move the data to a JSON +catalog and have the Pascal load it at unit initialisation. The +Pascal layer keeps its logic + lookup helpers; only the table moves. + +| Unit | Target catalog | Entries | +|---|---|---| +| `OBD.OEM.ServiceRoutines` | `catalogs/service-routines.json` | 27 | +| `OBD.J1939.PGNs` | `catalogs/j1939-pgns.json` | 40+ | +| `OBD.UDS.NRC` | `catalogs/uds-nrc.json` | ~50 | +| `OBD.Service06.Mode06` | `catalogs/mode06-units.json` + `mode06-tids.json` + `mode06-obdmids.json` | ~70 | +| `OBD.Protocol.WWHOBD` | `catalogs/wwhobd-dids.json` | 21 | +| `OBD.Adapter.Capabilities` | `catalogs/adapter-capabilities.json` | 5 | +| `OBD.RadioCode.Pending` | `catalogs/radiocode-pending-brands.json` | 8 | +| `OBD.RadioCode.VinResolver` | `catalogs/radiocode-variants.json` | ~30 | +| `OBD.OEM.KeyAdaptation.HMG` | `catalogs/key-platforms-hmg.json` | 6 | +| `OBD.OEM.KeyAdaptation.Ford` | `catalogs/key-platforms-ford.json` | 6 | +| `OBD.OEM.KeyAdaptation.Toyota` | `catalogs/key-platforms-toyota.json` | 5 | +| `OBD.DriveCycle.Advisor` | `catalogs/drive-cycle-generic.json` | 17 | +| `OBD.DriveCycle.Resolvers` | `catalogs/drive-cycle-{vw,bmw,mercedes,ford,toyota}.json` | 5 × ~5 | + +**Pascal layer for each:** +- Schema-defining record types stay (they're API). +- Lookup helpers stay (`FindNRC`, `FindPGN`, `FindHMGPlatform`, etc.). +- A `LoadFrom(JSONPath)` initialiser replaces `SeedDefault`. +- `ResolveCatalogPath` (already in repo for OEM catalogs) reused for path + discovery. + +**JSON schema:** versioned per catalog with a `schema_version` field ++ a top-level `entries` array. + +**Stays in Pascal:** wire codecs (DoIP, SecOC, ISO-TP timing, +WWH-OBD packing), pure logic (coding diff/audit, flashing checkpoint, +voltage gate, EV health math, tachograph signature, CalID/CVN +sweep, session helper). + +### Pre-existing units with hardcoded data (added to S5 scope) + +| Unit | Hardcoded data | Target catalog | Lines | +|---|---|---|---| +| `OBD.RadioCode.Becker4` | 10,000-entry serial→code array | `catalogs/radiocode-becker4.json` | ~1,000 | +| `OBD.RadioCode.Becker5` | 10,000-entry serial→code array | `catalogs/radiocode-becker5.json` | ~1,000 | +| `OBD.VIN.Constants` | WMI region + country + manufacturer tables | `catalogs/vin-regions.json` + `catalogs/vin-countries.json` + `catalogs/vin-wmi-manufacturers.json` | ~900 | +| Per-brand `OBD.RadioCode..Advanced` (32+ units) | Variant manager seed tables (year/region/security version per generation) | `catalogs/radiocode-variants/.json` | varies | + +**Total:** ~3,000 lines of pure data moves out of Pascal sources. + +**Scope (revised):** ~13 new-code JSON catalogs + ~3 pre-existing +catalogs (Becker4, Becker5, VIN constants) + per-brand variant +catalogs + corresponding Pascal refactor + schema validation tests. + +**Exit criterion:** A maintainer can add a new service routine, +correct an NRC description, register a new key-adaptation platform, +or refine a VW drive-cycle step by editing a JSON file in `catalogs/` +without recompiling. + +--- + +## Out-of-band housekeeping + +- Each pass appends to `CHANGELOG/v3.md` under v3.84. +- After S5, the `CONTENTS:` line of each affected unit shrinks + ("brand registry" instead of "brand registry seeded with 8 entries"). +- `docs/index.md` linked to the new `catalogs/*.json` files. diff --git a/docs/FLICKERING_FIX.md b/docs/FLICKERING_FIX.md deleted file mode 100644 index 72f039e2..00000000 --- a/docs/FLICKERING_FIX.md +++ /dev/null @@ -1,126 +0,0 @@ -# Visual Controls Flickering Fix - -## Problem Description - -After optimizing the visual controls by removing the temporary TBitmap buffer and using direct Skia rendering, users experienced flickering where the components would display empty or incomplete frames every second or so. - -## Root Cause - -The flickering was caused by the lack of double buffering when rendering directly through TSkCustomControl: - -1. **Direct Rendering**: Components inherited from `TSkCustomControl` and rendered directly to the screen via the `Draw()` method -2. **Timer-based Updates**: A timer triggered `Invalidate()` calls every frame (30 FPS by default) for animations -3. **No Double Buffering**: Without double buffering, there were brief moments where the screen showed incomplete rendering states -4. **Visible Tearing**: Users could see the "empty" frame between complete renders, especially noticeable during animations - -## Solution - -Implemented double buffering at the Skia level in the base `TOBDCustomControl` class: - -### Key Components Added - -1. **Back Buffer Surface** (`FBackBuffer: ISkSurface`) - - A persistent Skia surface that acts as an off-screen rendering target - - Recreated only when the control's size changes - -2. **Back Buffer Image** (`FBackBufferImage: ISkImage`) - - An immutable snapshot of the rendered content - - Used for atomic display to the screen - -3. **Back Buffer Invalid Flag** (`FBackBufferInvalid: Boolean`) - - Tracks when the back buffer needs to be recreated - - Set to true on resize or initialization - -### Rendering Flow - -``` -1. Timer triggers Invalidate() - ↓ -2. Windows paint message calls Draw() - ↓ -3. Draw() method: - a. Check if back buffer needs recreation (size change) - b. Render to back buffer via PaintSkia() - c. Create immutable snapshot - d. Copy snapshot to screen atomically - ↓ -4. No flickering - all rendering happens off-screen first -``` - -### Code Changes - -Modified `OBD.CustomControl.pas`: - -```delphi -procedure TOBDCustomControl.Draw(const ACanvas: ISkCanvas; const ADest: TRectF; const AOpacity: Single); -var - BufferCanvas: ISkCanvas; -begin - // Recreate back buffer if size changed or first draw - if not Assigned(FBackBuffer) or - (FBackBuffer.Width <> Width) or (FBackBuffer.Height <> Height) then - begin - FBackBuffer := TSkSurface.MakeRaster(Width, Height); - FBackBufferInvalid := False; - end; - - // Always render to the back buffer first (prevents flickering) - if Assigned(FBackBuffer) then - begin - BufferCanvas := FBackBuffer.Canvas; - PaintSkia(BufferCanvas); - FBackBufferImage := FBackBuffer.MakeImageSnapshot; - - // Draw snapshot to screen atomically - if Assigned(FBackBufferImage) then - ACanvas.DrawImage(FBackBufferImage, 0, 0); - end; -end; -``` - -## Benefits - -1. **No Flickering**: Content is rendered off-screen first, then displayed atomically -2. **Efficient**: Back buffer surface is only recreated on size changes -3. **Animation Support**: Works seamlessly with timer-based animations (30 FPS) -4. **All Components Fixed**: All components inheriting from `TOBDCustomControl` benefit automatically: - - `TOBDCircularGauge` - Animated gauge with needle - - `TOBDLed` - LED indicator - - `TOBDMatrixDisplay` - Animated matrix display - - `TOBDTouchHeader` - Header with buttons/tabs - - `TOBDTouchStatusbar` - Status bar - - `TOBDTouchSubheader` - Subheader - -## Performance Impact - -- **Minimal Overhead**: Only one additional surface copy per frame -- **No Extra Allocations**: Back buffer is reused between frames -- **Optimized**: Surface only recreated on size changes -- **Hardware Accelerated**: Skia uses GPU when available - -## Alternative Considered - -Setting the form to `DoubleBuffered := True` was considered but rejected because: -- Causes problems with some other VCL components -- Less efficient than Skia-level double buffering -- Doesn't work well with TSkCustomControl - -## Testing - -To verify the fix: - -1. Run any example application with animated components (e.g., `examples/advanced/AdvancedDashboard`) -2. Observe circular gauges, LEDs, or other animated controls -3. Verify no flickering or "empty frame" glitches occur -4. Test resizing windows to ensure back buffer recreation works correctly - -## Future Improvements - -Possible future optimizations: -- Add dirty region tracking to avoid full redraws -- Implement partial buffer updates for static content -- Add performance metrics/profiling - -## Conclusion - -The double buffering implementation successfully eliminates flickering while maintaining the performance benefits of direct Skia rendering. All visual controls now render smoothly without the need for setting form-level double buffering. diff --git a/docs/OEM_EXTENSION_PLAN.md b/docs/OEM_EXTENSION_PLAN.md index 0fa851b3..695188c7 100644 --- a/docs/OEM_EXTENSION_PLAN.md +++ b/docs/OEM_EXTENSION_PLAN.md @@ -1,5 +1,26 @@ # OEM Extension Build-Out Plan +> **Status (as of v3.79):** The 7-phase plan below has shipped. +> Phases 1.1–1.4 (catalogs / per-ECU sub-catalogs / session negotiation / +> Seed-Key plug-ins) shipped in v3.3–v3.6. Phase 2 (DTC catalogs) shipped +> in v3.7. Phase 3 (coding / variant-write) shipped in v3.8. Phase 4 +> (RoutineControl framework) shipped in v3.9. Phase 5 (capture-replay +> validation) shipped in v3.10. Phase 6.1 (`TOBDDiagSession` wrapper) +> shipped in v3.11. Phase 6.2 (DoIP / ISO 13400-2) shipped in v3.12. +> Phase 7 (golden-vector helper + reference CLI) shipped in v3.13 and +> closed the original plan. +> +> Subsequent releases (v3.14–v3.79) expanded coverage to **79 OEM +> catalogs / 247,279 entries / 5 vehicle classes**, added cross-platform +> DoIP, DoIP TLS, and the async UDS client. See [ROADMAP.md](ROADMAP.md) +> for the per-release breakdown. +> +> This document is kept as the historical design reference for how the +> framework was built. New OEM-coverage work is tracked in +> [ROADMAP.md](ROADMAP.md), not here. + +--- + The v3.0/v3.2 OEM extensions (`OBD.OEM.VW`, `OBD.OEM.BMW`, `OBD.OEM.Mercedes`, `OBD.OEM.Ford`, `OBD.OEM.GM`, `OBD.OEM.Stellantis`) are deliberately **starter catalogs**: ~15 diff --git a/docs/PROPOSALS.md b/docs/PROPOSALS.md index 1dca42ee..f5160f6f 100644 --- a/docs/PROPOSALS.md +++ b/docs/PROPOSALS.md @@ -4,14 +4,31 @@ The original roadmap (v2.1 Foundation → v3.0 FMX & OEM) is complete and tagged. This document is the menu for what comes next. Each proposal is sized to ship as a single milestone with a clear exit criterion — pick one, sequence two, or merge a few items across proposals into a custom -v3.1. +release. Effort key: **S** ≤1 day · **M** 2–5 days · **L** 1–2 weeks · **XL** >2 weeks. Priority key: 🔴 must-have for the proposal · 🟠 should-have · 🟢 nice-to-have. +## Status overview + +| Proposal | Status | Shipped in | +|---|---|---| +| A — FMX Component Completion | ✅ Done | v3.1 | +| B — Mobile Transports | 🟡 Partial — FMX dashboard runs cross-platform; native Bluetooth/USB transports not yet shipped | (open) | +| C — Production-Grade Crypto + Manufacturer Coverage | ✅ Done | Crypto v3.2; OEM coverage v3.3–v3.69 (79 catalogs / 247k entries) | +| D — Heavy-Duty / Commercial | ✅ Done | v3.16 (J1939 base + 6 HD OEMs), expanded through v3.69–v3.76 | +| E — Performance, Testing & Profiling | 🟡 Partial — coverage harness shipped v3.79; broader perf-testing backlog open | +| F — Localization, Theming & Accessibility | 🔴 Open | +| G — DevEx & Community | 🟡 Partial — CI + changelog shipped; GetIt + community templates open | +| H — Async Polish & Persistence | ✅ Done — async UDS shipped v3.79 (`OBD.OEM.UdsClient.Async`) | + +The detailed proposals below are kept verbatim for historical context +and as a reference for the open items. Each section header is annotated +with its current state. + --- -## Proposal A — FMX Component Completion +## Proposal A — FMX Component Completion ✅ Shipped in v3.1 > Ship the remaining six visual components on FMX so a single codebase > drives both desktop (VCL) and mobile/macOS (FMX). @@ -43,7 +60,7 @@ event-loop details. FMX gesture handling differs from VCL `MouseDown`/ --- -## Proposal B — Mobile Transports +## Proposal B — Mobile Transports 🟡 Partial > Light up the FMX components on real iOS / Android / macOS hardware. @@ -69,7 +86,7 @@ BLE has historic stack quirks per OEM ROM. Budget extra QA time. --- -## Proposal C — Production-Grade Crypto + Manufacturer Coverage +## Proposal C — Production-Grade Crypto + Manufacturer Coverage ✅ Shipped (v3.2 + v3.3–v3.69) > Make `TOBDECUFlashing` actually trustworthy in production by adding > real RSA / ECDSA verifiers and three more OEM extensions (Mercedes, @@ -99,7 +116,7 @@ will still need to plug in their own seed-key algorithms. --- -## Proposal D — Heavy-Duty / Commercial +## Proposal D — Heavy-Duty / Commercial ✅ Shipped (v3.16 + v3.69–v3.76) > Round out coverage for trucks, buses, agricultural equipment, and > EV-Ethernet diagnostics. @@ -123,7 +140,7 @@ validate. --- -## Proposal E — Performance, Testing & Profiling +## Proposal E — Performance, Testing & Profiling 🟡 Partial (coverage harness shipped v3.79) > Bring testing rigor up to "I'd ship this in a hospital" levels and > publish numbers. @@ -148,7 +165,7 @@ test fonts into the repo or render via a known-stable typeface. --- -## Proposal F — Localization, Theming & Accessibility +## Proposal F — Localization, Theming & Accessibility 🔴 Open > Make the framework usable internationally and by non-sighted users. @@ -168,7 +185,7 @@ WCAG AA contrast tier. --- -## Proposal G — DevEx & Community +## Proposal G — DevEx & Community 🟡 Partial > Ship the polish that turns "good library" into "go-to library." @@ -190,7 +207,7 @@ WCAG AA contrast tier. --- -## Proposal H — Async Polish & Persistence +## Proposal H — Async Polish & Persistence ✅ Async UDS shipped v3.79 > Smaller, opportunistic — bundle as a v3.1 polish release if you don't > want a big themed milestone. diff --git a/docs/RADIO_CALCULATORS.md b/docs/RADIO_CALCULATORS.md new file mode 100644 index 00000000..d7de217d --- /dev/null +++ b/docs/RADIO_CALCULATORS.md @@ -0,0 +1,196 @@ +# Radio Code Calculators + +The `src/RadioCode/` units compute the unlock code for OEM head units after +battery loss or theft-protection lockout. Every calculator implements +`IOBDRadioCode` (`src/RadioCode/OBD.RadioCode.pas`): + +```pascal +IOBDRadioCode = interface + function GetDescription: string; + function Validate(const Input: string; var ErrorMessage: string): Boolean; + function Calculate(const Input: string; var Output: string; + var ErrorMessage: string): Boolean; +end; +``` + +`TOBDRadioCode` (the base class) supplies the input-sanitisation helpers +(`SanitizeInput`, `ValidateLength`, `ValidateDigits`, …) so each brand +unit only contains the algorithm itself. + +## Usage + +```pascal +uses + OBD.RadioCode, OBD.RadioCode.VW.Advanced; + +var + Calc: IOBDRadioCode; + Code, Err: string; +begin + Calc := TOBDRadioCodeVWAdvanced.Create; + if Calc.Calculate('1234567', Code, Err) then + ShowMessage('Code: ' + Code) + else + ShowMessage('Failed: ' + Err); +end; +``` + +Inputs are always strings (serial numbers, VINs, or pre-codes printed on +the radio chassis). The calculator is responsible for validating format +before computing — a `False` return from `Calculate` means the input was +rejected, not that the algorithm failed. + +## Regional and security variants + +`OBD.RadioCode.Variants` (`src/RadioCode/OBD.RadioCode.Variants.pas`) +manages algorithm selection across: + +- **Region** — `TRadioCodeRegion` (`rcrNorthAmerica`, `rcrEurope`, + `rcrAsia`, `rcrAustralia`, `rcrMiddleEast`, `rcrSouthAmerica`, + `rcrAfrica`, `rcrUnknown`). +- **Model-year range** — `TRadioCodeYearRange` (use `EndYear = 9999` for + open-ended). +- **Security version** — `TRadioCodeSecurityVersion` for OEMs that + rotated the algorithm (e.g. VAG Concert/Symphony updates). + +A single brand unit can register multiple variants; `Variants` resolves +the right one from VIN or model-year metadata. + +## Brand registry + +`OBD.RadioCode.Registry` (`src/RadioCode/OBD.RadioCode.Registry.pas`) +keeps a process-wide map of brand key → factory. Every brand entry +exposes: + +- `BrandKey` — lower-case identifier, e.g. `'vw'`, `'pioneer'`. +- `DisplayName` — human-readable name for UIs. +- `DataAvailable` — `True` when a real algorithm/database backs the + calculator; `False` for **data-pending stubs** (see + [DATA_GAPS.md](DATA_GAPS.md)). +- `DataNotes` — for data-pending entries, describes precisely what + reference material would unblock the calculator. +- `Variants: TRadioCodeVariantManager` — region/year/security-version + dispatch (see "Regional and security variants" above). +- `CreateCalculator` — instantiates an `IOBDRadioCode` for the brand. + +```pascal +uses OBD.RadioCode, OBD.RadioCode.Registry; + +var + Brand: TOBDRadioCodeBrand; + Calc: IOBDRadioCode; + Code, Err: string; +begin + Brand := TOBDRadioCodeRegistry.Instance.Find('vw'); + if (Brand = nil) or not Brand.DataAvailable then + raise Exception.Create('Brand unsupported in this build'); + Calc := Brand.CreateCalculator; + if Calc.Calculate('1234567', Code, Err) then + ShowMessage('Code: ' + Code); +end; +``` + +Calling `Calculate` on a `DataAvailable = False` stub raises +`EOBDRadioCodeDataMissing`, never silently returning a wrong answer. + +### VIN-aware resolution + +`OBD.RadioCode.VinResolver` ties the registry, the variant manager, +and `OBD.VIN.Decoder` together so callers can ask "give me the right +calculator for this car" without juggling regions and year codes. + +```pascal +uses OBD.RadioCode.VinResolver; + +var + Ctx: TRadioCodeResolveContext; + Res: TRadioCodeResolveResult; +begin + Ctx := Default(TRadioCodeResolveContext); + Ctx.BrandKey := 'vw'; + Ctx.VIN := 'WVWZZZ8N8Z1234567'; // year/region decoded from VIN + Res := ResolveCalculator(Ctx); + if Assigned(Res.Calculator) and Res.DataAvailable then + Res.Calculator.Calculate('1234567', Code, Err); +end; +``` + +`TRadioCodeResolveContext` lets callers override the model year or +region (handy for cars where the VIN has been replaced or for +post-VIN units), and supply a model hint to disambiguate between +Concert, RNS, MMI, COMAND, NBT, etc. + +The resolver also seeds the variant manager for VW / Audi / Mercedes +/ BMW with documented per-generation entries (Audi Concert I → III, +Mercedes Becker → MBUX, BMW Business → iDrive 8, VW Gamma → Discover +Pro). Variant boundaries are sourced from public service-info notes +and community archives. The brand-internal calculators continue to +hold the actual algorithms; the registry-side variants give the +resolver enough metadata to dispatch. + +## Brand coverage + +Each brand has a dedicated unit `OBD.RadioCode..Advanced.pas` +unless noted otherwise. All units register on initialisation, so simply +including them via `uses` makes them discoverable through the registry. + +### Japanese +Acura, Honda, Hyundai, Infiniti, Lexus, Mazda, Mitsubishi, Nissan, +Subaru, Suzuki, Toyota. + +### European — VAG group +Audi (Concert/Symphony variants), SEAT, Skoda, VW. + +### European — premium +BMW, Mercedes, Mini, Porsche, Smart, Saab, Volvo, Jaguar, Land Rover, +Maserati. + +### European — French +Citroen, Peugeot, Renault. + +### European — Italian +Alfa Romeo, Fiat (Daiichi and VP variants). + +### European — other +Opel. + +### American +Chrysler / Jeep / Dodge, Ford (Advanced + V-series), GM (Chevrolet, +Cadillac, GMC, Buick). + +### Universal head-unit OEMs +Becker (Becker4, Becker5, Advanced), Blaupunkt, Alpine, Clarion, +Visteon. + +### Data-pending brands (registered as stubs) + +The following brands are registered through `OBD.RadioCode.Pending` +so they appear in the registry but raise `EOBDRadioCodeDataMissing` on +`Calculate`. They become live the moment a verified algorithm or +licensed database lands in the unit. See +[DATA_GAPS.md](DATA_GAPS.md) for the precise data each brand needs. + +Pioneer · Kenwood · JVC · Sony · Philips · Grundig · Panasonic +(Matsushita) · Continental / VDO. + +## Adding a new calculator + +1. Create `src/RadioCode/OBD.RadioCode..Advanced.pas`. +2. Inherit from `TOBDRadioCode`, override `Validate` and `Calculate`. +3. Use the `SanitizeInput` / `ValidateLength` / `ValidateDigits` + helpers — don't reimplement input cleaning. +4. Register the unit in `Packages/RunTime.dpk` and `RunTime.dproj`. +5. Add a fixture-driven test in `tests/` with at least one known + serial → code pair from a public service-manual reference. + +## Notes + +- These calculators target **legitimate recovery** by the vehicle owner + or an authorised workshop after a battery disconnect. They are not + bypass tools — every algorithm derives the code from the radio's own + serial number, so they only work when you have physical access to the + unit. +- Newer head units (post-2015 on most OEMs) tie the radio to the VIN + via the gateway, in which case unlock requires online dealer + activation rather than a serial-derived code. Those models are + intentionally out of scope for this library. diff --git a/docs/SERVICE_ROUTINES.md b/docs/SERVICE_ROUTINES.md new file mode 100644 index 00000000..e893e412 --- /dev/null +++ b/docs/SERVICE_ROUTINES.md @@ -0,0 +1,68 @@ +# Service Routines + +`OBD.OEM.ServiceRoutines` is the canonical home for publicly +documented workshop procedures. Each registered routine carries its +RoutineControl identifier (UDS 0x31), required diagnostic session, +pre-conditions, post-conditions, safety class, and a citation back to +the public spec / TSB / community archive that documents it. + +## Usage + +```pascal +uses OBD.OEM.ServiceRoutines; + +var + Routine: TOBDServiceRoutine; + Frame: TBytes; +begin + if TOBDServiceRoutineRegistry.Instance.Find('oil_reset_bmw', Routine) then + begin + // Caller is responsible for opening the right diagnostic session + // (Routine.RequiredSessionType) and honouring the pre-conditions + // (Routine.PreConditions); we only build the wire frame. + Frame := BuildRoutineControlFrame(Routine); + MyUdsClient.Send(Frame); + end; +end; +``` + +`GetByCategory` and `GetByOEM` let UIs filter the registry per panel. + +## Safety classes + +| Class | Meaning | +|---|---| +| `srsNone` | Safe; no movement / no electrical hazard. | +| `srsEngineMustBeRunning` | Engine running is part of the procedure (e.g. DPF regen). | +| `srsEngineMustBeOff` | Routine writes that require key-on engine-off. | +| `srsVehicleMustBeStationary` | Vehicle on level surface, parking brake set. | +| `srsVehicleMayMove` | EPB unwind, window/sunroof learn — caller MUST clear the area. | +| `srsBatteryMin12V5` | Pair with `OBD.ECU.Flashing.VoltageGate`. | +| `srsRequiresWorkshopLogin` | Workshop card / PIN / SVM portal needed. | + +## Coverage at a glance + +| Category | Routines | +|---|---| +| Maintenance | Oil reset (VAG, BMW, Mercedes, Ford, Toyota), AdBlue level reset | +| Steering & Brakes | SAS zero, EPB service-mode (open/close), ABS bleed, brake-pad change | +| Powertrain | DPF forced regen, throttle body adapt, idle relearn | +| Comfort | Window pinch learn, sunroof initialisation, seat memory reset, headlight aim | +| Battery & Electrical | BMW IBS, Mercedes IBS, Audi 12V battery write, alternator load test | +| TPMS | Sensor relearn, per-wheel ID write | +| Emissions | Readiness clear, drive-cycle marker | + +The full registry sits in `src/Services/OBD.OEM.ServiceRoutines.pas`; +see the `SeedDefault` procedure for the one-line-per-routine entries +with their citations. + +## Adding a new routine + +1. Confirm the routine is documented in a public spec (ISO/SAE), an + OEM TSB, or a reputable community archive (Ross-Tech, FORScan, + BimmerCode, etc.). Don't add proprietary procedures. +2. Add an `Add(...)` line in `SeedDefault` with all fields filled. +3. The Citation field is **mandatory** — `Tests.OEM.ServiceRoutines` + will fail if any entry has an empty citation. +4. Ship a fixture-driven test if the routine has a non-trivial + OptionRecord layout. diff --git a/docs/SUBSYSTEM_ADAPTERS.md b/docs/SUBSYSTEM_ADAPTERS.md new file mode 100644 index 00000000..e6e519b9 --- /dev/null +++ b/docs/SUBSYSTEM_ADAPTERS.md @@ -0,0 +1,35 @@ +# Subsystem: Adapters + +`src/Adapters/` wraps OBD-II hardware behind a single `TOBDAdapter` +contract. Pick or write an adapter when your app needs to talk to a +specific class of hardware (ELM327 clones, OBDLink ST-class, J2534 +pass-through). + +## Files + +| Unit | Purpose | +|---|---| +| `OBD.Adapter.pas` | Abstract `TOBDAdapter` base class — connection lifecycle, voltage, retry policy. | +| `OBD.Adapter.Types.pas` | Shared types and enums (adapter kind, capabilities, protocol guesses). | +| `OBD.Adapter.Constants.pas` | Wire-level constants common to AT/ST adapters. | +| `OBD.Adapter.ATCommands.pas` | ELM327 AT command set (init, headers, timing, sleep). | +| `OBD.Adapter.STCommands.pas` | OBDLink ST extension commands (SX/MX/EX models). | +| `OBD.Adapter.ELM327.pas` | ELM327 driver. Handles AT init, prompts, error recovery. | +| `OBD.Adapter.ELM327.Detection.pas` | Runtime detection of genuine vs Chinese-clone ELM327 chips and the quirks each clone needs. | +| `OBD.Adapter.OBDLink.pas` | OBDLink driver (STN-series chips). Inherits the ELM327 base, adds ST-command extensions. | +| `OBD.Adapter.PassThrough.pas` | SAE J2534 pass-through interface (DLL-loaded vendor drivers). | +| `OBD.Adapter.Enumerator.pas` | Discovery of attached adapters across transports. | + +## Pattern: writing a new adapter + +1. Create `src/Adapters/OBD.Adapter..pas`. +2. Inherit from `TOBDAdapter`; override the lifecycle hooks (`OpenAdapter`, + `CloseAdapter`, `SendBytes`, `ReceiveBytes`) and any quirk handlers. +3. Reuse `OBD.Adapter.ATCommands` if your hardware accepts the ELM327 + AT subset. +4. Register the adapter kind in `OBD.Adapter.Types`. +5. Add fixture-driven tests in `tests/` covering the init handshake. + +For the broader connection model (transport selection, reconnect +policy, simulator stubs) see [ARCHITECTURE.md](ARCHITECTURE.md) and the +`src/Connection/` units. diff --git a/docs/SUBSYSTEM_FORMS.md b/docs/SUBSYSTEM_FORMS.md new file mode 100644 index 00000000..505ac88e --- /dev/null +++ b/docs/SUBSYSTEM_FORMS.md @@ -0,0 +1,46 @@ +# Subsystem: Forms + +`src/Forms/` contains the base form scaffolding that every OBD form +inherits from. There's only one unit, but the conventions matter +because the IDE wizards (see [SUBSYSTEM_WIZARDS.md](SUBSYSTEM_WIZARDS.md)) +generate forms against this base. + +## Files + +| Unit | Purpose | +|---|---| +| `OBD.Form.pas` (+ `.dfm`) | `TOBDForm` — base VCL form. Owns application-settings access, fires `OnWindowStateChange`, and triggers a repaint of every touch control on the form when the window state changes (so headers / subheaders / status bars stay clean across minimize/maximize). | + +## What `TOBDForm` gives you + +- **`OnWindowStateChange: TWindowStateEvent`** — callback whenever + `WindowState` transitions. The base implementation walks the touch + controls and forces a redraw, eliminating a class of stale-buffer + bugs. +- **Application settings access** — convenience proxy onto + `OBD.Application.Settings` so per-form preferences (window position, + user choices) persist via the standard settings store. +- **Touch-control repaint** — built-in collection of + `TOBDTouchHeader` / `TOBDTouchSubheader` / `TOBDTouchStatusbar` + references discovered at runtime; you don't have to wire repaint + manually. + +## Pattern: writing a new form + +Always inherit from `TOBDForm`, not `TForm`: + +```pascal +type + TMyDiagForm = class(TOBDForm) + // ... + end; +``` + +The IDE wizards do this for you (`OBD.Form.Wizard.pas` / +`OBD.Mainform.Wizard.pas`). For ad-hoc forms added by hand, change the +inheritance line and the form will pick up the touch-control repaint +behaviour automatically. + +For non-form scaffolding (shared connection / protocol components), use +the data-module wizard instead — see +[SUBSYSTEM_WIZARDS.md](SUBSYSTEM_WIZARDS.md). diff --git a/docs/SUBSYSTEM_SERVICES.md b/docs/SUBSYSTEM_SERVICES.md new file mode 100644 index 00000000..785a30aa --- /dev/null +++ b/docs/SUBSYSTEM_SERVICES.md @@ -0,0 +1,75 @@ +# Subsystem: Services + +`src/Services/` is the largest subsystem in the repo. It bundles three +distinct concerns under one folder: + +1. **OBD-II Services 01–0A** (SAE J1979) — request encoders and response + decoders for the standard service modes. +2. **OEM extension framework** — `IOBDOEMExtension` registry, JSON + catalog loader, per-OEM extension units, UDS client (sync + async), + coding / RoutineControl / SeedKey helpers, capture-replay. +3. **ECU flashing** — `TOBDECUFlashing` pipeline with pluggable + signature verifiers (BCrypt, OpenSSL, HSM). + +## Layer 1 — OBD-II Services + +| Unit | Mode | Purpose | +|---|---|---| +| `OBD.Service.pas`, `OBD.Service.Types.pas` | — | Shared base + types. | +| `OBD.Service01.pas` | $01 | Live data (PIDs $00–$FF). | +| `OBD.Service02.pas` | $02 | Freeze frame data. | +| `OBD.Service03.pas` | $03 | Stored DTCs. | +| `OBD.Service04.pas` | $04 | Clear DTCs / MIL. | +| `OBD.Service05.pas` | $05 | Oxygen sensor test results. | +| `OBD.Service06.pas` | $06 | On-board monitoring test results. | +| `OBD.Service07.pas` | $07 | Pending DTCs. | +| `OBD.Service08.pas` | $08 | Control of on-board systems. | +| `OBD.Service09.pas` | $09 | Vehicle information (VIN, calibration ID). | +| `OBD.Service0A.pas` | $0A | Permanent DTCs. | +| `OBD.Request.Encoders.pas` / `OBD.Request.Constants.pas` | — | Wire-frame builders. | +| `OBD.Response.Decoders.pas` / `OBD.Response.Constants.pas` | — | Response parsers. | +| `OBD.Service.Recorder.pas` | — | Captures request/response pairs to `.obdlog`. | +| `OBD.ReadinessMonitor.pas` | — | PID $01 monitor decoder (17 monitor kinds, SI + CI). | +| `OBD.FreezeFrame.pas` | — | Service $02 helpers and trigger-DTC formatter. | +| `OBD.VehicleHealth.pas` | — | High-level orchestrator: VIN → OEM auto-detect → DTCs → readiness → live values → 0..100 health score. | + +## Layer 2 — OEM extension framework + +Core contract: + +| Unit | Role | +|---|---| +| `OBD.OEM.pas` | `IOBDOEMExtension` interface, `TOBDOEMRegistry`, `TOBDOEMExtensionBase`. | +| `OBD.OEM.Helpers.pas` | `DID()` / `Routine()` factory helpers for compact catalog literals. | +| `OBD.OEM.Catalog.JSON.pas` / `.CSV.pas` / `.Loader.pas` | JSON Schema v2 + CSV importer + recursive directory loader. | +| `OBD.OEM.Session.pas` / `.Session.Runner.pas` | `IOBDSessionNegotiator` per-OEM choreographies + plan runner with TesterPresent heartbeat. | +| `OBD.OEM.SeedKey.pas` | `TOBDSeedKeyRegistry` per OEM, four reference algorithms (KWP2000 two's-complement, XOR mask, byte-rotate, constant-key). | +| `OBD.OEM.Coding.pas` (+ `Common`, `BMW`, `Ford`, `Mercedes`, `VW`) | Coding / variant-write encoders: VW long-coding, BMW FA + I-Stufe, Mercedes SCN, Ford AsBuilt with FORScan checksum. | +| `OBD.OEM.RoutineControl.pas` | UDS 0x31 framework: request builder + response reader + `TOBDRoutineSchema` + `DecodeRoutineOutput`. | +| `OBD.OEM.DTC.pas` / `.DTC.Loader.pas` | DTC catalogs with provenance flags (ISO 15031-5 wire encoding, 22 OEM prefixes). | +| `OBD.OEM.UdsClient.pas` | Async-friendly facade: OpenSession / ReadDID / WriteAdaptation / ExecuteRoutine / ReadCodingBlock / WriteCodingBlock / RunActuatorTest / ReadDtcs / StreamLivePIDs. | +| `OBD.OEM.UdsClient.Async.pas` | Future-returning facade with cooperative cancellation (one serialised worker thread per client). | +| `OBD.OEM.DiagSession.pas` | High-level `TOBDDiagSession` wrapper: BeginSession + EndSession + UnlockSecurityAccess + ReadDID + StartRoutine / Stop / RequestResults; owns the tester-present heartbeat lifecycle. | +| `OBD.OEM.DoIP.pas` | DoIP / ISO 13400-2 wrapper used by the OEM client. | +| `OBD.OEM.Captures.pas` | Capture-replay validation: pairs Sent → Received from `.obdlog`, runs `0x22` reads through `Ext.DecodeDID`. | +| `OBD.OEM.GoldenCheck.pas` | `TOBDGoldenVector + CheckGoldenVectors` for curated per-OEM regression suites. | +| `OBD.OEM.ServiceFunction.pas` | Unified service-function API across OEMs. | + +Per-OEM extensions live alongside as `OBD.OEM..pas` (79 catalogs +across passenger / motorcycle / agricultural / marine / powersports +classes — see [../catalogs/INDEX.md](../catalogs/INDEX.md)). The shared +heavy-duty base is `OBD.OEM.HD.pas` (3000 ms heartbeat, J1939 +source-address constants, SPN-FMI helpers, DM1 packed-DTC parser). + +## Layer 3 — ECU flashing + +| Unit | Purpose | +|---|---| +| `OBD.ECU.Flashing.pas` | `TOBDECUFlashing` pipeline coordinator (pre-check → snapshot → signature → erase → write → finalise → verify, with automatic rollback). | +| `OBD.ECU.Signature.pas` | `IFirmwareSignatureVerifier` + `TOBDSha256SignatureVerifier` + `TOBDPermissiveSignatureVerifier`. | +| `OBD.ECU.Signature.BCrypt.pas` | RSA-PKCS1-SHA256 / ECDSA-P256 via Windows BCrypt — no external DLLs. | +| `OBD.ECU.Signature.OpenSSL.pas` | OpenSSL-backed verifier for cross-platform builds. | +| `OBD.ECU.Signature.HSM.pas` | HSM-backed verifier (PKCS#11 / vendor SDKs). | + +For end-to-end usage see `examples/ecuflashing_console/` and +`examples/ecuflashing/`. diff --git a/docs/SUBSYSTEM_WIZARDS.md b/docs/SUBSYSTEM_WIZARDS.md new file mode 100644 index 00000000..4e12f605 --- /dev/null +++ b/docs/SUBSYSTEM_WIZARDS.md @@ -0,0 +1,37 @@ +# Subsystem: IDE Wizards + +`src/Wizards/` registers four design-time wizards in the Delphi IDE +under **File → New → Other → ERDesigns OBD**. They scaffold projects, +forms, and data modules pre-wired with the non-visual binding +components described in [QuickStart.md](../QuickStart.md). + +## Files + +| Unit | Wizard menu | What it generates | +|---|---|---| +| `OBD.Project.Wizard.pas` | "ERDesigns OBD Project" | Empty OBD project scaffold (.dpr + main form). | +| `OBD.Mainform.Wizard.pas` | "ERDesigns OBD Mainform" | Form with touch header, subheader, status bar, and one circular gauge — pre-wired to `TOBDConnectionComponent` (serial, COM1 @ 38400), `TOBDProtocolComponent` (auto-binding), `TOBDHeaderComponent`, `TOBDSubheaderComponent`, `TOBDGaugeComponent`. | +| `OBD.Form.Wizard.pas` | "ERDesigns OBD Form" | Secondary form with the same visual + non-visual scaffold as the main form, so it participates in the same binding pattern. | +| `OBD.DataModule.Wizard.pas` | "ERDesigns OBD DataModule" | Data module hosting shared connection + protocol components for reuse across forms. | + +## Registration + +The wizards register at design time only — they live in +`Packages/DesignTime.dpk` and load via Delphi's IDE-package mechanism. +After installing `DesignTime.bpl` you must restart the IDE for the +"ERDesigns OBD" menu group to appear. + +## Pattern: editing a wizard template + +Each wizard generates source by string substitution against a template +embedded in the unit. To change what gets emitted: + +1. Edit the template literal in the wizard unit (search for the + `TStringList` build-up of the new file). +2. Recompile and reinstall `DesignTime.dpk`. +3. Restart the IDE; the new template applies to subsequently created + files only. + +When adding a new wizard, register it in +`OBD.CustomControl.Register.pas` alongside the visual component +registrations so a single `Register` call exposes everything. diff --git a/docs/TASKS.md b/docs/TASKS.md index 34b9cc5a..8531debd 100644 --- a/docs/TASKS.md +++ b/docs/TASKS.md @@ -1,886 +1,19 @@ -# Delphi-OBD Development Tasks -**Last Updated:** December 7, 2024 -**Copyright:** © 2024-2026 Ernst Reidinga (ERDesigns) - -This document provides a prioritized task list for the Delphi-OBD project. All radio calculator consolidation tasks have been completed. - ---- - -## 📋 Remaining Tasks - -### Phase 0: Visual Component Optimization (December 7, 2024) - -#### TASK 0.3: Create Visual Component Optimization Guidelines -- **Priority:** 🟢 LOW -- **Estimated Effort:** 1-2 hours -- **Description:** Document best practices for future visual components - -**Subtasks:** -- [ ] Document Skia-only rendering pattern (no TBitmap mixing) -- [ ] Create component development checklist -- [ ] Add memory profiling guidelines -- [ ] Document caching strategies (when to cache, when to regenerate) -- [ ] Add performance testing procedures -- [ ] Create "anti-patterns" section (what to avoid) -- [ ] Add to README or separate GUIDELINES.md - -**Expected Outcome:** Consistent, optimized components in future development - ---- - -### Phase 1: Optimizations & Improvements - -#### TASK 1.6: Performance Profiling & Optimization -- **Priority:** 🟡 MEDIUM -- **Estimated Effort:** 3-4 hours -- **Description:** Profile and optimize hot paths - -**Subtasks:** -- [ ] Set up profiling environment - - Install AQtime or similar profiler - - Create performance test suite -- [ ] Profile rendering performance - - Identify slow drawing operations - - Measure FPS under load - - Test with multiple components -- [ ] Profile connection performance - - Measure message parsing time - - Optimize protocol decoders - - Test with high-frequency data -- [ ] Optimize identified bottlenecks - - Replace slow algorithms - - Add caching where appropriate - - Reduce allocations in hot paths -- [ ] Benchmark improvements - - Before/after comparisons - - Document performance gains - - Update README with benchmarks - ---- - -### Phase 2: Extensions & New Features - -**Focus:** Add new features, protocols, and components to expand functionality. - -#### TASK 2.1: Complete OBD Services Component (All Modes 01-0A) (NEW - December 7, 2024) -- **Priority:** 🔴 HIGH -- **Estimated Effort:** 20-25 hours (split across 12-15 sessions) -- **Description:** Create comprehensive non-visual component wrapping ALL OBD Services (01-0A) with ALL PIDs as published properties for drag-and-drop IDE integration - -**Overview:** -Implement a single powerful `TOBDServicesComponent` that exposes ALL OBD diagnostic services (01-0A) with ALL their PIDs/parameters as published properties and events. This provides complete OBD functionality in a single component that can be dropped on a form in the IDE. - -**Architecture Decision:** -- **Single Unified Component** vs Multiple Service Components -- Choose: Single `TOBDServicesComponent` with sub-properties for each service -- Rationale: Easier to use, single connection binding, coordinated refresh, cleaner IDE experience -- Structure: `OBDServices.Service01.EngineRPM`, `OBDServices.Service03.DTCs`, etc. - -**Subtasks:** - -**2.1.1: Base Services Component Architecture** (Session 1: 120-150 min) -- [ ] Create `TOBDServicesComponent` main component - - Inherit from TComponent (non-visual) - - Add `ConnectionComponent: TOBDConnectionComponent` property with binding - - Add `AdapterComponent: TOBDAdapterComponent` property - - Add `AutoRefresh: Boolean` and `RefreshInterval: Cardinal` properties - - Implement timer for auto-refresh functionality - - Add `OnError` event for global error handling - - Add thread-safety for async operations -- [ ] Create internal service wrapper instances - - Private instances of TOBDService01 through TOBDService0A - - Handle initialization and cleanup - - Coordinate communication through adapter -- [ ] Test base infrastructure - -**2.1.2: Service 01 Component Wrapper (Live Data - 105 PIDs)** (Sessions 2-4: 360-450 min total) -- [ ] Create `TOBDService01Wrapper` class - - Wrap existing TOBDService01 with component-friendly interface - - **Expose ALL 105 properties as published** (currently read-only in TOBDService01): - - Monitor/Test Status Properties (~15 properties) - - `MIL: Boolean` - Malfunction Indicator Light status - - `DTC: Integer` - Diagnostic Trouble Code count - - `CommonTest`, `SparkEngineTest`, `CompressionEngineTest` objects - - Engine Parameters (~20 properties) - - `EngineRPM: Integer` - Engine speed in RPM - - `CalculatedEngineLoad: Double` - Engine load percentage - - `EngineCoolantTemperature: Integer` - Coolant temp in °C - - `IntakeAirTemperature: Integer` - Intake air temp in °C - - `RuntimeSinceEngineStart: Integer` - Runtime in seconds - - `TimingAdvance: Double` - Ignition timing advance - - `MassAirFlowRate: Double` - MAF sensor rate - - Fuel System (~15 properties) - - `FuelSystem1Status`, `FuelSystem2Status` enums - - `ShortTermFuelTrimBank1/2: Double` - Fuel trim percentages - - `LongTermFuelTrimBank1/2: Double` - Long term fuel trim - - `FuelPressure: Integer` - Fuel rail pressure - - `FuelRailPressure: Double` - Detailed fuel rail pressure - - `FuelRailGaugePressure: Double` - Gauge pressure - - `FuelType: Byte` - Fuel type code - - `EthanolFuelPercent: Double` - Ethanol content - - Vehicle Motion (~10 properties) - - `VehicleSpeed: Integer` - Speed in km/h - - `ThrottlePosition: Double` - Throttle percentage - - `AcceleratorPedalPositionD/E/F: Double` - Pedal positions - - `CommandedThrottleActuator: Double` - Actuator control - - `RelativeThrottlePosition: Double` - Relative position - - Oxygen Sensors (~25 properties) - - `OxygenSensorPresent2Banks`, `OxygenSensorPresent4Banks` objects - - 8x `OxygenSensor[1-4]Bank[1-2]VoltageFuelTrim` objects - - 8x `OxygenSensor[1-8]AirFuelRatioVoltage` objects - - 8x `OxygenSensor[1-8]AirFuelRatioCurrent` objects - - Trim values for all banks - - Temperature Sensors (~10 properties) - - `CatalystTemperatureSensor1/2Bank1/2: Double` - Cat temps - - `AmbientAirTemperature: Integer` - Outside temp - - `EngineOilTemperature: Integer` - Oil temp - - Sensor A/B temperatures - - Pressure Sensors (~8 properties) - - `IntakeManifoldAbsolutePressure: Integer` - MAP sensor - - `AbsoluteBarometricPressure: Integer` - Barometric pressure - - `EvapSystemVaporPressure: Double` - EVAP pressure - - Boost pressure values - - Emission Control (~12 properties) - - `CommandedSecondaryAirStatus` enum - - `CommandedEGR: Double`, `EGRError: Double` - - `CommandedEvaporativePurge: Double` - - `DistanceTraveledWithMILOn: Integer` - - `WarmUpsSinceCodesCleared: Integer` - - `DistanceTraveledSinceCodesCleared: Integer` - - **Add events for ALL property changes** (105+ events): - - `OnEngineRPMChange`, `OnVehicleSpeedChange`, `OnCoolantTempChange`, etc. - - Event fired only when value actually changes - - Include old and new value in event parameters - - **Add request methods:** - - `RequestPID(APID: Byte): Boolean` - Request single PID - - `RequestMultiplePIDs(APIDs: array of Byte): Boolean` - Multi-PID request - - `RequestAllSupported: Boolean` - Request all supported PIDs - - `RefreshCommonPIDs: Boolean` - Quick refresh of most common PIDs - - **Add PID management:** - - `IsPIDSupported(APID: Byte): Boolean` - Check if ECU supports PID - - `RefreshSupportedPIDs: Boolean` - Update supported PID list - - `SupportedPIDCount: Integer` - Number of supported PIDs -- [ ] Implement smart caching to prevent redundant reads -- [ ] Add selective refresh (only changed values trigger events) -- [ ] Test with live vehicle data stream - -**2.1.3: Service 02 Component Wrapper (Freeze Frame Data)** (Session 5: 90-120 min) -- [ ] Create `TOBDService02Wrapper` class - - Wrap existing TOBDService02 - - Property `FreezeFrameCount: Integer` - Number of stored freeze frames - - Property `StoredDTC: string` - DTC that triggered freeze frame - - **Expose same 105+ properties as Service 01 but for freeze frame data** - - Method `RequestFreezeFrame(FrameNumber: Byte): Boolean` - - Method `GetAvailableFrames: TArray` - - Event `OnFreezeFrameLoaded` -- [ ] Test with vehicles having freeze frames - -**2.1.4: Service 03 Component Wrapper (Stored DTCs)** (Session 6: 60-90 min) -- [ ] Create `TOBDService03Wrapper` class - - Wrap existing TOBDService03 - - Property `DTCs: TStringList` - List of stored codes - - Property `DTCCount: Integer` - Number of codes - - Property `MILStatus: Boolean` - Check engine light status - - Method `RefreshDTCs: Boolean` - Read all stored codes - - Method `GetDTCDescription(Code: string): string` - Code descriptions - - Method `ExportDTCs(Filename: string): Boolean` - Export to file - - Event `OnDTCsChanged(Sender: TObject; DTCs: TStringList)` - - Event `OnDTCAdded(Sender: TObject; DTC: string)` -- [ ] Add DTC description database -- [ ] Test with various fault codes - -**2.1.5: Service 04 Component Wrapper (Clear DTCs/Reset MIL)** (Session 6: 30-45 min) -- [ ] Create `TOBDService04Wrapper` class - - Wrap existing TOBDService04 - - Property `RequireConfirmation: Boolean` - Safety confirmation - - Property `LastClearResult: Boolean` - Status of last clear operation - - Method `ClearDTCs: Boolean` - Clear all codes and reset MIL - - Method `ClearDTCsWithConfirmation(ConfirmProc: TFunc): Boolean` - - Event `OnBeforeClear(Sender: TObject; var AllowClear: Boolean)` - Cancellable - - Event `OnAfterClear(Sender: TObject; Success: Boolean)` - - Event `OnClearFailed(Sender: TObject; ErrorMsg: string)` -- [ ] Add confirmation dialog support -- [ ] Test safe clearing procedure - -**2.1.6: Service 05 Component Wrapper (O2 Sensor Test Results)** (Session 7: 90-120 min) -- [ ] Create `TOBDService05Wrapper` class - - Wrap existing TOBDService05 - - **Expose all oxygen sensor test results** (~40+ properties): - - Test results for all sensor positions (Bank 1/2, Sensor 1-4) - - Voltage, current, and resistance values - - Rich/lean switching time - - Test limits and pass/fail status - - Method `RequestO2SensorTests: Boolean` - - Method `GetSensorTestResult(Bank, Sensor: Byte): TO2TestResult` - - Event `OnO2TestsCompleted` -- [ ] Test with vehicles supporting O2 sensor tests - -**2.1.7: Service 06 Component Wrapper (On-Board Test Results)** (Session 8: 60-90 min) -- [ ] Create `TOBDService06Wrapper` class - - Wrap existing TOBDService06 - - **Expose monitoring test results** (~20+ properties): - - Test IDs for catalyst, EVAP, O2 sensors, EGR, etc. - - Min/Max values and test results - - Pass/fail status for each test - - Method `RequestTestResults(TestID: Byte): Boolean` - - Method `GetAllTestResults: TArray` - - Property `TestCount: Integer` - - Event `OnTestResultsUpdated` -- [ ] Add test ID descriptions -- [ ] Test with various monitoring tests - -**2.1.8: Service 07 Component Wrapper (Pending DTCs)** (Session 9: 45-60 min) -- [ ] Create `TOBDService07Wrapper` class - - Wrap existing TOBDService07 - - Property `PendingDTCs: TStringList` - Codes pending confirmation - - Property `PendingDTCCount: Integer` - - Method `RefreshPendingDTCs: Boolean` - - Method `GetDTCStatus(Code: string): string` - Pending vs Confirmed - - Event `OnPendingDTCsChanged` - - Event `OnNewPendingDTC(DTC: string)` - Alert on new pending code -- [ ] Test with intermittent fault conditions - -**2.1.9: Service 08 Component Wrapper (Control On-Board Systems)** (Session 10: 90-120 min) -- [ ] Create `TOBDService08Wrapper` class - - Wrap existing TOBDService08 - - **Expose control test parameters** (~15+ properties): - - Available test IDs - - Test control parameters - - Test results and status - - Method `RequestControl(TestID: Byte): Boolean` - - Method `StopControl: Boolean` - - Method `GetAvailableTests: TArray` - - Property `ActiveTestID: Byte` - Currently active test - - Property `TestActive: Boolean` - - Event `OnControlTestStarted(TestID: Byte)` - - Event `OnControlTestCompleted(TestID: Byte; Result: Boolean)` - - Event `OnControlTestFailed(TestID: Byte; Error: string)` -- [ ] Add safety interlocks for critical tests -- [ ] Test with supported vehicle control tests - -**2.1.10: Service 09 Component Wrapper (Vehicle Information)** (Session 11: 90-120 min) -- [ ] Create `TOBDService09Wrapper` class - - Wrap existing TOBDService09 - - **Expose all vehicle info PIDs** (~25+ properties): - - `VIN: string` - Vehicle Identification Number - - `CalibrationID: string` - ECU calibration ID - - `CalibrationVerificationNumbers: TStringList` - CVNs - - `ECUName: string` - ECU identification - - `InUsePerformanceTracking: TStringList` - Performance data - - `IPTSpark: TIPTSparkData` - Spark ignition tracking - - `IPTCompression: TIPTCompressionData` - Compression ignition tracking - - All PID $00-$0F info types - - Method `RequestVehicleInfo: Boolean` - Populate all at once - - Method `RequestVIN: string` - Quick VIN only - - Method `RequestCalibrationData: Boolean` - - Method `ExportVehicleInfo(Filename: string): Boolean` - - Event `OnVehicleInfoRetrieved(Sender: TObject)` - - Event `OnVINRetrieved(VIN: string)` -- [ ] Integrate with existing TVINDecoder -- [ ] Add info export to JSON/XML -- [ ] Test with multiple vehicle types - -**2.1.11: Service 0A Component Wrapper (Permanent DTCs)** (Session 12: 45-60 min) -- [ ] Create `TOBDService0AWrapper` class - - Wrap existing TOBDService0A - - Property `PermanentDTCs: TStringList` - Codes requiring drive cycle - - Property `PermanentDTCCount: Integer` - - Property `RequiresDriveCycle: Boolean` - Any permanent codes present - - Method `RefreshPermanentDTCs: Boolean` - - Method `GetDriveCycleStatus: string` - Explain what's needed to clear - - Event `OnPermanentDTCsChanged` - - Event `OnDriveCycleRequired` - Alert user to drive cycle needed -- [ ] Add drive cycle instructions per manufacturer -- [ ] Test with permanent fault conditions - -**2.1.12: Integration and Coordination** (Session 13: 120-150 min) -- [ ] Wire up all service wrappers to main component - - Published sub-properties: `Service01`, `Service02`, ... `Service0A` - - Coordinate adapter communication - - Shared connection handling - - Unified error handling -- [ ] Implement smart refresh strategies - - Priority queue for PIDs (common ones first) - - Batch multiple PID requests - - Avoid overwhelming slow adapters - - Adaptive refresh rates based on update frequency -- [ ] Add global configuration - - `RefreshMode: TRefreshMode` - Auto, Manual, OnDemand - - `CommonPIDsOnly: Boolean` - Only refresh frequently used PIDs - - `EnableService[01-0A]: Boolean` - Enable/disable individual services -- [ ] Create component registration -- [ ] Add component icon and palette category - -**2.1.13: Testing and Documentation** (Sessions 14-15: 180-240 min) -- [ ] Create comprehensive test suite - - Test with real vehicles - - Test with simulators - - Test all 105+ Service 01 PIDs - - Test all services (01-0A) - - Test error conditions - - Test rapid refresh scenarios -- [ ] Write detailed documentation - - Property reference for all 250+ properties - - Event reference for all 150+ events - - Usage examples for common scenarios - - Performance tuning guide - - Troubleshooting guide -- [ ] Create example applications - - Simple dashboard (5-10 properties) - - Full diagnostic tool (all properties) - - DTC reader/clearer - - Vehicle info viewer - -**Expected Outcomes:** -- Single powerful `TOBDServicesComponent` with 250+ properties -- All OBD services (01-0A) wrapped with full PID coverage -- 150+ events for property changes and operations -- Drag-and-drop IDE support (zero code for basic monitoring) -- Auto-refresh with smart batching -- Type-safe access to all OBD data -- Production-ready for commercial applications -- Complete documentation and examples - -#### TASK 2.2: ECU Flashing Component (NEW - December 7, 2024) -- **Priority:** 🔴 HIGH -- **Estimated Effort:** 6-8 hours (split across 3-4 sessions) -- **Description:** Create non-visual component for ECU programming/flashing operations - -**Subtasks:** - -**2.2.1: Base Flashing Component** (Session 1: 120-150 min) -- [ ] Create `TOBDECUFlasherComponent` - - Property `TargetECU: string` (PCM, TCM, ABS, etc.) - - Property `FirmwareFile: string` (path to .bin/.hex file) - - Property `BackupFile: string` (path for backup before flashing) - - Property `VerifyAfterWrite: Boolean` (default: true) - - Property `RequireVoltageCheck: Boolean` (default: true, min 12.5V) - - Property `MinimumVoltage: Single` (default: 12.5V) - - Events: - - `OnProgress(Percent: Integer; Status: string)` - - `OnPhaseChange(Phase: TFlashPhase)` (Backup, Erase, Write, Verify) - - `OnComplete(Success: Boolean)` - - `OnError(ErrorCode: Integer; ErrorMsg: string)` - - `OnVoltageWarning(CurrentVoltage: Single)` -- [ ] Add flash phases enum: TFlashPhase = (fpBackup, fpErase, fpWrite, fpVerify, fpComplete) -- [ ] Implement voltage monitoring during flash - -**2.2.2: Flash File Handling** (Session 2: 90-120 min) -- [ ] Add firmware file validation - - Detect format (.bin, .hex, .s19, .frf) - - Parse and validate checksums - - Verify file size matches ECU memory - - Check compatibility with VIN/ECU type -- [ ] Implement backup functionality - - Read current ECU firmware - - Save to backup file with metadata - - Add restore capability -- [ ] Add safety checks before flashing - -**2.2.3: Manufacturer-Specific Algorithms** (Session 3: 120-150 min) -- [ ] Implement seed/key algorithms - - Ford algorithm support - - GM algorithm support - - VAG algorithm support - - Auto-detect algorithm from ECU response -- [ ] Add security access level handling - - Level 1: Diagnostic - - Level 2: Programming - - Level 3: Manufacturer -- [ ] Test with seed/key calculator tools - -**2.2.4: Flash Operation Implementation** (Session 4: 120-150 min) -- [ ] Implement `StartFlashing` method - - Enter programming mode - - Perform backup if enabled - - Erase ECU flash - - Write firmware blocks - - Verify each block - - Exit programming mode -- [ ] Add progress tracking (% complete, blocks written, time remaining) -- [ ] Implement abort/rollback functionality -- [ ] Add detailed logging to file -- [ ] Test with bench ECUs (safe testing) - -**Expected Outcomes:** -- Safe, verified ECU flashing from IDE -- Automatic backup before flashing -- Voltage monitoring and safety checks -- Support for multiple manufacturers -- Progress tracking with abort capability - -#### TASK 2.3: PassThrough J2534 Component (NEW - December 7, 2024) -- **Priority:** 🟡 MEDIUM -- **Estimated Effort:** 4-5 hours (split across 2-3 sessions) -- **Description:** Create non-visual component wrapping J2534 PassThrough interface - -**Subtasks:** - -**2.3.1: J2534 Component Basics** (Session 1: 90-120 min) -- [ ] Create `TOBDJ2534Component` - - Property `DeviceName: string` (selected J2534 device) - - Property `AvailableDevices: TStringList` (discovered devices) - - Method `ScanForDevices` to populate available list - - Property `ProtocolID: TJ2534Protocol` (J1850PWM, J1850VPW, ISO9141, etc.) - - Property `Baudrate: Cardinal` - - Events: `OnDeviceConnected`, `OnDeviceDisconnected`, `OnMessageReceived` -- [ ] Implement device enumeration from registry -- [ ] Test device discovery - -**2.3.2: PassThrough Operations** (Session 2: 120-150 min) -- [ ] Implement core J2534 functions - - `PassThruOpen` / `PassThruClose` - - `PassThruConnect` / `PassThruDisconnect` - - `PassThruReadMsgs` / `PassThruWriteMsgs` - - `PassThruStartPeriodicMsg` / `PassThruStopPeriodicMsg` - - `PassThruSetProgrammingVoltage` -- [ ] Add message filtering - - Pass filters: Only receive matching messages - - Block filters: Block unwanted messages - - Flow control filters: For multi-frame messages -- [ ] Implement timeout handling -- [ ] Test with real J2534 device - -**2.3.3: High-Level Helpers** (Session 3: 60-90 min) -- [ ] Add convenience methods - - `SendDiagnosticRequest(Data: TBytes): TBytes` - - `ReadDTCs: TStringList` - - `ClearDTCs: Boolean` - - `SetProgrammingVoltage(Volts: Single)` -- [ ] Implement auto-retry on errors -- [ ] Add connection keep-alive -- [ ] Test with diagnostic operations - -**Expected Outcomes:** -- Direct J2534 device access from IDE -- Support for professional-grade adapters -- Programming voltage control -- Message filtering and flow control -- High-level diagnostic helpers - -#### TASK 2.4: J1939 Protocol Enhancements -- **Priority:** 🟡 MEDIUM -- **Estimated Effort:** 3-4 hours -- **Description:** Enhance J1939 protocol support - -**Subtasks:** -- [ ] Parameter group number (PGN) library -- [ ] Transport protocol support -- [ ] Diagnostic messages - -#### TASK 2.5: Data Logging & Playback -- **Priority:** 🟡 MEDIUM -- **Estimated Effort:** 4-6 hours -- **Description:** Record and replay OBD sessions - -**Subtasks:** -- [ ] Design log file format (JSON/CSV/Binary) -- [ ] Implement session recording - - Timestamp all messages - - Include metadata (vehicle info, adapter type) - - Compress large log files -- [ ] Implement playback functionality - - Play logs at original speed or accelerated - - Seek/pause/resume controls - - Filter by service/PID -- [ ] Add log analysis tools - - Statistics and summaries - - Error detection - - Performance metrics - -#### TASK 2.6: Manufacturer-Specific ECU Programming -- **Priority:** 🔴 HIGH -- **Estimated Effort:** 20-30 hours -- **Description:** Add ECU programming support for major manufacturers via ELM327/J2534 - -**Overview:** -Enable ECU programming, tuning, and firmware updates for major vehicle manufacturers using both ELM327 adapters (for basic programming) and J2534 pass-through interfaces (for advanced programming). Focus on manufacturers with well-documented protocols that can work with consumer-grade adapters. - -**Subtasks:** - -**2.6.1: Ford/Mazda ECU Programming** -- [ ] Implement Ford IDS (Integrated Diagnostic System) protocol subset - - Service $34 (Request Download) - download firmware to ECU - - Service $35 (Request Upload) - upload firmware from ECU - - Service $36 (Transfer Data) - transfer firmware blocks - - Service $37 (Request Transfer Exit) - complete transfer - - Service $31 (Routine Control) - erase flash, check programming dependencies - - Ford-specific seed/key algorithms (multiple generations) - - Support for PCM (Powertrain), TCM (Transmission), ABS, BCM modules -- [ ] Implement Mazda CAN protocol (similar to Ford) - - Mazda uses Ford-derived protocols for 2006+ vehicles - - Support for PCM, TCM programming via UDS - - Mazda-specific security access algorithms -- [ ] Add Ford/Mazda module database - - List of programmable modules per model/year - - Required security levels - - Flash memory layouts - - Known calibration IDs -- [ ] Test with ELM327 + J2534 - - Verify ELM327 can handle Ford SWCAN (125 kbps) - - Test J2534 for high-speed programming - - Implement voltage control for programming (13.5V minimum) - -**2.6.2: GM (General Motors) ECU Programming** -- [ ] Implement GM VCI (Vehicle Communication Interface) protocol - - Mode $34 (Request Download) - - Mode $36 (Transfer Data) with 4KB block size - - Mode $37 (Request Transfer Exit) - - GM-specific seed/key algorithms (multiple generations) - - Support for E38, E67, E78 ECM modules - - Support for 6L80/6L90 TCM modules -- [ ] Add GM security access levels - - Level 1: Diagnostic access - - Level 2: Programming access (GMLAN) - - Level 3: Manufacturer access -- [ ] Implement GMLAN (GM Local Area Network) specific commands - - Device control ($AE) for programming mode - - Programming mode enable/disable - - CAN arbitration ID modifications -- [ ] Test with ELM327 + J2534 - - GM requires J2534 for most programming - - ELM327 can be used for reading/diagnostics - - Implement proper voltage control (12-15V) - -**2.6.3: VAG (VW/Audi/SEAT/Skoda) ECU Programming** -- [ ] Implement VAG KWP2000 + UDS protocols - - Support for EDC15/EDC16/EDC17 ECUs - - Support for ME7/MED9/MED17 ECUs - - Service $27 security access (VAG-specific algorithms) - - Service $2E write data by identifier (flash write) - - Service $31 routine control (erase, checksum) -- [ ] Add ODIS (Offboard Diagnostic Information System) support - - Flash file parsing (.frf, .odx) - - Module coding - - Adaptation channels - - Long coding -- [ ] Implement VAG immobilizer programming - - EEPROM read/write for immobilizer data - - Key adaptation - - Module pairing -- [ ] Test with ELM327 + J2534 - - KWP2000 works well with ELM327 - - UDS requires faster adapters - - Implement proper wake-up sequences - -**2.6.4: BMW ECU Programming** -- [ ] Implement BMW EDIABAS protocol subset - - D-CAN and K-CAN protocol support - - Job-based communication - - Diagnostic jobs (LESEN, SCHREIBEN) - - Coding/programming jobs -- [ ] Add BMW module programming - - Service $34/$36/$37 (UDS-based) - - BMW-specific security access (ISN-based) - - Support for MSV80, MSD80, MSD85 DME modules - - Support for EGS, CAS, FRM modules -- [ ] Implement BMW Flash (CAFD) file support - - Parse BMW flash container files - - Extract flash data and calibrations - - Verify checksums before programming -- [ ] Test with ENET cable + ELM327 - - DoIP preferred for F-series and newer - - K-CAN for E-series via ELM327 - - Implement proper wake-up (5-baud init) - -**2.6.5: Toyota/Lexus ECU Programming** -- [ ] Implement Toyota Techstream protocol - - Service $10 diagnostic session control - - Service $27 security access (Toyota algorithms) - - Service $34/$36/$37 for programming - - Service $31 routine control - - Support for multiple ECUs (ECM, TCM, ABS, VSC) -- [ ] Add Toyota security algorithms - - Generation 1 (1996-2005): Simple XOR - - Generation 2 (2006-2015): RSA-based - - Generation 3 (2016+): AES encryption -- [ ] Implement Toyota calibration management - - Calibration ID verification - - VIN writing - - Immobilizer key registration -- [ ] Test with ELM327 + J2534 - - ELM327 works for older vehicles - - J2534 required for 2010+ programming - - Implement proper timing parameters - -**2.6.6: Honda/Acura ECU Programming** -- [ ] Implement Honda HDS (Honda Diagnostic System) protocol - - Service $27 security access (Honda seed/key) - - Service $34/$36/$37 for firmware upload - - Service $31 routine control (erase/write) - - Support for K-series, L-series, R-series ECUs -- [ ] Add Honda-specific features - - Knock sensor learning - - VTEC calibration - - A/F ratio learning reset - - Idle learning -- [ ] Implement Honda immobilizer programming - - Key programming via OBD - - Immobilizer reset - - PCM replacement procedures -- [ ] Test with ELM327 + J2534 - - Works well with ELM327 for most operations - - J2534 preferred for programming - - Implement proper voltage control - -**2.6.7: Nissan/Infiniti ECU Programming** -- [ ] Implement Nissan CONSULT III protocol - - Service $27 security access - - Service $34/$36/$37 programming - - Service $31 erase/write routines - - Support for Hitachi ECUs -- [ ] Add Nissan-specific features - - Throttle body relearn - - NATS (Nissan Anti-Theft System) programming - - CVT adaptation - - Steering angle sensor calibration -- [ ] Test with ELM327 + J2534 - - ELM327 limited for programming - - J2534 required for most operations - -**2.6.8: ECU Programming Safety Features** -- [ ] Implement voltage monitoring - - Check battery voltage before programming (min 12.5V) - - Monitor voltage during programming - - Abort if voltage drops below threshold -- [ ] Add backup/restore functionality - - Automatic ECU backup before programming - - Restore original firmware on failure - - Store backup metadata (VIN, date, module info) -- [ ] Implement verification procedures - - Checksum verification before write - - Block-by-block CRC checking - - Read-back verification after write - - Software version validation -- [ ] Add progress tracking and logging - - Real-time progress display (0-100%) - - Detailed operation logging - - Error recovery procedures - - Programming time estimation - -**2.6.9: Universal ECU Programming UI** -- [ ] Create comprehensive ECU flashing interface - - Vehicle selection (make/model/year) - - Module selection (PCM/TCM/ABS/etc.) - - Firmware file browser (.bin, .hex, .s19) - - Backup/restore buttons - - Progress bar with status - - Detailed log viewer -- [ ] Add firmware file validation - - File format detection - - Size validation - - Checksum verification - - Compatibility checking (VIN match) -- [ ] Implement safety checks UI - - Battery voltage indicator - - Programming prerequisites checklist - - Warning dialogs for critical operations - - Rollback options on failure - -**2.6.10: Manufacturer Algorithm Libraries** -- [ ] Create seed/key algorithm library - - Ford algorithms (multiple generations) - - GM algorithms (Tis2Web, GM PASS) - - VAG algorithms (Component Protection) - - BMW ISN-based algorithms - - Toyota challenge/response - - Honda seed/key - - Nissan NATS algorithms -- [ ] Add algorithm auto-detection - - Detect algorithm from ECU response - - Try multiple algorithms automatically - - Fallback options for unknown ECUs -- [ ] Implement algorithm testing tools - - Test known seed/key pairs - - Validate algorithm implementations - - Performance benchmarking - -**Expected Outcomes:** -- ECU programming support for 7+ major manufacturers -- Works with both ELM327 (basic) and J2534 (advanced) -- Safe, verified programming procedures with backup/restore -- Comprehensive manufacturer-specific algorithms -- Professional-grade UI for ECU flashing operations - -**Testing Requirements:** -- Test with real vehicles (bench testing preferred) -- Verify voltage control works correctly -- Ensure backup/restore functionality is reliable -- Test with multiple adapter types (ELM327, J2534, OBDLink) -- Validate all safety features work as intended - -**Documentation Requirements:** -- Document supported modules per manufacturer -- List compatible adapters per operation -- Create programming guides with screenshots -- Document known limitations and issues -- Provide troubleshooting guides - ---- - -## Task Priority Legend - -- 🔴 **HIGH**: Critical functionality or blocking issues -- 🟡 **MEDIUM**: Important but not urgent -- 🟢 **LOW**: Nice to have, future enhancements - ---- - -## Completed Tasks - -### ✅ Visual Component Optimizations (December 7, 2024) - -#### TASK 0.1: Optimize MatrixDisplay Component ✅ -- Removed redundant `FBackgroundBuffer: TBitmap` field -- Eliminated unnecessary Skia → GDI bitmap conversion -- Now uses only `FBackgroundImage: ISkImage` for caching -- **Result:** ~50% memory reduction for background storage, 25-33% faster resize operations - -#### TASK 0.2: Implement Lazy State Loading for LED Component ✅ -- Added dirty flags: `FGrayedImageDirty`, `FOffImageDirty`, `FOnImageDirty` -- Created accessor methods: `GetGrayedImage()`, `GetOffImage()`, `GetOnImage()` -- Modified `InvalidateColors()` to only set dirty flags -- Images generated on-demand only when needed -- **Result:** ~66% memory reduction (only 1 of 3 states loaded at a time), 66% faster color property changes - -### ✅ Radio Calculator Consolidation (December 7, 2024) -- Converted all 36+ simple calculators to advanced multi-variant versions -- Consolidated Ford M + Ford V + Ford Regional → Ford Advanced -- Consolidated Becker4 + Becker5 → Becker Advanced -- Merged Toyota, Honda, VW regional variants into advanced versions -- **Result:** 40 Advanced Calculators with 200+ algorithm variants - -### ✅ Enhanced VIN Decoder (December 7, 2024) -- Added check digit validation (ISO 3779 standard) -- Implemented model year detection (most likely year based on current date) -- Added plant location database for major manufacturers (Ford, GM, Toyota, Honda, BMW, Mercedes, VW) -- Implemented VIN-based feature detection: - - Vehicle type detection (passenger car, truck, SUV, van, electric, hybrid, etc.) - - Engine type and displacement detection - - Body style identification - - Drive type detection (FWD, RWD, AWD, 4WD) - - Restraint system codes - - Commercial vehicle identification -- Enhanced TVINParseResult with comprehensive vehicle features - -### ✅ Adapter Support Enhancements (December 7, 2024) -- **J2534 Pass-Through Support:** - - SAE J2534 compliant pass-through interface - - Registry scanner for installed J2534 devices - - Support for multiple protocols (J1850, ISO9141, ISO14230, CAN, ISO15765) - - Direct vehicle communication without ELM327 - - Programming voltage control for ECU flashing - - Periodic message transmission - - Message filtering capabilities - -- **Chinese ELM327 Clone Detection:** - - Automatic detection of genuine vs clone adapters - - Version string analysis (v1.2, v1.3, v1.4, v1.5, fake v2.x) - - Command pattern testing - - Timing characteristic analysis - - Confidence level scoring (0-100%) - - Quirk identification for known issues - - Adapter-specific recommendations - - Support for OBDLink STN chips - -### ✅ Specialized Protocol Support (December 7, 2024) -- **Tachograph/Odometer Protocol:** - - EU Regulation 1360/2002 (Gen1) and 165/2014 (Gen2 Smart Tachograph) - - Korean E-Tachograph System support - - Odometer reading (total/trip distance) - - Trip counter reset with authentication - - Driver activity recording (28-day history) - - Workshop card authentication - - Odometer calibration (tire circumference, pulses/km) - - DDD file format download - - Events and faults reading - -- **ECU Security & Flashing Protocol:** - - Multi-level security access (diagnostic, programming, developer, manufacturer) - - Seed/key algorithms (multiple types) - - RSA/AES encryption support - - Flash memory operations (read, write, erase, verify) - - Firmware flashing with progress tracking - - Flash memory layout detection - - CRC/checksum validation - - Programming voltage control - - Firmware backup and restore - - ECU identification reading - - Hardware/software version detection - -### ✅ Additional Protocol Support (December 7, 2024) -- **KWP2000 (ISO 14230) Extended Support:** - - Full diagnostic service implementation with 20+ services - - Security access procedures (seed/key mechanism) - - ECU flashing support (Request Download, Transfer Data, Request Transfer Exit) - - Diagnostic session control (default, programming, extended) - - DTC management (read, clear, status) - - Tester present keep-alive - - ECU identification and data read/write - -- **UDS (ISO 14229) Protocol:** - - Complete Unified Diagnostic Services implementation - - 25+ diagnostic services (session control, security access, DTC management) - - Security and authentication (multiple security levels) - - Memory read/write operations - - Routine control for diagnostic procedures - - ECU flashing support with download/upload - - Response code handling with negative response support - - DTC information with multiple sub-functions - - Control DTC setting (enable/disable fault code logging) - -- **LIN (Local Interconnect Network) Protocol:** - - LIN 1.3, 2.0, 2.1, 2.2A protocol versions - - Protected identifier with parity calculation - - Classic and enhanced checksum types - - Unconditional, event-triggered, sporadic, and diagnostic frames - - Node addressing and configuration - - Diagnostic services (read/write by identifier, session control) - - Frame ID assignment and node configuration - - Support for 9600, 19200, 20000 bps baud rates - -- **FlexRay Protocol:** - - High-speed deterministic communication (2.5, 5, 10 Mbps) - - Dual-channel fault-tolerant operation (Channel A/B) - - Static and dynamic segment support - - Header and frame CRC calculation - - Cycle-based scheduling (0-63 cycles) - - Startup and sync frame support - - Configurable cluster parameters - - Diagnostic data read/write via FlexRay frames - - Payload up to 254 bytes per frame - -- **MOST (Media Oriented Systems Transport) Protocol:** - - MOST25 (25 Mbps), MOST50 (50 Mbps), MOST150 (150 Mbps) - - Control, asynchronous, streaming, and isochronous message types - - Function Block catalog (Audio, Video, Phone, Navigation, Diagnostics) - - Property get/set operations - - Diagnostic services integration - - Streaming channel management - - Network configuration - - Support for automotive infotainment systems - ---- - -## Contributing Guidelines - -1. **Before Starting:** - - Check if task is already assigned - - Review related code and documentation - - Ensure development environment is set up - -2. **During Development:** - - Follow existing code style and patterns - - Write clear commit messages - - Add XML documentation to public methods - - Test thoroughly - -3. **Before Submitting:** - - Run all existing tests - - Add new tests for new functionality - - Update documentation - - Request code review - ---- - -**Note:** This is a living document. Tasks may be added, modified, or reprioritized as the project evolves. +# Development Tasks + +> **Status:** This document has been retired. The canonical place for both +> shipped milestones and the open backlog is **[ROADMAP.md](ROADMAP.md)**. +> +> Tactical, in-flight blockers are tracked in **[../GAPS.md](../GAPS.md)** +> with `✅ FIXED` / `🔴 BLOCKED` markers. +> +> Forward-looking RFCs (proposals not yet on the roadmap) live in +> **[PROPOSALS.md](PROPOSALS.md)**. + +## Doc ownership at a glance + +| Doc | Purpose | Lifetime | +|---|---|---| +| [ROADMAP.md](ROADMAP.md) | Shipped milestones + future backlog (the source of truth) | Living — append per release | +| [../GAPS.md](../GAPS.md) | Current blockers and recently-resolved gaps | Short — items removed once they roll into a release | +| [PROPOSALS.md](PROPOSALS.md) | RFCs awaiting acceptance into the roadmap | Per-proposal until accepted or rejected | +| [OEM_EXTENSION_PLAN.md](OEM_EXTENSION_PLAN.md) | Phased plan for the OEM extension framework | Historical — Phases 1–7 shipped in v3.3–v3.13 | diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..d0734729 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,59 @@ +# Documentation Index + +Navigation hub for every doc in the repository, grouped by topic. + +## Getting started + +- [README.md](../README.md) — repository overview, install, basic usage. +- [QuickStart.md](../QuickStart.md) — wizard-driven Skia/OBD UI scaffolding. +- [examples/README.md](../examples/README.md) — catalog of 23 runnable examples. +- [TROUBLESHOOTING.md](TROUBLESHOOTING.md) — common installation and runtime issues. + +## Architecture & internals + +- [ARCHITECTURE.md](ARCHITECTURE.md) — module map and rendering pipeline. +- [PERFORMANCE.md](PERFORMANCE.md) — perf characteristics of the visual stack. +- [COMPONENT_AUTHORING.md](COMPONENT_AUTHORING.md) — canonical pattern for new visual components (read this before adding any). + +## Subsystem reference + +- [SUBSYSTEM_ADAPTERS.md](SUBSYSTEM_ADAPTERS.md) — `src/Adapters/` (ELM327, OBDLink, J2534). +- [SUBSYSTEM_SERVICES.md](SUBSYSTEM_SERVICES.md) — `src/Services/` (OBD-II 01–0A + OEM extension framework + ECU flashing). +- [SUBSYSTEM_FORMS.md](SUBSYSTEM_FORMS.md) — `src/Forms/` (`TOBDForm` base class). +- [SUBSYSTEM_WIZARDS.md](SUBSYSTEM_WIZARDS.md) — `src/Wizards/` (IDE project / form / data-module / mainform wizards). + +## Protocols & wire formats + +- [PROTOCOLS.md](PROTOCOLS.md) — wire-level reference for CAN, DoIP, J1939, Legacy, and UDS. +- [CATALOG_FORMAT.md](CATALOG_FORMAT.md) — JSON Schema v2 for OEM catalogs. +- [../catalogs/INDEX.md](../catalogs/INDEX.md) — list of shipped OEM catalogs. + +## OEM extension framework + +- [OEM_EXTENSION_PLAN.md](OEM_EXTENSION_PLAN.md) — historical design plan; Phases 1–7 shipped in v3.3–v3.13. +- [RADIO_CALCULATORS.md](RADIO_CALCULATORS.md) — head-unit unlock-code calculators (`src/RadioCode/`). + +## Planning & process + +- [ROADMAP.md](ROADMAP.md) — shipped milestones + future backlog (canonical). +- [EXTENSION_PLAN_v3.80.md](EXTENSION_PLAN_v3.80.md) — v3.80 milestone (shipped). +- [EXTENSION_PLAN_v3.81.md](EXTENSION_PLAN_v3.81.md) — v3.81 milestone (shipped). +- [EXTENSION_PLAN_v3.82.md](EXTENSION_PLAN_v3.82.md) — v3.82 milestone (shipped). +- [EXTENSION_PLAN_v3.83.md](EXTENSION_PLAN_v3.83.md) — v3.83 milestone (shipped). +- [EXTENSION_PLAN_v3.84.md](EXTENSION_PLAN_v3.84.md) — v3.84 milestone (active: style + JSON-catalog alignment). +- [PROPOSALS.md](PROPOSALS.md) — RFCs awaiting acceptance (with status table). +- [../GAPS.md](../GAPS.md) — current blockers and recently-resolved gaps. +- [DATA_GAPS.md](DATA_GAPS.md) — features shipped as framework + stubs because reference data is not publicly available. +- [TASKS.md](TASKS.md) — retired; redirects to ROADMAP / GAPS / PROPOSALS. +- [../CONTRIBUTING.md](../CONTRIBUTING.md) — contribution workflow. + +## Release history + +- [../CHANGELOG.md](../CHANGELOG.md) — changelog index. +- [../CHANGELOG/v3.md](../CHANGELOG/v3.md) — v3.0.0 → v3.79.0. +- [../CHANGELOG/v2.md](../CHANGELOG/v2.md) — v2.1.0 → v2.5.0. + +## Tooling + +- [../tools/coverage/README.md](../tools/coverage/README.md) — `delphi-code-coverage` harness. +- [../tests/README.md](../tests/README.md) — DUnitX test suite layout. diff --git a/examples/README.md b/examples/README.md index db2449e6..fef350c2 100644 --- a/examples/README.md +++ b/examples/README.md @@ -12,7 +12,7 @@ This directory contains full Delphi projects (DPR + PAS + DFM) that illustrate h ## Examples Overview -**Total Examples**: 14 (7 connection types + 1 advanced dashboard + 6 protocol-specific examples) +**Total Examples**: 23 (connection types, dashboards, protocol stacks, OEM/UDS/DoIP tooling, ECU flashing, replay, tachograph) ### 📦 minimal/ **Complexity**: Beginner @@ -57,9 +57,9 @@ This directory contains full Delphi projects (DPR + PAS + DFM) that illustrate h ### 📱 mobile_dashboard/ **Complexity**: Intermediate -**Connection**: Simulator (Proposal B in docs/PROPOSALS.md adds live transports) +**Connection**: Simulator (live transports tracked in docs/PROPOSALS.md) **What it demonstrates**: -- FMX dashboard exercising **every** v3.1 FMX component +- FMX dashboard exercising **every** shipped FMX component (LinearGauge, Tachometer, TrendGraph, DtcList, Terminal, Knob, SegmentedSwitch, LED) - Runs on Win32, Win64, macOS, iOS, Android — same source, same @@ -338,7 +338,7 @@ OBDConnection1.WifiHost := '192.168.0.10'; // Your adapter's IP OBDConnection1.WifiPort := 35000; // Common: 35000, 23 ``` -### DoIP/UDP Connection (New in v2.0) +### DoIP/UDP Connection ```delphi uses OBD.Connection.UDP, OBD.Protocol.DoIP; diff --git a/examples/advanced/README.md b/examples/advanced/README.md new file mode 100644 index 00000000..c2713b4d --- /dev/null +++ b/examples/advanced/README.md @@ -0,0 +1,14 @@ +# advanced + +Full-featured dashboard demonstrating the multi-component binding +pattern: live PIDs, freeze-frame capture, VIN decoding with check-digit +validation, DTC viewer, multiple gauges and indicators, and integration +with the radio-code calculators (`docs/RADIO_CALCULATORS.md`). + +- **Project:** `AdvancedDashboard.dpr` +- **Connection:** Configurable (Serial / Bluetooth / WiFi / FTDI) +- **Complexity:** Advanced + +Also exercises the modern protocol stacks (KWP2000, UDS, LIN, FlexRay, +MOST), J2534 pass-through, and Chinese ELM327 clone quirk handling. See +[../README.md](../README.md) for the full example catalog. diff --git a/examples/bluetooth/README.md b/examples/bluetooth/README.md new file mode 100644 index 00000000..de698edd --- /dev/null +++ b/examples/bluetooth/README.md @@ -0,0 +1,13 @@ +# bluetooth + +Wireless dashboard using a paired Bluetooth OBD-II adapter (e.g. ELM327 +clone). Demonstrates VCI status monitoring and connection-state +management for spotty wireless links. + +- **Project:** `BluetoothDashboard.dpr` +- **Connection:** Bluetooth (RFCOMM) +- **Complexity:** Intermediate + +Pair the adapter at the OS level first, then set the device name in the +form's connection component. See [../README.md](../README.md) for the +full example catalog. diff --git a/examples/diagsession_console/README.md b/examples/diagsession_console/README.md new file mode 100644 index 00000000..cad53f93 --- /dev/null +++ b/examples/diagsession_console/README.md @@ -0,0 +1,12 @@ +# diagsession_console + +Console walk-through of the diagnostic session lifecycle: open session, +issue requests, handle negative responses, run TesterPresent keep-alive, +close cleanly. No GUI — useful as a ground-truth reference when you're +debugging higher-level dashboards. + +- **Project:** `DiagSessionDemo.dpr` +- **Connection:** Configurable / simulated +- **Complexity:** Intermediate + +See [../README.md](../README.md) for the full example catalog. diff --git a/examples/ecuflashing/README.md b/examples/ecuflashing/README.md new file mode 100644 index 00000000..22984a9d --- /dev/null +++ b/examples/ecuflashing/README.md @@ -0,0 +1,15 @@ +# ecuflashing + +GUI counterpart to `ecuflashing_console/`. Demonstrates the multi-level +ECU security model (Diagnostic / Programming / Developer / Manufacturer), +Seed/Key + RSA + AES + HMAC algorithms, flash memory operations +(erase / write / read / verify), firmware flashing with progress and +rollback, and ECU identification (SW/HW version, serial, part number). + +- **Project:** `ECUFlashingExample.dpr` +- **Feature:** ECU security & firmware management +- **Complexity:** Expert + +For a deterministic, console-only walk-through of the same pipeline +against a fake ECU (no adapter required), use `../ecuflashing_console`. +See [../README.md](../README.md) for the full example catalog. diff --git a/examples/flexray/README.md b/examples/flexray/README.md new file mode 100644 index 00000000..ca8201b2 --- /dev/null +++ b/examples/flexray/README.md @@ -0,0 +1,14 @@ +# flexray + +FlexRay (ISO 17458) example covering the deterministic 2.5–10 Mbps +network used in safety-critical and X-by-wire systems: static and +dynamic segment communication, dual-channel A/B fault tolerance, cluster +configuration, cycle-based frame scheduling. + +- **Project:** `FlexRayExample.dpr` +- **Protocol:** FlexRay (ISO 17458) +- **Complexity:** Advanced + +Typical targets are ADAS, premium chassis controllers, and high-end +powertrain ECUs. See [../README.md](../README.md) for the full example +catalog. diff --git a/examples/ftdi/README.md b/examples/ftdi/README.md new file mode 100644 index 00000000..35029c97 --- /dev/null +++ b/examples/ftdi/README.md @@ -0,0 +1,12 @@ +# ftdi + +FTDI USB-serial adapter cable setup with guarded reconnect logic. +Targets diagnostic cables that expose an FTDI chip rather than a generic +serial port. + +- **Project:** `FTDIDashboard.dpr` +- **Connection:** FTDI USB +- **Complexity:** Intermediate + +Requires the FTDI D2XX driver to be installed. See +[../README.md](../README.md) for the full example catalog. diff --git a/examples/kwp2000/README.md b/examples/kwp2000/README.md new file mode 100644 index 00000000..541da2c5 --- /dev/null +++ b/examples/kwp2000/README.md @@ -0,0 +1,13 @@ +# kwp2000 + +Keyword Protocol 2000 (ISO 14230) example covering the full diagnostic +session lifecycle: default / programming / extended sessions, security +access (seed/key), DTC read/clear, ECU identification, and tester-present +keep-alive. + +- **Project:** `KWP2000Example.dpr` +- **Protocol:** KWP2000 (ISO 14230) +- **Complexity:** Advanced + +Best fit for European and Asian vehicles from the 1990s to the early +2010s. See [../README.md](../README.md) for the full example catalog. diff --git a/examples/lin/README.md b/examples/lin/README.md new file mode 100644 index 00000000..e934463c --- /dev/null +++ b/examples/lin/README.md @@ -0,0 +1,14 @@ +# lin + +Local Interconnect Network (ISO 17987) example covering LIN 1.3 / 2.0 / +2.1 / 2.2A: read/write data by identifier, frame ID assignment, node +configuration, protected identifier with parity, and classic + enhanced +checksums. + +- **Project:** `LINExample.dpr` +- **Protocol:** LIN (ISO 17987) +- **Complexity:** Intermediate + +Typical targets are body electronics, low-speed sensors, and convenience +sub-systems. See [../README.md](../README.md) for the full example +catalog. diff --git a/examples/minimal/README.md b/examples/minimal/README.md new file mode 100644 index 00000000..ca9af614 --- /dev/null +++ b/examples/minimal/README.md @@ -0,0 +1,12 @@ +# minimal + +Smallest possible OBD-II application. Demonstrates connecting to an +ELM327-class adapter over a serial port, reading stored DTCs, and +clearing the MIL. + +- **Project:** `MinimalDashboard.dpr` +- **Connection:** Serial (COM port, 38400 baud) +- **Complexity:** Beginner — read this first. + +Edit the COM port in `MinimalDashboard.pas` (or the form) before running. +See [../README.md](../README.md) for the full example catalog. diff --git a/examples/most/README.md b/examples/most/README.md new file mode 100644 index 00000000..b4fe4ef9 --- /dev/null +++ b/examples/most/README.md @@ -0,0 +1,14 @@ +# most + +MOST (Media Oriented Systems Transport) example covering MOST25/50/150 +infotainment networks: control messages, property get/set, function +blocks (Audio, Video, Phone, Navigation), and streaming-channel +management. + +- **Project:** `MOSTExample.dpr` +- **Protocol:** MOST +- **Complexity:** Advanced + +Typical targets are premium infotainment systems and audio/video +distribution buses. See [../README.md](../README.md) for the full +example catalog. diff --git a/examples/serial/README.md b/examples/serial/README.md new file mode 100644 index 00000000..6480b0a9 --- /dev/null +++ b/examples/serial/README.md @@ -0,0 +1,12 @@ +# serial + +Production-style serial transport setup with explicit baud rate, custom +header captions, and connection-status indicators. + +- **Project:** `SerialDashboard.dpr` +- **Connection:** Serial (RS-232) +- **Complexity:** Intermediate + +Set the COM port and baud rate (`38400`, `115200`, `9600`) in the form's +connection component before running. See [../README.md](../README.md) for +the full example catalog. diff --git a/examples/simple/README.md b/examples/simple/README.md new file mode 100644 index 00000000..f29f44c7 --- /dev/null +++ b/examples/simple/README.md @@ -0,0 +1,11 @@ +# simple + +Single-form dashboard with one circular gauge bound to live RPM. Smallest +"connected gauge" you can write — a useful starting point after +`minimal/`. + +- **Project:** `SimpleDashboard.dpr` +- **Connection:** Serial (COM port) +- **Complexity:** Beginner + +See [../README.md](../README.md) for the full example catalog. diff --git a/examples/tachograph/README.md b/examples/tachograph/README.md new file mode 100644 index 00000000..2cc13538 --- /dev/null +++ b/examples/tachograph/README.md @@ -0,0 +1,16 @@ +# tachograph + +Digital tachograph / odometer example covering EU Gen1/Gen2 Smart +Tachographs and 8 international standards (EU, Korea, Russia, China, +Brazil, Japan, Australia). Auto-detects manufacturer (VDO, Stoneridge, +Denso) and supports 20+ commercial vehicle makes. + +- **Project:** `TachographExample.dpr` +- **Feature:** Digital Tachograph +- **Complexity:** Advanced + +Demonstrates: odometer read (total/trip/speed), 28-day driver activity, +workshop-card authentication, calibration and trip reset, VIN/event/fault +read, and DDD download. + +See [../README.md](../README.md) for the full example catalog. diff --git a/examples/uds/README.md b/examples/uds/README.md new file mode 100644 index 00000000..86b22451 --- /dev/null +++ b/examples/uds/README.md @@ -0,0 +1,14 @@ +# uds + +Unified Diagnostic Services (ISO 14229) example covering session +management, multi-level security access (Diagnostic / Programming / +Developer / Manufacturer), DTC read with sub-functions, read/write data +by identifier, memory operations, routine control, and ECU reset. + +- **Project:** `UDSExample.dpr` +- **Protocol:** UDS (ISO 14229) +- **Complexity:** Advanced + +For the async, future-returning client (`OBD.OEM.UdsClient.Async`), see +the patterns exercised in `tests/Tests.OEM.UdsClient.Async`. See +[../README.md](../README.md) for the full example catalog. diff --git a/examples/wifi/README.md b/examples/wifi/README.md new file mode 100644 index 00000000..2ce41e39 --- /dev/null +++ b/examples/wifi/README.md @@ -0,0 +1,12 @@ +# wifi + +WiFi/TCP transport for network OBD-II adapters (typical default +`192.168.0.10:35000`). Reconnect-friendly setup suitable for unstable +networks. + +- **Project:** `WifiDashboard.dpr` +- **Connection:** WiFi (TCP) +- **Complexity:** Intermediate + +Set the adapter IP and port in the form's connection component before +running. See [../README.md](../README.md) for the full example catalog. diff --git a/src/Adapters/OBD.Adapter.ATCommands.pas b/src/Adapters/OBD.Adapter.ATCommands.pas index ecfaaa8f..e1e41248 100644 --- a/src/Adapters/OBD.Adapter.ATCommands.pas +++ b/src/Adapters/OBD.Adapter.ATCommands.pas @@ -923,6 +923,9 @@ function FormatATCommand(Command: ATCommand; Params: Array of const): string; implementation +//------------------------------------------------------------------------------ +// FORMAT ATCOMMAND +//------------------------------------------------------------------------------ function FormatATCommand(Command: ATCommand; Params: Array of const): string; var ExpectedParamCount, I, C: Integer; diff --git a/src/Adapters/OBD.Adapter.Capabilities.pas b/src/Adapters/OBD.Adapter.Capabilities.pas new file mode 100644 index 00000000..6cfffda6 --- /dev/null +++ b/src/Adapters/OBD.Adapter.Capabilities.pas @@ -0,0 +1,293 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.Adapter.Capabilities.pas +// CONTENTS : Adapter capability registry (CAN, CAN-FD, ISO-TP, DoIP, ...) +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows 7, 8/8.1, 10, 11 +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.Adapter.Capabilities; + +interface + +uses + System.SysUtils, System.SyncObjs, System.Generics.Collections; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + /// + /// One capability bit. Stable enum values; never renumber. + /// + TOBDAdapterCapability = ( + acCAN = 0, + acCANFD = 1, // CAN-FD (ISO 11898-1:2015) + acISOTP = 2, // ISO 15765-2 framing + acISOTPLargeFrame = 3, // CAN-FD-only 64-byte single-frame + acDoIP = 4, + acJ1939 = 5, + acKLine = 6, // ISO 9141-2 / KWP2000 + acVoltageMonitor = 7, + acSecureOnboard = 8, // SecOC awareness (per-OEM still required) + acJ2534 = 9, + acJ2534v2 = 10, // J2534-2 (2018) extensions + acBluetoothLE = 11, + acWiFi = 12, + acFTDI = 13 + ); + + TOBDAdapterCapabilitySet = set of TOBDAdapterCapability; + + TOBDAdapterCapabilities = record + AdapterKey: string; // e.g. 'elm327', 'obdlink_ex', 'doip_gateway' + /// + /// Display name. + /// + DisplayName: string; + /// + /// Cap set. + /// + CapSet: TOBDAdapterCapabilitySet; + /// + /// Maximum ISO-TP frame body length in bytes. 7 for CAN + /// classic single-frame; 62 for CAN-FD 64-byte single-frame. + /// + MaxIsoTpFrameBytes: Integer; + end; + +/// +/// Render a capability set as a comma-separated list, useful +/// for log lines and UI display. +/// +function CapabilitySetToString(const S: TOBDAdapterCapabilitySet): string; + +/// +/// Register or replace an adapter's capabilities. Idempotent +/// on the same key. +/// +procedure RegisterAdapterCapabilities(const Caps: TOBDAdapterCapabilities); + +/// +/// Look up an adapter's capabilities by key. Returns False if +/// the adapter hasn't registered. +/// +function FindAdapterCapabilities(const AdapterKey: string; + out Caps: TOBDAdapterCapabilities): Boolean; + +/// +/// Convenience: True iff the adapter is registered and the +/// capability is set. +/// +function AdapterSupports(const AdapterKey: string; + Capability: TOBDAdapterCapability): Boolean; + +/// +/// Pick the best ISO-TP single-frame size for the resolved +/// adapter. Returns 7 for CAN-classic (or unknown), 62 for CAN-FD +/// when acISOTPLargeFrame is set. +/// +function ResolveIsoTpFrameBytes(const AdapterKey: string): Integer; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +uses + System.TypInfo, System.Classes, System.JSON, + OBD.Catalog.Path; + +var + GLock: TCriticalSection; + GByKey: TDictionary; + +//------------------------------------------------------------------------------ +// CONSTANTS +//------------------------------------------------------------------------------ +const + CapNames: array[TOBDAdapterCapability] of string = ( + 'CAN', 'CAN-FD', 'ISO-TP', 'ISO-TP-LF', 'DoIP', 'J1939', 'K-Line', + 'Voltage', 'SecOC', 'J2534', 'J2534v2', 'BLE', 'WiFi', 'FTDI' + ); + +//------------------------------------------------------------------------------ +// CAPABILITY SET TO STRING +//------------------------------------------------------------------------------ +function CapabilitySetToString(const S: TOBDAdapterCapabilitySet): string; +var + C: TOBDAdapterCapability; + Buf: TStringList; +begin + // Create Buf + Buf := TStringList.Create; + try + Buf.Delimiter := ','; + Buf.StrictDelimiter := True; + // Loop over TOBDAdapterCapability + for C := Low(TOBDAdapterCapability) to High(TOBDAdapterCapability) do + if C in S then + Buf.Add(CapNames[C]); + Result := Buf.DelimitedText; + finally + // Free Buf + Buf.Free; + end; +end; + +//------------------------------------------------------------------------------ +// REGISTER ADAPTER CAPABILITIES +//------------------------------------------------------------------------------ +procedure RegisterAdapterCapabilities(const Caps: TOBDAdapterCapabilities); +var + Stored: TOBDAdapterCapabilities; + Key: string; +begin + if Caps.AdapterKey = '' then + raise Exception.Create('AdapterKey required'); + Stored := Caps; + Key := LowerCase(Caps.AdapterKey); + Stored.AdapterKey := Key; + GLock.Acquire; + try + GByKey.AddOrSetValue(Key, Stored); + finally + GLock.Release; + end; +end; + +//------------------------------------------------------------------------------ +// FIND ADAPTER CAPABILITIES +//------------------------------------------------------------------------------ +function FindAdapterCapabilities(const AdapterKey: string; + out Caps: TOBDAdapterCapabilities): Boolean; +begin + GLock.Acquire; + try + Result := GByKey.TryGetValue(LowerCase(AdapterKey), Caps); + finally + GLock.Release; + end; +end; + +//------------------------------------------------------------------------------ +// ADAPTER SUPPORTS +//------------------------------------------------------------------------------ +function AdapterSupports(const AdapterKey: string; + Capability: TOBDAdapterCapability): Boolean; +var + Caps: TOBDAdapterCapabilities; +begin + Result := FindAdapterCapabilities(AdapterKey, Caps) + and (Capability in Caps.CapSet); +end; + +//------------------------------------------------------------------------------ +// RESOLVE ISO TP FRAME BYTES +//------------------------------------------------------------------------------ +function ResolveIsoTpFrameBytes(const AdapterKey: string): Integer; +var + Caps: TOBDAdapterCapabilities; +begin + if FindAdapterCapabilities(AdapterKey, Caps) then + begin + if (acISOTPLargeFrame in Caps.CapSet) and (Caps.MaxIsoTpFrameBytes > 0) then + Exit(Caps.MaxIsoTpFrameBytes); + if acISOTPLargeFrame in Caps.CapSet then Exit(62); + end; + Result := 7; +end; + +//------------------------------------------------------------------------------ +// CAPABILITY FROM STRING +//------------------------------------------------------------------------------ +function CapabilityFromString(const S: string; out C: TOBDAdapterCapability): Boolean; +var + I: TOBDAdapterCapability; +begin + // Loop over TOBDAdapterCapability + for I := Low(TOBDAdapterCapability) to High(TOBDAdapterCapability) do + if SameText(S, CapNames[I]) or SameText(S, GetEnumName(TypeInfo(TOBDAdapterCapability), Ord(I))) then + begin + C := I; Exit(True); + end; + // Initialize result + Result := False; +end; + +//------------------------------------------------------------------------------ +// LOAD ADAPTER CATALOG +//------------------------------------------------------------------------------ +procedure LoadAdapterCatalog; +var + Path, Raw: string; + Doc: TJSONValue; + Arr, CapArr: TJSONArray; + Item, CapItem: TJSONValue; + Obj: TJSONObject; + R: TOBDAdapterCapabilities; + Cap: TOBDAdapterCapability; + Stream: TStringStream; +begin + // Resolve catalog path + Path := ResolveCatalogPath('adapter-capabilities.json'); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + R := Default(TOBDAdapterCapabilities); + R.AdapterKey := Obj.GetValue('adapter_key', ''); + if R.AdapterKey = '' then Continue; + R.DisplayName := Obj.GetValue('display_name', ''); + R.MaxIsoTpFrameBytes := Obj.GetValue('max_iso_tp_frame_bytes', 7); + CapArr := Obj.GetValue('capabilities'); + if CapArr <> nil then + // Loop over CapArr + for CapItem in CapArr do + if (CapItem is TJSONString) and CapabilityFromString(CapItem.Value, Cap) then + Include(R.CapSet, Cap); + RegisterAdapterCapabilities(R); + end; + finally + // Free the document + Doc.Free; + end; +end; + +initialization + GLock := TCriticalSection.Create; + GByKey := TDictionary.Create; + LoadAdapterCatalog; + +finalization + GByKey.Free; + GLock.Free; + +end. diff --git a/src/Adapters/OBD.Adapter.Enumerator.pas b/src/Adapters/OBD.Adapter.Enumerator.pas index 103361ab..971fbef9 100644 --- a/src/Adapters/OBD.Adapter.Enumerator.pas +++ b/src/Adapters/OBD.Adapter.Enumerator.pas @@ -340,8 +340,20 @@ implementation // SERIAL //------------------------------------------------------------------------------ function SetupDiGetClassDevs(const ClassGuid: PGUID; Enumerator: PChar; hwndParent: HWND; Flags: DWORD): HDEVINFO; stdcall; external SETUP_API_DLL name 'SetupDiGetClassDevsA'; + +//------------------------------------------------------------------------------ +// SETUP DI ENUM DEVICE INFO +//------------------------------------------------------------------------------ function SetupDiEnumDeviceInfo(DeviceInfoSet: HDEVINFO; MemberIndex: DWORD; var DeviceInfoData: SP_DEVINFO_DATA): BOOL; stdcall; external SETUP_API_DLL name 'SetupDiEnumDeviceInfo'; + +//------------------------------------------------------------------------------ +// SETUP DI GET DEVICE REGISTRY PROPERTY +//------------------------------------------------------------------------------ function SetupDiGetDeviceRegistryProperty(DeviceInfoSet: HDEVINFO; const DeviceInfoData: SP_DEVINFO_DATA; PropertyReg: DWORD; PropertyRegDataType: PDWORD; PropertyBuffer: PBYTE; PropertyBufferSize: DWORD; RequiredSize: PDWORD): BOOL; stdcall; external SETUP_API_DLL name 'SetupDiGetDeviceRegistryPropertyA'; + +//------------------------------------------------------------------------------ +// SETUP DI DESTROY DEVICE INFO LIST +//------------------------------------------------------------------------------ function SetupDiDestroyDeviceInfoList(DeviceInfoSet: HDEVINFO): BOOL; stdcall; external SETUP_API_DLL name 'SetupDiDestroyDeviceInfoList'; //------------------------------------------------------------------------------ @@ -878,12 +890,18 @@ function TOBDAdapterEnumerator.SnapshotSerialAdapters: TArray; begin // Provide a copy of the cached FTDI adapters Result := GetFTDIAdapters; end; +//------------------------------------------------------------------------------ +// SNAPSHOT BLUETOOTH ADAPTERS +//------------------------------------------------------------------------------ function TOBDAdapterEnumerator.SnapshotBluetoothAdapters: TArray; begin // Provide a copy of the cached Bluetooth adapters diff --git a/src/Adapters/OBD.Adapter.PassThrough.J2534v2.pas b/src/Adapters/OBD.Adapter.PassThrough.J2534v2.pas new file mode 100644 index 00000000..a2273764 --- /dev/null +++ b/src/Adapters/OBD.Adapter.PassThrough.J2534v2.pas @@ -0,0 +1,188 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.Adapter.PassThrough.J2534v2.pas +// CONTENTS : J2534-2 (2018) IOCTL constants and SCONFIG_LIST builder +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows 7, 8/8.1, 10, 11 +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.Adapter.PassThrough.J2534v2; + +interface + +uses + System.SysUtils; + +//------------------------------------------------------------------------------ +// CONSTANTS +//------------------------------------------------------------------------------ +const + // J2534-1 IOCTL ids retained for reference; J2534-2 adds many more. + IOCTL_GET_CONFIG = $00000001; + IOCTL_SET_CONFIG = $00000002; + IOCTL_READ_VBATT = $00000003; + IOCTL_FIVE_BAUD_INIT = $00000004; + IOCTL_FAST_INIT = $00000005; + IOCTL_CLEAR_TX_BUFFER = $00000007; + IOCTL_CLEAR_RX_BUFFER = $00000008; + IOCTL_CLEAR_PERIODIC_MSGS = $00000009; + IOCTL_CLEAR_MSG_FILTERS = $0000000A; + IOCTL_CLEAR_FUNCT_MSG_LOOKUP_TABLE = $0000000B; + + // J2534-2 SET_CONFIG parameter IDs (selection — see spec table 4.1). + CFG_DATA_RATE = $00000001; + CFG_LOOPBACK = $00000003; + CFG_NODE_ADDRESS = $00000004; + CFG_NETWORK_LINE = $00000005; + CFG_P1_MIN = $00000006; + CFG_P1_MAX = $00000007; + CFG_P2_MIN = $00000008; + CFG_P2_MAX = $00000009; + CFG_P3_MIN = $0000000A; + CFG_P3_MAX = $0000000B; + CFG_P4_MIN = $0000000C; + CFG_P4_MAX = $0000000D; + CFG_W0 = $0000000E; + CFG_W1 = $0000000F; + CFG_W2 = $00000010; + CFG_W3 = $00000011; + CFG_W4 = $00000012; + CFG_W5 = $00000013; + CFG_TIDLE = $00000014; + CFG_TINIL = $00000015; + CFG_TWUP = $00000016; + CFG_PARITY = $00000017; + CFG_BIT_SAMPLE_POINT = $00000018; + CFG_SYNC_JUMP_WIDTH = $00000019; + CFG_T1_MAX = $0000001C; + CFG_T2_MAX = $0000001D; + CFG_T3_MAX = $0000001E; + CFG_T4_MAX = $0000001F; + CFG_T5_MAX = $00000020; + CFG_ISO15765_BS = $00000021; + CFG_ISO15765_STMIN = $00000022; + CFG_DATA_BITS = $00000023; + CFG_FIVE_BAUD_MOD = $00000024; + CFG_BS_TX = $00000025; + CFG_STMIN_TX = $00000026; + CFG_T3_TIME_OUT = $00000027; + CFG_ISO15765_WFT_MAX = $00000028; + + // J2534-2 (2018) additions + CFG_CAN_MIXED_FORMAT = $0000800B; // 0=disable, 1=CAN-classic+CAN-FD on the same channel, 2=CAN-FD only + CFG_J1962_PINS = $0000800C; // pin assignment overrides + CFG_CAN_FD_DATA_RATE = $00008011; // bps for the CAN-FD data phase + CFG_BIT_SAMPLE_POINT_FD = $00008012; + CFG_SYNC_JUMP_WIDTH_FD = $00008013; + CFG_TX_DELAY_COMP = $00008014; + CFG_ISO15765_FD_BS = $00008021; + CFG_ISO15765_FD_STMIN = $00008022; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDPassThroughJ2534v2 = class(Exception); + + /// + /// One (parameter, value) entry as understood by SET_CONFIG. + /// + TJ2534ConfigEntry = record + /// + /// Parameter. + /// + Parameter: Cardinal; + /// + /// Value. + /// + Value: Cardinal; + end; + + /// + /// Builder for the SCONFIG_LIST struct passed into + /// IOCTL_SET_CONFIG. Use Add(...) for each parameter; ToBytes + /// renders the buffer in the layout the J2534 spec defines: + /// uint32 NumOfParams + /// for each: uint32 Parameter, uint32 Value + /// + TJ2534ConfigList = class + private + FEntries: TArray; + public + /// + /// Add. + /// + procedure Add(Parameter, Value: Cardinal); + /// + /// Count. + /// + function Count: Integer; + /// + /// To bytes. + /// + function ToBytes: TBytes; + end; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// ADD +//------------------------------------------------------------------------------ +procedure TJ2534ConfigList.Add(Parameter, Value: Cardinal); +var + E: TJ2534ConfigEntry; +begin + E.Parameter := Parameter; + E.Value := Value; + FEntries := FEntries + [E]; +end; + +//------------------------------------------------------------------------------ +// COUNT +//------------------------------------------------------------------------------ +function TJ2534ConfigList.Count: Integer; +begin + Result := Length(FEntries); +end; + +//------------------------------------------------------------------------------ +// TO BYTES +//------------------------------------------------------------------------------ +function TJ2534ConfigList.ToBytes: TBytes; +var + Buf: TBytes; + Cursor, I: Integer; + N: Cardinal; +begin + N := Cardinal(Length(FEntries)); + // Allocate Buf + SetLength(Buf, 4 + Length(FEntries) * 8); + // Little-endian everywhere — matches Windows DLL layout the + // J2534 vendor binaries use. + Buf[0] := Byte(N); + Buf[1] := Byte(N shr 8); + Buf[2] := Byte(N shr 16); + Buf[3] := Byte(N shr 24); + Cursor := 4; + for I := 0 to High(FEntries) do + begin + Buf[Cursor] := Byte(FEntries[I].Parameter); + Buf[Cursor + 1] := Byte(FEntries[I].Parameter shr 8); + Buf[Cursor + 2] := Byte(FEntries[I].Parameter shr 16); + Buf[Cursor + 3] := Byte(FEntries[I].Parameter shr 24); + Buf[Cursor + 4] := Byte(FEntries[I].Value); + Buf[Cursor + 5] := Byte(FEntries[I].Value shr 8); + Buf[Cursor + 6] := Byte(FEntries[I].Value shr 16); + Buf[Cursor + 7] := Byte(FEntries[I].Value shr 24); + Inc(Cursor, 8); + end; + Result := Buf; +end; + +end. diff --git a/src/Adapters/OBD.Adapter.STCommands.pas b/src/Adapters/OBD.Adapter.STCommands.pas index 65b79184..bd3a91b0 100644 --- a/src/Adapters/OBD.Adapter.STCommands.pas +++ b/src/Adapters/OBD.Adapter.STCommands.pas @@ -845,6 +845,9 @@ function FormatSTCommand(Command: STCommand; Params: Array of const): string; implementation +//------------------------------------------------------------------------------ +// FORMAT STCOMMAND +//------------------------------------------------------------------------------ function FormatSTCommand(Command: STCommand; Params: Array of const): string; var ExpectedParamCount, I, C: Integer; diff --git a/src/Components/OBD.CircularGauge.pas b/src/Components/OBD.CircularGauge.pas index 1b0cbf39..f0eabe89 100755 --- a/src/Components/OBD.CircularGauge.pas +++ b/src/Components/OBD.CircularGauge.pas @@ -1343,6 +1343,9 @@ procedure TOBDCircularGaugeTick.SetDivider(Value: Single); end; end; +//------------------------------------------------------------------------------ +// SET OFFSET +//------------------------------------------------------------------------------ procedure TOBDCircularGaugeTick.SetOffset(Value: Single); begin if (FOffset <> Value) and (Value >= 0) then diff --git a/src/Components/OBD.Component.Editors.pas b/src/Components/OBD.Component.Editors.pas index f4939561..64bb439e 100644 --- a/src/Components/OBD.Component.Editors.pas +++ b/src/Components/OBD.Component.Editors.pas @@ -88,6 +88,9 @@ function TOBDConnectionComponentProperty.GetAttributes: TPropertyAttributes; Result := inherited GetAttributes + [paValueList, paSortList, paMultiSelect]; end; +//------------------------------------------------------------------------------ +// GET VALUES +//------------------------------------------------------------------------------ procedure TOBDConnectionComponentProperty.GetValues(Proc: TGetStrProc); begin if not Assigned(Designer) then @@ -103,6 +106,9 @@ function TOBDProtocolComponentProperty.GetAttributes: TPropertyAttributes; Result := inherited GetAttributes + [paValueList, paSortList, paMultiSelect]; end; +//------------------------------------------------------------------------------ +// GET VALUES +//------------------------------------------------------------------------------ procedure TOBDProtocolComponentProperty.GetValues(Proc: TGetStrProc); begin if not Assigned(Designer) then @@ -118,6 +124,9 @@ function TOBDGaugeComponentProperty.GetAttributes: TPropertyAttributes; Result := inherited GetAttributes + [paValueList, paSortList, paMultiSelect]; end; +//------------------------------------------------------------------------------ +// GET VALUES +//------------------------------------------------------------------------------ procedure TOBDGaugeComponentProperty.GetValues(Proc: TGetStrProc); begin if not Assigned(Designer) then diff --git a/src/Components/OBD.DtcList.FMX.pas b/src/Components/OBD.DtcList.FMX.pas index e2509242..93990581 100644 --- a/src/Components/OBD.DtcList.FMX.pas +++ b/src/Components/OBD.DtcList.FMX.pas @@ -131,6 +131,9 @@ TOBDDtcListFMX = class(TSkPaintBox) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDDtcListFMX.Create(AOwner: TComponent); begin inherited Create(AOwner); @@ -156,12 +159,22 @@ constructor TOBDDtcListFMX.Create(AOwner: TComponent); OnDraw := HandleDraw; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDDtcListFMX.Destroy; -begin FItems.Free; inherited; end; +begin + FItems.Free; + inherited; +end; +//------------------------------------------------------------------------------ +// ADD ITEM +//------------------------------------------------------------------------------ function TOBDDtcListFMX.AddItem(const Code, Description: string; Severity: TOBDDtcSeverity; Status: TOBDDtcStatus; Tag: NativeInt): Integer; -var Item: TOBDDtcItemFMX; +var + Item: TOBDDtcItemFMX; begin Item.Code := Code; Item.Description := Description; Item.Severity := Severity; Item.Status := Status; Item.Tag := Tag; @@ -169,6 +182,9 @@ function TOBDDtcListFMX.AddItem(const Code, Description: string; Redraw; end; +//------------------------------------------------------------------------------ +// UPDATE ITEM +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.UpdateItem(Index: Integer; const Item: TOBDDtcItemFMX); begin if (Index < 0) or (Index >= FItems.Count) then Exit; @@ -176,6 +192,9 @@ procedure TOBDDtcListFMX.UpdateItem(Index: Integer; const Item: TOBDDtcItemFMX); Redraw; end; +//------------------------------------------------------------------------------ +// REMOVE ITEM +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.RemoveItem(Index: Integer); begin if (Index < 0) or (Index >= FItems.Count) then Exit; @@ -185,43 +204,79 @@ procedure TOBDDtcListFMX.RemoveItem(Index: Integer); Redraw; end; +//------------------------------------------------------------------------------ +// CLEAR ITEMS +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.ClearItems; begin FItems.Clear; FSelectedIndex := -1; FScrollY := 0; Redraw; end; +//------------------------------------------------------------------------------ +// GET ITEM +//------------------------------------------------------------------------------ function TOBDDtcListFMX.GetItem(Index: Integer): TOBDDtcItemFMX; -begin Result := FItems[Index]; end; +begin + Result := FItems[Index]; +end; +//------------------------------------------------------------------------------ +// GET ITEM COUNT +//------------------------------------------------------------------------------ function TOBDDtcListFMX.GetItemCount: Integer; -begin Result := FItems.Count; end; +begin + Result := FItems.Count; +end; +//------------------------------------------------------------------------------ +// LIST AREA TOP +//------------------------------------------------------------------------------ function TOBDDtcListFMX.ListAreaTop: Integer; -begin if FShowHeader then Result := FHeaderHeight else Result := 0; end; +begin + if FShowHeader then Result := FHeaderHeight else Result := 0; +end; +//------------------------------------------------------------------------------ +// LIST AREA HEIGHT +//------------------------------------------------------------------------------ function TOBDDtcListFMX.ListAreaHeight: Integer; begin Result := Trunc(Height) - ListAreaTop; if Result < 0 then Result := 0; end; +//------------------------------------------------------------------------------ +// CONTENT HEIGHT +//------------------------------------------------------------------------------ function TOBDDtcListFMX.ContentHeight: Integer; -begin Result := FItems.Count * FRowHeight; end; +begin + Result := FItems.Count * FRowHeight; +end; +//------------------------------------------------------------------------------ +// MAX SCROLL +//------------------------------------------------------------------------------ function TOBDDtcListFMX.MaxScroll: Integer; begin Result := ContentHeight - ListAreaHeight; if Result < 0 then Result := 0; end; +//------------------------------------------------------------------------------ +// CLAMP SCROLL +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.ClampScroll; begin if FScrollY < 0 then FScrollY := 0; if FScrollY > MaxScroll then FScrollY := MaxScroll; end; +//------------------------------------------------------------------------------ +// INDEX AT Y +//------------------------------------------------------------------------------ function TOBDDtcListFMX.IndexAtY(Y: Single): Integer; -var RelY, Idx: Integer; +var + RelY, Idx: Integer; begin Result := -1; if Y < ListAreaTop then Exit; @@ -230,8 +285,12 @@ function TOBDDtcListFMX.IndexAtY(Y: Single): Integer; if (Idx >= 0) and (Idx < FItems.Count) then Result := Idx; end; +//------------------------------------------------------------------------------ +// ENSURE VISIBLE +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.EnsureVisible(Index: Integer); -var RowTop, RowBottom: Integer; +var + RowTop, RowBottom: Integer; begin if (Index < 0) or (Index >= FItems.Count) then Exit; RowTop := Index * FRowHeight; @@ -243,53 +302,176 @@ procedure TOBDDtcListFMX.EnsureVisible(Index: Integer); Redraw; end; +//------------------------------------------------------------------------------ +// SET ROW HEIGHT +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetRowHeight(const AValue: Integer); begin if (AValue >= 12) and (FRowHeight <> AValue) then - begin FRowHeight := AValue; ClampScroll; Redraw; end; + begin + FRowHeight := AValue; + ClampScroll; + Redraw; + end; end; + +//------------------------------------------------------------------------------ +// SET HEADER HEIGHT +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetHeaderHeight(const AValue: Integer); begin if (AValue >= 0) and (FHeaderHeight <> AValue) then - begin FHeaderHeight := AValue; ClampScroll; Redraw; end; + begin + FHeaderHeight := AValue; + ClampScroll; + Redraw; + end; end; + +//------------------------------------------------------------------------------ +// SET SELECTED INDEX +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetSelectedIndex(const AValue: Integer); -begin if FSelectedIndex <> AValue then begin FSelectedIndex := AValue; Redraw; end; end; +begin + if FSelectedIndex <> AValue then begin FSelectedIndex := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetBackgroundColor(const AValue: TAlphaColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Redraw; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET HEADER BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetHeaderBackgroundColor(const AValue: TAlphaColor); -begin if FHeaderBackgroundColor <> AValue then begin FHeaderBackgroundColor := AValue; Redraw; end; end; +begin + if FHeaderBackgroundColor <> AValue then begin FHeaderBackgroundColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET ROW ALT COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetRowAltColor(const AValue: TAlphaColor); -begin if FRowAltColor <> AValue then begin FRowAltColor := AValue; Redraw; end; end; +begin + if FRowAltColor <> AValue then begin FRowAltColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetBorderColor(const AValue: TAlphaColor); -begin if FBorderColor <> AValue then begin FBorderColor := AValue; Redraw; end; end; +begin + if FBorderColor <> AValue then begin FBorderColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetTextColor(const AValue: TAlphaColor); -begin if FTextColor <> AValue then begin FTextColor := AValue; Redraw; end; end; +begin + if FTextColor <> AValue then begin FTextColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SELECTION COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetSelectionColor(const AValue: TAlphaColor); -begin if FSelectionColor <> AValue then begin FSelectionColor := AValue; Redraw; end; end; +begin + if FSelectionColor <> AValue then begin FSelectionColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SEVERITY INFO COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetSeverityInfoColor(const AValue: TAlphaColor); -begin if FSeverityInfoColor <> AValue then begin FSeverityInfoColor := AValue; Redraw; end; end; +begin + if FSeverityInfoColor <> AValue then begin FSeverityInfoColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SEVERITY WARNING COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetSeverityWarningColor(const AValue: TAlphaColor); -begin if FSeverityWarningColor <> AValue then begin FSeverityWarningColor := AValue; Redraw; end; end; +begin + if FSeverityWarningColor <> AValue then begin FSeverityWarningColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SEVERITY CRITICAL COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetSeverityCriticalColor(const AValue: TAlphaColor); -begin if FSeverityCriticalColor <> AValue then begin FSeverityCriticalColor := AValue; Redraw; end; end; +begin + if FSeverityCriticalColor <> AValue then begin FSeverityCriticalColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SHOW HEADER +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetShowHeader(const AValue: Boolean); -begin if FShowHeader <> AValue then begin FShowHeader := AValue; ClampScroll; Redraw; end; end; +begin + if FShowHeader <> AValue then begin FShowHeader := AValue; + ClampScroll; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SHOW ALTERNATE ROWS +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.SetShowAlternateRows(const AValue: Boolean); -begin if FShowAlternateRows <> AValue then begin FShowAlternateRows := AValue; Redraw; end; end; +begin + if FShowAlternateRows <> AValue then begin FShowAlternateRows := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// MOUSE DOWN +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); -var Idx: Integer; +var + Idx: Integer; begin inherited; if not IsFocused then SetFocus; if Button <> TMouseButton.mbLeft then Exit; Idx := IndexAtY(Y); - if Idx <> FSelectedIndex then begin FSelectedIndex := Idx; Redraw; end; + if Idx <> FSelectedIndex then + begin + FSelectedIndex := Idx; + Redraw; + end; if (Idx >= 0) and Assigned(FOnDtcClick) then FOnDtcClick(Self, Idx); end; +//------------------------------------------------------------------------------ +// DBL CLICK +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.DblClick; begin inherited; @@ -297,8 +479,12 @@ procedure TOBDDtcListFMX.DblClick; FOnDtcDoubleClick(Self, FSelectedIndex); end; +//------------------------------------------------------------------------------ +// MOUSE WHEEL +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.MouseWheel(Shift: TShiftState; WheelDelta: Integer; - var Handled: Boolean); + var + Handled: Boolean); const WHEEL_LINE_DELTA = 120; begin @@ -311,6 +497,9 @@ procedure TOBDDtcListFMX.MouseWheel(Shift: TShiftState; WheelDelta: Integer; Handled := True; end; +//------------------------------------------------------------------------------ +// HANDLE DRAW +//------------------------------------------------------------------------------ procedure TOBDDtcListFMX.HandleDraw(ASender: TObject; const ACanvas: ISkCanvas; const ADest: TRectF; const AOpacity: Single); var diff --git a/src/Components/OBD.DtcList.pas b/src/Components/OBD.DtcList.pas index e138960a..dbf8027b 100644 --- a/src/Components/OBD.DtcList.pas +++ b/src/Components/OBD.DtcList.pas @@ -129,19 +129,29 @@ TOBDDtcList = class(TOBDCustomControl) constructor Create(AOwner: TComponent); override; destructor Destroy; override; - /// Append a DTC and repaint. Returns the new index. + /// + /// Append a DTC and repaint. Returns the new index. + /// function AddItem(const Code, Description: string; Severity: TOBDDtcSeverity = dsWarning; Status: TOBDDtcStatus = dsActive; Tag: NativeInt = 0): Integer; - /// Replace the row at Index. + /// + /// Replace the row at Index. + /// procedure UpdateItem(Index: Integer; const Item: TOBDDtcItem); - /// Remove a row, shifting later rows up. + /// + /// Remove a row, shifting later rows up. + /// procedure RemoveItem(Index: Integer); - /// Drop every row. + /// + /// Drop every row. + /// procedure ClearItems; - /// Scroll the given index into view. + /// + /// Scroll the given index into view. + /// procedure EnsureVisible(Index: Integer); property ItemCount: Integer read GetItemCount; @@ -196,6 +206,9 @@ constructor TOBDDtcList.Create(AOwner: TComponent); Height := 240; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDDtcList.Destroy; begin FItems.Free; @@ -219,6 +232,9 @@ function TOBDDtcList.AddItem(const Code, Description: string; Invalidate; end; +//------------------------------------------------------------------------------ +// UPDATE ITEM +//------------------------------------------------------------------------------ procedure TOBDDtcList.UpdateItem(Index: Integer; const Item: TOBDDtcItem); begin if (Index < 0) or (Index >= FItems.Count) then Exit; @@ -226,6 +242,9 @@ procedure TOBDDtcList.UpdateItem(Index: Integer; const Item: TOBDDtcItem); Invalidate; end; +//------------------------------------------------------------------------------ +// REMOVE ITEM +//------------------------------------------------------------------------------ procedure TOBDDtcList.RemoveItem(Index: Integer); begin if (Index < 0) or (Index >= FItems.Count) then Exit; @@ -235,6 +254,9 @@ procedure TOBDDtcList.RemoveItem(Index: Integer); Invalidate; end; +//------------------------------------------------------------------------------ +// CLEAR ITEMS +//------------------------------------------------------------------------------ procedure TOBDDtcList.ClearItems; begin FItems.Clear; @@ -251,11 +273,17 @@ function TOBDDtcList.GetItem(Index: Integer): TOBDDtcItem; Result := FItems[Index]; end; +//------------------------------------------------------------------------------ +// GET ITEM COUNT +//------------------------------------------------------------------------------ function TOBDDtcList.GetItemCount: Integer; begin Result := FItems.Count; end; +//------------------------------------------------------------------------------ +// LIST AREA TOP +//------------------------------------------------------------------------------ function TOBDDtcList.ListAreaTop: Integer; begin if FShowHeader then @@ -264,29 +292,44 @@ function TOBDDtcList.ListAreaTop: Integer; Result := 0; end; +//------------------------------------------------------------------------------ +// LIST AREA HEIGHT +//------------------------------------------------------------------------------ function TOBDDtcList.ListAreaHeight: Integer; begin Result := Height - ListAreaTop; if Result < 0 then Result := 0; end; +//------------------------------------------------------------------------------ +// CONTENT HEIGHT +//------------------------------------------------------------------------------ function TOBDDtcList.ContentHeight: Integer; begin Result := FItems.Count * FRowHeight; end; +//------------------------------------------------------------------------------ +// MAX SCROLL +//------------------------------------------------------------------------------ function TOBDDtcList.MaxScroll: Integer; begin Result := ContentHeight - ListAreaHeight; if Result < 0 then Result := 0; end; +//------------------------------------------------------------------------------ +// CLAMP SCROLL +//------------------------------------------------------------------------------ procedure TOBDDtcList.ClampScroll; begin if FScrollY < 0 then FScrollY := 0; if FScrollY > MaxScroll then FScrollY := MaxScroll; end; +//------------------------------------------------------------------------------ +// INDEX AT Y +//------------------------------------------------------------------------------ function TOBDDtcList.IndexAtY(Y: Integer): Integer; var RelY, Idx: Integer; @@ -299,6 +342,9 @@ function TOBDDtcList.IndexAtY(Y: Integer): Integer; Result := Idx; end; +//------------------------------------------------------------------------------ +// ENSURE VISIBLE +//------------------------------------------------------------------------------ procedure TOBDDtcList.EnsureVisible(Index: Integer); var RowTop, RowBottom: Integer; @@ -327,6 +373,9 @@ procedure TOBDDtcList.SetRowHeight(const AValue: Integer); end; end; +//------------------------------------------------------------------------------ +// SET HEADER HEIGHT +//------------------------------------------------------------------------------ procedure TOBDDtcList.SetHeaderHeight(const AValue: Integer); begin if (AValue >= 0) and (FHeaderHeight <> AValue) then @@ -337,6 +386,9 @@ procedure TOBDDtcList.SetHeaderHeight(const AValue: Integer); end; end; +//------------------------------------------------------------------------------ +// SET SELECTED INDEX +//------------------------------------------------------------------------------ procedure TOBDDtcList.SetSelectedIndex(const AValue: Integer); begin if FSelectedIndex <> AValue then @@ -346,38 +398,116 @@ procedure TOBDDtcList.SetSelectedIndex(const AValue: Integer); end; end; +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcList.SetBackgroundColor(const AValue: TColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Invalidate; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET HEADER BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcList.SetHeaderBackgroundColor(const AValue: TColor); -begin if FHeaderBackgroundColor <> AValue then begin FHeaderBackgroundColor := AValue; Invalidate; end; end; +begin + if FHeaderBackgroundColor <> AValue then begin FHeaderBackgroundColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET ROW ALT COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcList.SetRowAltColor(const AValue: TColor); -begin if FRowAltColor <> AValue then begin FRowAltColor := AValue; Invalidate; end; end; +begin + if FRowAltColor <> AValue then begin FRowAltColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcList.SetBorderColor(const AValue: TColor); -begin if FBorderColor <> AValue then begin FBorderColor := AValue; Invalidate; end; end; +begin + if FBorderColor <> AValue then begin FBorderColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcList.SetTextColor(const AValue: TColor); -begin if FTextColor <> AValue then begin FTextColor := AValue; Invalidate; end; end; +begin + if FTextColor <> AValue then begin FTextColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SELECTION COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcList.SetSelectionColor(const AValue: TColor); -begin if FSelectionColor <> AValue then begin FSelectionColor := AValue; Invalidate; end; end; +begin + if FSelectionColor <> AValue then begin FSelectionColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SEVERITY INFO COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcList.SetSeverityInfoColor(const AValue: TColor); -begin if FSeverityInfoColor <> AValue then begin FSeverityInfoColor := AValue; Invalidate; end; end; +begin + if FSeverityInfoColor <> AValue then begin FSeverityInfoColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SEVERITY WARNING COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcList.SetSeverityWarningColor(const AValue: TColor); -begin if FSeverityWarningColor <> AValue then begin FSeverityWarningColor := AValue; Invalidate; end; end; +begin + if FSeverityWarningColor <> AValue then begin FSeverityWarningColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SEVERITY CRITICAL COLOR +//------------------------------------------------------------------------------ procedure TOBDDtcList.SetSeverityCriticalColor(const AValue: TColor); -begin if FSeverityCriticalColor <> AValue then begin FSeverityCriticalColor := AValue; Invalidate; end; end; +begin + if FSeverityCriticalColor <> AValue then begin FSeverityCriticalColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SHOW HEADER +//------------------------------------------------------------------------------ procedure TOBDDtcList.SetShowHeader(const AValue: Boolean); -begin if FShowHeader <> AValue then begin FShowHeader := AValue; ClampScroll; Invalidate; end; end; +begin + if FShowHeader <> AValue then begin FShowHeader := AValue; + ClampScroll; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SHOW ALTERNATE ROWS +//------------------------------------------------------------------------------ procedure TOBDDtcList.SetShowAlternateRows(const AValue: Boolean); -begin if FShowAlternateRows <> AValue then begin FShowAlternateRows := AValue; Invalidate; end; end; +begin + if FShowAlternateRows <> AValue then begin FShowAlternateRows := AValue; + Invalidate; + end; +end; //------------------------------------------------------------------------------ // MOUSE @@ -399,6 +529,9 @@ procedure TOBDDtcList.MouseDown(Button: TMouseButton; Shift: TShiftState; if (Idx >= 0) and Assigned(FOnDtcClick) then FOnDtcClick(Self, Idx); end; +//------------------------------------------------------------------------------ +// DBL CLICK +//------------------------------------------------------------------------------ procedure TOBDDtcList.DblClick; begin inherited; @@ -406,6 +539,9 @@ procedure TOBDDtcList.DblClick; FOnDtcDoubleClick(Self, FSelectedIndex); end; +//------------------------------------------------------------------------------ +// DO MOUSE WHEEL +//------------------------------------------------------------------------------ function TOBDDtcList.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; const diff --git a/src/Components/OBD.Knob.FMX.pas b/src/Components/OBD.Knob.FMX.pas index 3e3c294c..ab06806d 100644 --- a/src/Components/OBD.Knob.FMX.pas +++ b/src/Components/OBD.Knob.FMX.pas @@ -96,6 +96,9 @@ TOBDKnobFMX = class(TSkPaintBox) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDKnobFMX.Create(AOwner: TComponent); begin inherited Create(AOwner); @@ -119,6 +122,9 @@ constructor TOBDKnobFMX.Create(AOwner: TComponent); OnDraw := HandleDraw; end; +//------------------------------------------------------------------------------ +// NORMALIZE ANGLE +//------------------------------------------------------------------------------ function TOBDKnobFMX.NormalizeAngle(A: Single): Single; begin while A < 0 do A := A + 360; @@ -126,12 +132,18 @@ function TOBDKnobFMX.NormalizeAngle(A: Single): Single; Result := A; end; +//------------------------------------------------------------------------------ +// SNAP TO STEP +//------------------------------------------------------------------------------ function TOBDKnobFMX.SnapToStep(const AValue: Single): Single; begin if FStep <= 0 then Exit(AValue); Result := Round((AValue - FMin) / FStep) * FStep + FMin; end; +//------------------------------------------------------------------------------ +// POINT TO VALUE +//------------------------------------------------------------------------------ function TOBDKnobFMX.PointToValue(const X, Y: Single): Single; var Cx, Cy, Dx, Dy: Single; @@ -150,6 +162,9 @@ function TOBDKnobFMX.PointToValue(const X, Y: Single): Single; Result := FMin + (RelAngle / FSweepAngle) * (FMax - FMin); end; +//------------------------------------------------------------------------------ +// SET MIN +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetMin(const AValue: Single); begin if FMin <> AValue then @@ -160,6 +175,9 @@ procedure TOBDKnobFMX.SetMin(const AValue: Single); end; end; +//------------------------------------------------------------------------------ +// SET MAX +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetMax(const AValue: Single); begin if FMax <> AValue then @@ -170,8 +188,12 @@ procedure TOBDKnobFMX.SetMax(const AValue: Single); end; end; +//------------------------------------------------------------------------------ +// SET VALUE +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetValue(const AValue: Single); -var Clamped: Single; +var + Clamped: Single; begin Clamped := SnapToStep(AValue); if Clamped < FMin then Clamped := FMin; @@ -182,29 +204,119 @@ procedure TOBDKnobFMX.SetValue(const AValue: Single); if Assigned(FOnChange) then FOnChange(Self, FValue); end; +//------------------------------------------------------------------------------ +// SET STEP +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetStep(const AValue: Single); -begin if (AValue > 0) and (FStep <> AValue) then begin FStep := AValue; Redraw; end; end; +begin + if (AValue > 0) and (FStep <> AValue) then begin FStep := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET START ANGLE +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetStartAngle(const AValue: Single); -begin if FStartAngle <> AValue then begin FStartAngle := AValue; Redraw; end; end; +begin + if FStartAngle <> AValue then begin FStartAngle := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SWEEP ANGLE +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetSweepAngle(const AValue: Single); -begin if FSweepAngle <> AValue then begin FSweepAngle := AValue; Redraw; end; end; +begin + if FSweepAngle <> AValue then begin FSweepAngle := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetBackgroundColor(const AValue: TAlphaColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Redraw; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BODY COLOR +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetBodyColor(const AValue: TAlphaColor); -begin if FBodyColor <> AValue then begin FBodyColor := AValue; Redraw; end; end; +begin + if FBodyColor <> AValue then begin FBodyColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET RING COLOR +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetRingColor(const AValue: TAlphaColor); -begin if FRingColor <> AValue then begin FRingColor := AValue; Redraw; end; end; +begin + if FRingColor <> AValue then begin FRingColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET ACTIVE RING COLOR +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetActiveRingColor(const AValue: TAlphaColor); -begin if FActiveRingColor <> AValue then begin FActiveRingColor := AValue; Redraw; end; end; +begin + if FActiveRingColor <> AValue then begin FActiveRingColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET INDICATOR COLOR +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetIndicatorColor(const AValue: TAlphaColor); -begin if FIndicatorColor <> AValue then begin FIndicatorColor := AValue; Redraw; end; end; +begin + if FIndicatorColor <> AValue then begin FIndicatorColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetTextColor(const AValue: TAlphaColor); -begin if FTextColor <> AValue then begin FTextColor := AValue; Redraw; end; end; +begin + if FTextColor <> AValue then begin FTextColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET CAPTION +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetCaption(const AValue: string); -begin if FCaption <> AValue then begin FCaption := AValue; Redraw; end; end; +begin + if FCaption <> AValue then begin FCaption := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SHOW VALUE +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.SetShowValue(const AValue: Boolean); -begin if FShowValue <> AValue then begin FShowValue := AValue; Redraw; end; end; +begin + if FShowValue <> AValue then begin FShowValue := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// MOUSE DOWN +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); begin @@ -218,12 +330,18 @@ procedure TOBDKnobFMX.MouseDown(Button: TMouseButton; Shift: TShiftState; end; end; +//------------------------------------------------------------------------------ +// MOUSE MOVE +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.MouseMove(Shift: TShiftState; X, Y: Single); begin inherited; if FDragging then SetValue(PointToValue(X, Y)); end; +//------------------------------------------------------------------------------ +// MOUSE UP +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Single); begin @@ -235,8 +353,12 @@ procedure TOBDKnobFMX.MouseUp(Button: TMouseButton; Shift: TShiftState; end; end; +//------------------------------------------------------------------------------ +// MOUSE WHEEL +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.MouseWheel(Shift: TShiftState; WheelDelta: Integer; - var Handled: Boolean); + var + Handled: Boolean); const WHEEL_LINE_DELTA = 120; begin @@ -245,6 +367,9 @@ procedure TOBDKnobFMX.MouseWheel(Shift: TShiftState; WheelDelta: Integer; Handled := True; end; +//------------------------------------------------------------------------------ +// HANDLE DRAW +//------------------------------------------------------------------------------ procedure TOBDKnobFMX.HandleDraw(ASender: TObject; const ACanvas: ISkCanvas; const ADest: TRectF; const AOpacity: Single); var diff --git a/src/Components/OBD.Knob.pas b/src/Components/OBD.Knob.pas index fe2d9a6f..910ea23d 100644 --- a/src/Components/OBD.Knob.pas +++ b/src/Components/OBD.Knob.pas @@ -92,7 +92,9 @@ TOBDKnob = class(TOBDCustomControl) property Min: Single read FMin write SetMin; property Max: Single read FMax write SetMax; property Value: Single read FValue write SetValue; - /// Increment / decrement step for the wheel and snapping. + /// + /// Increment / decrement step for the wheel and snapping. + /// property Step: Single read FStep write SetStep; property StartAngle: Single read FStartAngle write SetStartAngle; property SweepAngle: Single read FSweepAngle write SetSweepAngle; @@ -110,6 +112,9 @@ TOBDKnob = class(TOBDCustomControl) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDKnob.Create(AOwner: TComponent); begin inherited Create(AOwner); @@ -146,6 +151,9 @@ procedure TOBDKnob.SetMin(const AValue: Single); end; end; +//------------------------------------------------------------------------------ +// SET MAX +//------------------------------------------------------------------------------ procedure TOBDKnob.SetMax(const AValue: Single); begin if FMax <> AValue then @@ -156,8 +164,12 @@ procedure TOBDKnob.SetMax(const AValue: Single); end; end; +//------------------------------------------------------------------------------ +// SET VALUE +//------------------------------------------------------------------------------ procedure TOBDKnob.SetValue(const AValue: Single); -var Clamped: Single; +var + Clamped: Single; begin Clamped := SnapToStep(AValue); if Clamped < FMin then Clamped := FMin; @@ -168,44 +180,122 @@ procedure TOBDKnob.SetValue(const AValue: Single); if Assigned(FOnChange) then FOnChange(Self, FValue); end; +//------------------------------------------------------------------------------ +// SET STEP +//------------------------------------------------------------------------------ procedure TOBDKnob.SetStep(const AValue: Single); -begin if (AValue > 0) and (FStep <> AValue) then begin FStep := AValue; Invalidate; end; end; +begin + if (AValue > 0) and (FStep <> AValue) then begin FStep := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET START ANGLE +//------------------------------------------------------------------------------ procedure TOBDKnob.SetStartAngle(const AValue: Single); -begin if FStartAngle <> AValue then begin FStartAngle := AValue; Invalidate; end; end; +begin + if FStartAngle <> AValue then begin FStartAngle := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SWEEP ANGLE +//------------------------------------------------------------------------------ procedure TOBDKnob.SetSweepAngle(const AValue: Single); -begin if FSweepAngle <> AValue then begin FSweepAngle := AValue; Invalidate; end; end; +begin + if FSweepAngle <> AValue then begin FSweepAngle := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDKnob.SetBackgroundColor(const AValue: TColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Invalidate; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET BODY COLOR +//------------------------------------------------------------------------------ procedure TOBDKnob.SetBodyColor(const AValue: TColor); -begin if FBodyColor <> AValue then begin FBodyColor := AValue; Invalidate; end; end; +begin + if FBodyColor <> AValue then begin FBodyColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET RING COLOR +//------------------------------------------------------------------------------ procedure TOBDKnob.SetRingColor(const AValue: TColor); -begin if FRingColor <> AValue then begin FRingColor := AValue; Invalidate; end; end; +begin + if FRingColor <> AValue then begin FRingColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET ACTIVE RING COLOR +//------------------------------------------------------------------------------ procedure TOBDKnob.SetActiveRingColor(const AValue: TColor); -begin if FActiveRingColor <> AValue then begin FActiveRingColor := AValue; Invalidate; end; end; +begin + if FActiveRingColor <> AValue then begin FActiveRingColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET INDICATOR COLOR +//------------------------------------------------------------------------------ procedure TOBDKnob.SetIndicatorColor(const AValue: TColor); -begin if FIndicatorColor <> AValue then begin FIndicatorColor := AValue; Invalidate; end; end; +begin + if FIndicatorColor <> AValue then begin FIndicatorColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDKnob.SetTextColor(const AValue: TColor); -begin if FTextColor <> AValue then begin FTextColor := AValue; Invalidate; end; end; +begin + if FTextColor <> AValue then begin FTextColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET CAPTION +//------------------------------------------------------------------------------ procedure TOBDKnob.SetCaption(const AValue: string); -begin if FCaption <> AValue then begin FCaption := AValue; Invalidate; end; end; +begin + if FCaption <> AValue then begin FCaption := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SHOW VALUE +//------------------------------------------------------------------------------ procedure TOBDKnob.SetShowValue(const AValue: Boolean); -begin if FShowValue <> AValue then begin FShowValue := AValue; Invalidate; end; end; +begin + if FShowValue <> AValue then begin FShowValue := AValue; + Invalidate; + end; +end; //------------------------------------------------------------------------------ // MATH //------------------------------------------------------------------------------ function TOBDKnob.ValueToFraction(const AValue: Single): Single; -var Span: Single; +var + Span: Single; begin Span := FMax - FMin; if Span <= 0 then Exit(0); @@ -214,6 +304,9 @@ function TOBDKnob.ValueToFraction(const AValue: Single): Single; if Result > 1 then Result := 1; end; +//------------------------------------------------------------------------------ +// NORMALIZE ANGLE +//------------------------------------------------------------------------------ function TOBDKnob.NormalizeAngle(A: Single): Single; begin // Wrap into [0, 360). @@ -222,12 +315,18 @@ function TOBDKnob.NormalizeAngle(A: Single): Single; Result := A; end; +//------------------------------------------------------------------------------ +// SNAP TO STEP +//------------------------------------------------------------------------------ function TOBDKnob.SnapToStep(const AValue: Single): Single; begin if FStep <= 0 then Exit(AValue); Result := Round((AValue - FMin) / FStep) * FStep + FMin; end; +//------------------------------------------------------------------------------ +// POINT TO VALUE +//------------------------------------------------------------------------------ function TOBDKnob.PointToValue(const X, Y: Integer): Single; var Cx, Cy, Dx, Dy: Single; @@ -251,6 +350,9 @@ function TOBDKnob.PointToValue(const X, Y: Integer): Single; Result := FMin + (RelAngle / FSweepAngle) * (FMax - FMin); end; +//------------------------------------------------------------------------------ +// APPLY VALUE FROM MOUSE +//------------------------------------------------------------------------------ procedure TOBDKnob.ApplyValueFromMouse(X, Y: Integer); begin SetValue(PointToValue(X, Y)); @@ -271,12 +373,18 @@ procedure TOBDKnob.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Int end; end; +//------------------------------------------------------------------------------ +// MOUSE MOVE +//------------------------------------------------------------------------------ procedure TOBDKnob.MouseMove(Shift: TShiftState; X, Y: Integer); begin inherited; if FDragging then ApplyValueFromMouse(X, Y); end; +//------------------------------------------------------------------------------ +// MOUSE UP +//------------------------------------------------------------------------------ procedure TOBDKnob.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin inherited; @@ -287,6 +395,9 @@ procedure TOBDKnob.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integ end; end; +//------------------------------------------------------------------------------ +// DO MOUSE WHEEL +//------------------------------------------------------------------------------ function TOBDKnob.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint): Boolean; const diff --git a/src/Components/OBD.LED.FMX.pas b/src/Components/OBD.LED.FMX.pas index dc6e41f6..a496e3da 100644 --- a/src/Components/OBD.LED.FMX.pas +++ b/src/Components/OBD.LED.FMX.pas @@ -81,6 +81,9 @@ TOBDLedFMX = class(TSkPaintBox) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDLedFMX.Create(AOwner: TComponent); begin inherited Create(AOwner); @@ -102,31 +105,129 @@ constructor TOBDLedFMX.Create(AOwner: TComponent); OnDraw := HandleDraw; end; +//------------------------------------------------------------------------------ +// SET STATE +//------------------------------------------------------------------------------ procedure TOBDLedFMX.SetState(const AValue: TOBDLedState); -begin if FState <> AValue then begin FState := AValue; Redraw; end; end; +begin + if FState <> AValue then begin FState := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDLedFMX.SetBackgroundColor(const AValue: TAlphaColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Redraw; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET GRAYED FROM COLOR +//------------------------------------------------------------------------------ procedure TOBDLedFMX.SetGrayedFromColor(const AValue: TAlphaColor); -begin if FGrayedFromColor <> AValue then begin FGrayedFromColor := AValue; Redraw; end; end; +begin + if FGrayedFromColor <> AValue then begin FGrayedFromColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET GRAYED TO COLOR +//------------------------------------------------------------------------------ procedure TOBDLedFMX.SetGrayedToColor(const AValue: TAlphaColor); -begin if FGrayedToColor <> AValue then begin FGrayedToColor := AValue; Redraw; end; end; +begin + if FGrayedToColor <> AValue then begin FGrayedToColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET OFF FROM COLOR +//------------------------------------------------------------------------------ procedure TOBDLedFMX.SetOffFromColor(const AValue: TAlphaColor); -begin if FOffFromColor <> AValue then begin FOffFromColor := AValue; Redraw; end; end; +begin + if FOffFromColor <> AValue then begin FOffFromColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET OFF TO COLOR +//------------------------------------------------------------------------------ procedure TOBDLedFMX.SetOffToColor(const AValue: TAlphaColor); -begin if FOffToColor <> AValue then begin FOffToColor := AValue; Redraw; end; end; +begin + if FOffToColor <> AValue then begin FOffToColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET ON FROM COLOR +//------------------------------------------------------------------------------ procedure TOBDLedFMX.SetOnFromColor(const AValue: TAlphaColor); -begin if FOnFromColor <> AValue then begin FOnFromColor := AValue; Redraw; end; end; +begin + if FOnFromColor <> AValue then begin FOnFromColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET ON TO COLOR +//------------------------------------------------------------------------------ procedure TOBDLedFMX.SetOnToColor(const AValue: TAlphaColor); -begin if FOnToColor <> AValue then begin FOnToColor := AValue; Redraw; end; end; +begin + if FOnToColor <> AValue then begin FOnToColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BORDER FROM COLOR +//------------------------------------------------------------------------------ procedure TOBDLedFMX.SetBorderFromColor(const AValue: TAlphaColor); -begin if FBorderFromColor <> AValue then begin FBorderFromColor := AValue; Redraw; end; end; +begin + if FBorderFromColor <> AValue then begin FBorderFromColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BORDER TO COLOR +//------------------------------------------------------------------------------ procedure TOBDLedFMX.SetBorderToColor(const AValue: TAlphaColor); -begin if FBorderToColor <> AValue then begin FBorderToColor := AValue; Redraw; end; end; +begin + if FBorderToColor <> AValue then begin FBorderToColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BORDER WIDTH +//------------------------------------------------------------------------------ procedure TOBDLedFMX.SetBorderWidth(const AValue: Single); -begin if (AValue >= 0) and (FBorderWidth <> AValue) then begin FBorderWidth := AValue; Redraw; end; end; +begin + if (AValue >= 0) and (FBorderWidth <> AValue) then begin FBorderWidth := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET MARGIN FROM BORDER +//------------------------------------------------------------------------------ procedure TOBDLedFMX.SetMarginFromBorder(const AValue: Single); -begin if (AValue >= 0) and (FMarginFromBorder <> AValue) then begin FMarginFromBorder := AValue; Redraw; end; end; +begin + if (AValue >= 0) and (FMarginFromBorder <> AValue) then begin FMarginFromBorder := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// HANDLE DRAW +//------------------------------------------------------------------------------ procedure TOBDLedFMX.HandleDraw(ASender: TObject; const ACanvas: ISkCanvas; const ADest: TRectF; const AOpacity: Single); var diff --git a/src/Components/OBD.LinearGauge.FMX.pas b/src/Components/OBD.LinearGauge.FMX.pas index 79e745dc..443af2a4 100644 --- a/src/Components/OBD.LinearGauge.FMX.pas +++ b/src/Components/OBD.LinearGauge.FMX.pas @@ -117,6 +117,9 @@ TOBDLinearGaugeFMX = class(TSkPaintBox) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDLinearGaugeFMX.Create(AOwner: TComponent); begin inherited Create(AOwner); @@ -149,12 +152,18 @@ constructor TOBDLinearGaugeFMX.Create(AOwner: TComponent); OnDraw := HandleDraw; end; +//------------------------------------------------------------------------------ +// EASE OUT CUBIC +//------------------------------------------------------------------------------ function TOBDLinearGaugeFMX.EaseOutCubic(T: Single): Single; begin T := 1 - T; Result := 1 - (T * T * T); end; +//------------------------------------------------------------------------------ +// UPDATE ANIMATION VALUE +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.UpdateAnimationValue; var Elapsed: Int64; @@ -196,6 +205,9 @@ procedure TOBDLinearGaugeFMX.SetMin(const AValue: Single); end; end; +//------------------------------------------------------------------------------ +// SET MAX +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetMax(const AValue: Single); begin if FMax <> AValue then @@ -207,6 +219,9 @@ procedure TOBDLinearGaugeFMX.SetMax(const AValue: Single); end; end; +//------------------------------------------------------------------------------ +// SET VALUE +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetValue(const AValue: Single); var Clamped: Single; @@ -229,45 +244,139 @@ procedure TOBDLinearGaugeFMX.SetValue(const AValue: Single); Redraw; end; +//------------------------------------------------------------------------------ +// SET ORIENTATION +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetOrientation(const AValue: TOBDLinearGaugeOrientation); -begin if FOrientation <> AValue then begin FOrientation := AValue; Redraw; end; end; +begin + if FOrientation <> AValue then begin FOrientation := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SET DIRECTION +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetDirection(const AValue: TOBDLinearGaugeDirection); -begin if FDirection <> AValue then begin FDirection := AValue; Redraw; end; end; +begin + if FDirection <> AValue then begin FDirection := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetBackgroundColor(const AValue: TAlphaColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Redraw; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetBorderColor(const AValue: TAlphaColor); -begin if FBorderColor <> AValue then begin FBorderColor := AValue; Redraw; end; end; +begin + if FBorderColor <> AValue then begin FBorderColor := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SET BAR COLOR FROM +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetBarColorFrom(const AValue: TAlphaColor); -begin if FBarColorFrom <> AValue then begin FBarColorFrom := AValue; Redraw; end; end; +begin + if FBarColorFrom <> AValue then begin FBarColorFrom := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SET BAR COLOR TO +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetBarColorTo(const AValue: TAlphaColor); -begin if FBarColorTo <> AValue then begin FBarColorTo := AValue; Redraw; end; end; +begin + if FBarColorTo <> AValue then begin FBarColorTo := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SET TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetTextColor(const AValue: TAlphaColor); -begin if FTextColor <> AValue then begin FTextColor := AValue; Redraw; end; end; +begin + if FTextColor <> AValue then begin FTextColor := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SET BORDER WIDTH +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetBorderWidth(const AValue: Single); -begin if (AValue >= 0) and (FBorderWidth <> AValue) then begin FBorderWidth := AValue; Redraw; end; end; +begin + if (AValue >= 0) and (FBorderWidth <> AValue) then begin FBorderWidth := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SET CORNER RADIUS +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetCornerRadius(const AValue: Single); -begin if (AValue >= 0) and (FCornerRadius <> AValue) then begin FCornerRadius := AValue; Redraw; end; end; +begin + if (AValue >= 0) and (FCornerRadius <> AValue) then begin FCornerRadius := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SET PADDING +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetPadding(const AValue: Single); -begin if (AValue >= 0) and (FPadding <> AValue) then begin FPadding := AValue; Redraw; end; end; +begin + if (AValue >= 0) and (FPadding <> AValue) then begin FPadding := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SET CAPTION +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetCaption(const AValue: string); -begin if FCaption <> AValue then begin FCaption := AValue; Redraw; end; end; +begin + if FCaption <> AValue then begin FCaption := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SET UNITS +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetUnits(const AValue: string); -begin if FUnits <> AValue then begin FUnits := AValue; Redraw; end; end; +begin + if FUnits <> AValue then begin FUnits := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SET SHOW VALUE +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetShowValue(const AValue: Boolean); -begin if FShowValue <> AValue then begin FShowValue := AValue; Redraw; end; end; +begin + if FShowValue <> AValue then begin FShowValue := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SET ANIMATION ENABLED +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetAnimationEnabled(const AValue: Boolean); begin if FAnimationEnabled <> AValue then @@ -278,8 +387,15 @@ procedure TOBDLinearGaugeFMX.SetAnimationEnabled(const AValue: Boolean); end; end; +//------------------------------------------------------------------------------ +// SET ANIMATION DURATION MS +//------------------------------------------------------------------------------ procedure TOBDLinearGaugeFMX.SetAnimationDurationMs(const AValue: Integer); -begin if (FAnimationDurationMs <> AValue) and (AValue >= 0) then begin FAnimationDurationMs := AValue; Redraw; end; end; +begin + if (FAnimationDurationMs <> AValue) and (AValue >= 0) then begin FAnimationDurationMs := AValue; + Redraw; + end; +end; //------------------------------------------------------------------------------ // HANDLE DRAW diff --git a/src/Components/OBD.LinearGauge.pas b/src/Components/OBD.LinearGauge.pas index 4b457635..f86a8408 100644 --- a/src/Components/OBD.LinearGauge.pas +++ b/src/Components/OBD.LinearGauge.pas @@ -44,27 +44,49 @@ interface // CONSTANTS //------------------------------------------------------------------------------ const - /// Default minimum value. + /// + /// Default minimum value. + /// LG_DEFAULT_MIN: Single = 0; - /// Default maximum value. + /// + /// Default maximum value. + /// LG_DEFAULT_MAX: Single = 100; - /// Default value-transition duration (milliseconds). + /// + /// Default value-transition duration (milliseconds). + /// LG_DEFAULT_ANIM_DURATION = 350; - /// Default corner radius for the bar and frame. + /// + /// Default corner radius for the bar and frame. + /// LG_DEFAULT_CORNER_RADIUS = 6; - /// Default border thickness. + /// + /// Default border thickness. + /// LG_DEFAULT_BORDER_WIDTH = 1; - /// Default outer padding inside the control bounds. + /// + /// Default outer padding inside the control bounds. + /// LG_DEFAULT_PADDING = 6; - /// Default background fill (matches the rest of the touch UI chrome). + /// + /// Default background fill (matches the rest of the touch UI chrome). + /// LG_DEFAULT_BACKGROUND_COLOR = $00181818; - /// Default frame / border colour. + /// + /// Default frame / border colour. + /// LG_DEFAULT_BORDER_COLOR = $00404040; - /// Default bar gradient start (cool side — green). + /// + /// Default bar gradient start (cool side — green). + /// LG_DEFAULT_BAR_FROM_COLOR = $0033C033; - /// Default bar gradient end (hot side — yellow-red). + /// + /// Default bar gradient end (hot side — yellow-red). + /// LG_DEFAULT_BAR_TO_COLOR = $001F8FE6; - /// Default text colour. + /// + /// Default text colour. + /// LG_DEFAULT_TEXT_COLOR = clWhite; //------------------------------------------------------------------------------ @@ -138,41 +160,77 @@ TOBDLinearGauge = class(TOBDCustomControl) procedure Assign(Source: TPersistent); override; published - /// Minimum value. + /// + /// Minimum value. + /// property Min: Single read FMin write SetMin; - /// Maximum value. + /// + /// Maximum value. + /// property Max: Single read FMax write SetMax; - /// Current value (clamped into [Min..Max]). + /// + /// Current value (clamped into [Min..Max]). + /// property Value: Single read FValue write SetValue; - /// Bar orientation — horizontal or vertical. + /// + /// Bar orientation — horizontal or vertical. + /// property Orientation: TOBDLinearGaugeOrientation read FOrientation write SetOrientation default loHorizontal; - /// Direction of fill growth. + /// + /// Direction of fill growth. + /// property Direction: TOBDLinearGaugeDirection read FDirection write SetDirection default ldNormal; - /// Background fill colour for the unfilled bar. + /// + /// Background fill colour for the unfilled bar. + /// property BackgroundColor: TColor read FBackgroundColor write SetBackgroundColor default LG_DEFAULT_BACKGROUND_COLOR; - /// Outline colour drawn around the bar. + /// + /// Outline colour drawn around the bar. + /// property BorderColor: TColor read FBorderColor write SetBorderColor default LG_DEFAULT_BORDER_COLOR; - /// Outline thickness in pixels (0 disables the border). + /// + /// Outline thickness in pixels (0 disables the border). + /// property BorderWidth: Integer read FBorderWidth write SetBorderWidth default LG_DEFAULT_BORDER_WIDTH; - /// Gradient start colour for the filled portion. + /// + /// Gradient start colour for the filled portion. + /// property BarColorFrom: TColor read FBarColorFrom write SetBarColorFrom default LG_DEFAULT_BAR_FROM_COLOR; - /// Gradient end colour for the filled portion. + /// + /// Gradient end colour for the filled portion. + /// property BarColorTo: TColor read FBarColorTo write SetBarColorTo default LG_DEFAULT_BAR_TO_COLOR; - /// Corner radius for the bar and frame. + /// + /// Corner radius for the bar and frame. + /// property CornerRadius: Integer read FCornerRadius write SetCornerRadius default LG_DEFAULT_CORNER_RADIUS; - /// Inner padding between the control edge and the bar. + /// + /// Inner padding between the control edge and the bar. + /// property Padding: Integer read FPadding write SetPadding default LG_DEFAULT_PADDING; - /// Optional caption rendered above (horizontal) or beside (vertical) the bar. + /// + /// Optional caption rendered above (horizontal) or beside (vertical) the bar. + /// property Caption: string read FCaption write SetCaption; - /// Optional units suffix appended to the value text. + /// + /// Optional units suffix appended to the value text. + /// property Units: string read FUnits write SetUnits; - /// If true, render the numeric value next to the bar. + /// + /// If true, render the numeric value next to the bar. + /// property ShowValue: Boolean read FShowValue write SetShowValue default True; - /// Colour used for caption + value text. + /// + /// Colour used for caption + value text. + /// property TextColor: TColor read FTextColor write SetTextColor default LG_DEFAULT_TEXT_COLOR; - /// Whether value transitions are animated. + /// + /// Whether value transitions are animated. + /// property AnimationEnabled: Boolean read FAnimationEnabled write SetAnimationEnabled default True; - /// Animation duration in milliseconds. + /// + /// Animation duration in milliseconds. + /// property AnimationDurationMs: Integer read FAnimationDurationMs write SetAnimationDurationMs default LG_DEFAULT_ANIM_DURATION; end; @@ -281,6 +339,9 @@ procedure TOBDLinearGauge.SetMin(const AValue: Single); end; end; +//------------------------------------------------------------------------------ +// SET MAX +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetMax(const AValue: Single); begin if FMax <> AValue then @@ -292,6 +353,9 @@ procedure TOBDLinearGauge.SetMax(const AValue: Single); end; end; +//------------------------------------------------------------------------------ +// SET VALUE +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetValue(const AValue: Single); var Clamped: Single; @@ -317,6 +381,9 @@ procedure TOBDLinearGauge.SetValue(const AValue: Single); Invalidate; end; +//------------------------------------------------------------------------------ +// SET ORIENTATION +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetOrientation(const AValue: TOBDLinearGaugeOrientation); begin if FOrientation <> AValue then @@ -326,6 +393,9 @@ procedure TOBDLinearGauge.SetOrientation(const AValue: TOBDLinearGaugeOrientatio end; end; +//------------------------------------------------------------------------------ +// SET DIRECTION +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetDirection(const AValue: TOBDLinearGaugeDirection); begin if FDirection <> AValue then @@ -335,6 +405,9 @@ procedure TOBDLinearGauge.SetDirection(const AValue: TOBDLinearGaugeDirection); end; end; +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetBackgroundColor(const AValue: TColor); begin if FBackgroundColor <> AValue then @@ -344,6 +417,9 @@ procedure TOBDLinearGauge.SetBackgroundColor(const AValue: TColor); end; end; +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetBorderColor(const AValue: TColor); begin if FBorderColor <> AValue then @@ -353,6 +429,9 @@ procedure TOBDLinearGauge.SetBorderColor(const AValue: TColor); end; end; +//------------------------------------------------------------------------------ +// SET BORDER WIDTH +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetBorderWidth(const AValue: Integer); begin if (FBorderWidth <> AValue) and (AValue >= 0) then @@ -362,6 +441,9 @@ procedure TOBDLinearGauge.SetBorderWidth(const AValue: Integer); end; end; +//------------------------------------------------------------------------------ +// SET BAR COLOR FROM +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetBarColorFrom(const AValue: TColor); begin if FBarColorFrom <> AValue then @@ -371,6 +453,9 @@ procedure TOBDLinearGauge.SetBarColorFrom(const AValue: TColor); end; end; +//------------------------------------------------------------------------------ +// SET BAR COLOR TO +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetBarColorTo(const AValue: TColor); begin if FBarColorTo <> AValue then @@ -380,6 +465,9 @@ procedure TOBDLinearGauge.SetBarColorTo(const AValue: TColor); end; end; +//------------------------------------------------------------------------------ +// SET CORNER RADIUS +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetCornerRadius(const AValue: Integer); begin if (FCornerRadius <> AValue) and (AValue >= 0) then @@ -389,6 +477,9 @@ procedure TOBDLinearGauge.SetCornerRadius(const AValue: Integer); end; end; +//------------------------------------------------------------------------------ +// SET PADDING +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetPadding(const AValue: Integer); begin if (FPadding <> AValue) and (AValue >= 0) then @@ -398,6 +489,9 @@ procedure TOBDLinearGauge.SetPadding(const AValue: Integer); end; end; +//------------------------------------------------------------------------------ +// SET CAPTION +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetCaption(const AValue: string); begin if FCaption <> AValue then @@ -407,6 +501,9 @@ procedure TOBDLinearGauge.SetCaption(const AValue: string); end; end; +//------------------------------------------------------------------------------ +// SET UNITS +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetUnits(const AValue: string); begin if FUnits <> AValue then @@ -416,6 +513,9 @@ procedure TOBDLinearGauge.SetUnits(const AValue: string); end; end; +//------------------------------------------------------------------------------ +// SET SHOW VALUE +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetShowValue(const AValue: Boolean); begin if FShowValue <> AValue then @@ -425,6 +525,9 @@ procedure TOBDLinearGauge.SetShowValue(const AValue: Boolean); end; end; +//------------------------------------------------------------------------------ +// SET TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetTextColor(const AValue: TColor); begin if FTextColor <> AValue then @@ -434,6 +537,9 @@ procedure TOBDLinearGauge.SetTextColor(const AValue: TColor); end; end; +//------------------------------------------------------------------------------ +// SET ANIMATION ENABLED +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetAnimationEnabled(const AValue: Boolean); begin if FAnimationEnabled <> AValue then @@ -444,6 +550,9 @@ procedure TOBDLinearGauge.SetAnimationEnabled(const AValue: Boolean); end; end; +//------------------------------------------------------------------------------ +// SET ANIMATION DURATION MS +//------------------------------------------------------------------------------ procedure TOBDLinearGauge.SetAnimationDurationMs(const AValue: Integer); begin if (FAnimationDurationMs <> AValue) and (AValue >= 0) then diff --git a/src/Components/OBD.LogViewer.pas b/src/Components/OBD.LogViewer.pas index 5c04be46..83cb005e 100644 --- a/src/Components/OBD.LogViewer.pas +++ b/src/Components/OBD.LogViewer.pas @@ -37,43 +37,65 @@ TOBDLogViewer = class(TOBDTerminal, IOBDLogSink) public constructor Create(AOwner: TComponent); override; - /// Render a single log event. + /// + /// Render a single log event. + /// procedure Write(const Event: TOBDLogEvent); procedure Flush; published - /// Prefix every line with [LEVEL] when true (default). + /// + /// Prefix every line with [LEVEL] when true (default). + /// property ShowLevelTag: Boolean read FShowLevelTag write SetShowLevelTag default True; end; implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDLogViewer.Create(AOwner: TComponent); begin inherited Create(AOwner); FShowLevelTag := True; end; +//------------------------------------------------------------------------------ +// SET SHOW LEVEL TAG +//------------------------------------------------------------------------------ procedure TOBDLogViewer.SetShowLevelTag(const AValue: Boolean); begin if FShowLevelTag <> AValue then FShowLevelTag := AValue; end; +//------------------------------------------------------------------------------ +// QUERY INTERFACE +//------------------------------------------------------------------------------ function TOBDLogViewer.QueryInterface(const IID: TGUID; out Obj): HResult; begin if GetInterface(IID, Obj) then Result := 0 else Result := E_NOINTERFACE; end; +//------------------------------------------------------------------------------ +// _ADD REF +//------------------------------------------------------------------------------ function TOBDLogViewer._AddRef: Integer; begin // VCL controls own themselves — never reference count. Result := -1; end; +//------------------------------------------------------------------------------ +// _RELEASE +//------------------------------------------------------------------------------ function TOBDLogViewer._Release: Integer; begin Result := -1; end; +//------------------------------------------------------------------------------ +// WRITE +//------------------------------------------------------------------------------ procedure TOBDLogViewer.Write(const Event: TOBDLogEvent); var Tag, Source, Body: string; @@ -97,6 +119,9 @@ procedure TOBDLogViewer.Write(const Event: TOBDLogEvent); end; end; +//------------------------------------------------------------------------------ +// FLUSH +//------------------------------------------------------------------------------ procedure TOBDLogViewer.Flush; begin // Nothing to flush — terminal renders synchronously. diff --git a/src/Components/OBD.SegmentedSwitch.FMX.pas b/src/Components/OBD.SegmentedSwitch.FMX.pas index f61144b6..181a7c10 100644 --- a/src/Components/OBD.SegmentedSwitch.FMX.pas +++ b/src/Components/OBD.SegmentedSwitch.FMX.pas @@ -73,6 +73,9 @@ TOBDSegmentedSwitchFMX = class(TSkPaintBox) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDSegmentedSwitchFMX.Create(AOwner: TComponent); begin inherited Create(AOwner); @@ -92,9 +95,18 @@ constructor TOBDSegmentedSwitchFMX.Create(AOwner: TComponent); OnDraw := HandleDraw; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDSegmentedSwitchFMX.Destroy; -begin FSegments.Free; inherited; end; +begin + FSegments.Free; + inherited; +end; +//------------------------------------------------------------------------------ +// SEGMENTS CHANGED +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitchFMX.SegmentsChanged(Sender: TObject); begin if FSelectedIndex >= FSegments.Count then FSelectedIndex := FSegments.Count - 1; @@ -102,11 +114,20 @@ procedure TOBDSegmentedSwitchFMX.SegmentsChanged(Sender: TObject); Redraw; end; +//------------------------------------------------------------------------------ +// SET SEGMENTS +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitchFMX.SetSegments(const AValue: TStringList); -begin FSegments.Assign(AValue); end; +begin + FSegments.Assign(AValue); +end; +//------------------------------------------------------------------------------ +// SET SELECTED INDEX +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitchFMX.SetSelectedIndex(const AValue: Integer); -var Clamped: Integer; +var + Clamped: Integer; begin Clamped := AValue; if Clamped < 0 then Clamped := 0; @@ -119,27 +140,81 @@ procedure TOBDSegmentedSwitchFMX.SetSelectedIndex(const AValue: Integer); end; end; +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitchFMX.SetBackgroundColor(const AValue: TAlphaColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Redraw; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitchFMX.SetBorderColor(const AValue: TAlphaColor); -begin if FBorderColor <> AValue then begin FBorderColor := AValue; Redraw; end; end; +begin + if FBorderColor <> AValue then begin FBorderColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET ACTIVE COLOR +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitchFMX.SetActiveColor(const AValue: TAlphaColor); -begin if FActiveColor <> AValue then begin FActiveColor := AValue; Redraw; end; end; +begin + if FActiveColor <> AValue then begin FActiveColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET ACTIVE TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitchFMX.SetActiveTextColor(const AValue: TAlphaColor); -begin if FActiveTextColor <> AValue then begin FActiveTextColor := AValue; Redraw; end; end; +begin + if FActiveTextColor <> AValue then begin FActiveTextColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET INACTIVE TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitchFMX.SetInactiveTextColor(const AValue: TAlphaColor); -begin if FInactiveTextColor <> AValue then begin FInactiveTextColor := AValue; Redraw; end; end; +begin + if FInactiveTextColor <> AValue then begin FInactiveTextColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET CORNER RADIUS +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitchFMX.SetCornerRadius(const AValue: Integer); -begin if (AValue >= 0) and (FCornerRadius <> AValue) then begin FCornerRadius := AValue; Redraw; end; end; +begin + if (AValue >= 0) and (FCornerRadius <> AValue) then begin FCornerRadius := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// SEGMENT WIDTH +//------------------------------------------------------------------------------ function TOBDSegmentedSwitchFMX.SegmentWidth: Single; begin if FSegments.Count = 0 then Exit(0); Result := Width / FSegments.Count; end; +//------------------------------------------------------------------------------ +// INDEX AT +//------------------------------------------------------------------------------ function TOBDSegmentedSwitchFMX.IndexAt(X: Single): Integer; -var W: Single; +var + W: Single; begin Result := -1; W := SegmentWidth; @@ -149,9 +224,13 @@ function TOBDSegmentedSwitchFMX.IndexAt(X: Single): Integer; if Result >= FSegments.Count then Result := FSegments.Count - 1; end; +//------------------------------------------------------------------------------ +// MOUSE DOWN +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitchFMX.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Single); -var Idx: Integer; +var + Idx: Integer; begin inherited; if not IsFocused then SetFocus; @@ -160,6 +239,9 @@ procedure TOBDSegmentedSwitchFMX.MouseDown(Button: TMouseButton; if Idx >= 0 then SetSelectedIndex(Idx); end; +//------------------------------------------------------------------------------ +// HANDLE DRAW +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitchFMX.HandleDraw(ASender: TObject; const ACanvas: ISkCanvas; const ADest: TRectF; const AOpacity: Single); var diff --git a/src/Components/OBD.SegmentedSwitch.pas b/src/Components/OBD.SegmentedSwitch.pas index 9cc2509a..5f770f90 100644 --- a/src/Components/OBD.SegmentedSwitch.pas +++ b/src/Components/OBD.SegmentedSwitch.pas @@ -86,6 +86,9 @@ TOBDSegmentedSwitch = class(TOBDCustomControl) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDSegmentedSwitch.Create(AOwner: TComponent); begin inherited Create(AOwner); @@ -104,12 +107,18 @@ constructor TOBDSegmentedSwitch.Create(AOwner: TComponent); Height := 32; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDSegmentedSwitch.Destroy; begin FSegments.Free; inherited; end; +//------------------------------------------------------------------------------ +// SEGMENTS CHANGED +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitch.SegmentsChanged(Sender: TObject); begin if FSelectedIndex >= FSegments.Count then @@ -118,11 +127,17 @@ procedure TOBDSegmentedSwitch.SegmentsChanged(Sender: TObject); Invalidate; end; +//------------------------------------------------------------------------------ +// SET SEGMENTS +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitch.SetSegments(const AValue: TStringList); begin FSegments.Assign(AValue); end; +//------------------------------------------------------------------------------ +// SET SELECTED INDEX +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitch.SetSelectedIndex(const AValue: Integer); var Clamped: Integer; @@ -138,30 +153,78 @@ procedure TOBDSegmentedSwitch.SetSelectedIndex(const AValue: Integer); end; end; +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitch.SetBackgroundColor(const AValue: TColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Invalidate; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitch.SetBorderColor(const AValue: TColor); -begin if FBorderColor <> AValue then begin FBorderColor := AValue; Invalidate; end; end; +begin + if FBorderColor <> AValue then begin FBorderColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET ACTIVE COLOR +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitch.SetActiveColor(const AValue: TColor); -begin if FActiveColor <> AValue then begin FActiveColor := AValue; Invalidate; end; end; +begin + if FActiveColor <> AValue then begin FActiveColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET ACTIVE TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitch.SetActiveTextColor(const AValue: TColor); -begin if FActiveTextColor <> AValue then begin FActiveTextColor := AValue; Invalidate; end; end; +begin + if FActiveTextColor <> AValue then begin FActiveTextColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET INACTIVE TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitch.SetInactiveTextColor(const AValue: TColor); -begin if FInactiveTextColor <> AValue then begin FInactiveTextColor := AValue; Invalidate; end; end; +begin + if FInactiveTextColor <> AValue then begin FInactiveTextColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET CORNER RADIUS +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitch.SetCornerRadius(const AValue: Integer); -begin if (AValue >= 0) and (FCornerRadius <> AValue) then begin FCornerRadius := AValue; Invalidate; end; end; +begin + if (AValue >= 0) and (FCornerRadius <> AValue) then begin FCornerRadius := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SEGMENT WIDTH +//------------------------------------------------------------------------------ function TOBDSegmentedSwitch.SegmentWidth: Single; begin if FSegments.Count = 0 then Exit(0); Result := Width / FSegments.Count; end; +//------------------------------------------------------------------------------ +// INDEX AT +//------------------------------------------------------------------------------ function TOBDSegmentedSwitch.IndexAt(X: Integer): Integer; var W: Single; @@ -174,9 +237,13 @@ function TOBDSegmentedSwitch.IndexAt(X: Integer): Integer; if Result >= FSegments.Count then Result := FSegments.Count - 1; end; +//------------------------------------------------------------------------------ +// MOUSE DOWN +//------------------------------------------------------------------------------ procedure TOBDSegmentedSwitch.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer); -var Idx: Integer; +var + Idx: Integer; begin inherited; if not Focused then SetFocus; diff --git a/src/Components/OBD.Tachometer.FMX.pas b/src/Components/OBD.Tachometer.FMX.pas index e5dec244..e29d43b9 100644 --- a/src/Components/OBD.Tachometer.FMX.pas +++ b/src/Components/OBD.Tachometer.FMX.pas @@ -134,6 +134,9 @@ TOBDTachometerFMX = class(TSkPaintBox) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDTachometerFMX.Create(AOwner: TComponent); begin inherited Create(AOwner); @@ -176,17 +179,31 @@ constructor TOBDTachometerFMX.Create(AOwner: TComponent); OnDraw := HandleDraw; end; +//------------------------------------------------------------------------------ +// EASE OUT CUBIC +//------------------------------------------------------------------------------ function TOBDTachometerFMX.EaseOutCubic(T: Single): Single; begin T := 1 - T; Result := 1 - (T * T * T); end; +//------------------------------------------------------------------------------ +// UPDATE ANIMATION VALUE +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.UpdateAnimationValue; var Elapsed: Int64; Progress: Single; begin - if not FAnimationEnabled then begin FDisplayValue := FValue; Exit; end; - if FAnimationDurationMs <= 0 then begin FDisplayValue := FValue; Exit; end; + if not FAnimationEnabled then + begin + FDisplayValue := FValue; + Exit; + end; + if FAnimationDurationMs <= 0 then + begin + FDisplayValue := FValue; + Exit; + end; Elapsed := FStopwatch.ElapsedMilliseconds - FAnimationStartMs; if Elapsed >= FAnimationDurationMs then FDisplayValue := FValue @@ -198,11 +215,17 @@ procedure TOBDTachometerFMX.UpdateAnimationValue; end; end; +//------------------------------------------------------------------------------ +// SHIFT LIGHT ACTIVE +//------------------------------------------------------------------------------ function TOBDTachometerFMX.ShiftLightActive: Boolean; begin Result := FShowShiftLight and (FDisplayValue >= FShiftPoint); end; +//------------------------------------------------------------------------------ +// SET MIN +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetMin(const AValue: Single); begin if FMin <> AValue then @@ -214,6 +237,9 @@ procedure TOBDTachometerFMX.SetMin(const AValue: Single); end; end; +//------------------------------------------------------------------------------ +// SET MAX +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetMax(const AValue: Single); begin if FMax <> AValue then @@ -225,8 +251,12 @@ procedure TOBDTachometerFMX.SetMax(const AValue: Single); end; end; +//------------------------------------------------------------------------------ +// SET VALUE +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetValue(const AValue: Single); -var Clamped: Single; +var + Clamped: Single; begin Clamped := AValue; if Clamped < FMin then Clamped := FMin; @@ -246,44 +276,199 @@ procedure TOBDTachometerFMX.SetValue(const AValue: Single); Redraw; end; +//------------------------------------------------------------------------------ +// SET REDLINE FROM +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetRedlineFrom(const AValue: Single); -begin if FRedlineFrom <> AValue then begin FRedlineFrom := AValue; Redraw; end; end; +begin + if FRedlineFrom <> AValue then begin FRedlineFrom := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SHIFT POINT +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetShiftPoint(const AValue: Single); -begin if FShiftPoint <> AValue then begin FShiftPoint := AValue; Redraw; end; end; +begin + if FShiftPoint <> AValue then begin FShiftPoint := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET START ANGLE +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetStartAngle(const AValue: Single); -begin if FStartAngle <> AValue then begin FStartAngle := AValue; Redraw; end; end; +begin + if FStartAngle <> AValue then begin FStartAngle := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SWEEP ANGLE +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetSweepAngle(const AValue: Single); -begin if FSweepAngle <> AValue then begin FSweepAngle := AValue; Redraw; end; end; +begin + if FSweepAngle <> AValue then begin FSweepAngle := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET MAJOR TICK INTERVAL +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetMajorTickInterval(const AValue: Single); -begin if (AValue > 0) and (FMajorTickInterval <> AValue) then begin FMajorTickInterval := AValue; Redraw; end; end; +begin + if (AValue > 0) and (FMajorTickInterval <> AValue) then begin FMajorTickInterval := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET MINOR TICK INTERVAL +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetMinorTickInterval(const AValue: Single); -begin if (AValue > 0) and (FMinorTickInterval <> AValue) then begin FMinorTickInterval := AValue; Redraw; end; end; +begin + if (AValue > 0) and (FMinorTickInterval <> AValue) then begin FMinorTickInterval := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET TICK LABEL DIVISOR +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetTickLabelDivisor(const AValue: Single); -begin if (AValue > 0) and (FTickLabelDivisor <> AValue) then begin FTickLabelDivisor := AValue; Redraw; end; end; +begin + if (AValue > 0) and (FTickLabelDivisor <> AValue) then begin FTickLabelDivisor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetBackgroundColor(const AValue: TAlphaColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Redraw; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET RING COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetRingColor(const AValue: TAlphaColor); -begin if FRingColor <> AValue then begin FRingColor := AValue; Redraw; end; end; +begin + if FRingColor <> AValue then begin FRingColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetBorderColor(const AValue: TAlphaColor); -begin if FBorderColor <> AValue then begin FBorderColor := AValue; Redraw; end; end; +begin + if FBorderColor <> AValue then begin FBorderColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET TICK COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetTickColor(const AValue: TAlphaColor); -begin if FTickColor <> AValue then begin FTickColor := AValue; Redraw; end; end; +begin + if FTickColor <> AValue then begin FTickColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET REDLINE COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetRedlineColor(const AValue: TAlphaColor); -begin if FRedlineColor <> AValue then begin FRedlineColor := AValue; Redraw; end; end; +begin + if FRedlineColor <> AValue then begin FRedlineColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET NEEDLE COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetNeedleColor(const AValue: TAlphaColor); -begin if FNeedleColor <> AValue then begin FNeedleColor := AValue; Redraw; end; end; +begin + if FNeedleColor <> AValue then begin FNeedleColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetTextColor(const AValue: TAlphaColor); -begin if FTextColor <> AValue then begin FTextColor := AValue; Redraw; end; end; +begin + if FTextColor <> AValue then begin FTextColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SHIFT LIGHT COLOR OFF +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetShiftLightColorOff(const AValue: TAlphaColor); -begin if FShiftLightColorOff <> AValue then begin FShiftLightColorOff := AValue; Redraw; end; end; +begin + if FShiftLightColorOff <> AValue then begin FShiftLightColorOff := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SHIFT LIGHT COLOR ON +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetShiftLightColorOn(const AValue: TAlphaColor); -begin if FShiftLightColorOn <> AValue then begin FShiftLightColorOn := AValue; Redraw; end; end; +begin + if FShiftLightColorOn <> AValue then begin FShiftLightColorOn := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET CAPTION +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetCaption(const AValue: string); -begin if FCaption <> AValue then begin FCaption := AValue; Redraw; end; end; +begin + if FCaption <> AValue then begin FCaption := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET UNITS +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetUnits(const AValue: string); -begin if FUnits <> AValue then begin FUnits := AValue; Redraw; end; end; +begin + if FUnits <> AValue then begin FUnits := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SHOW SHIFT LIGHT +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetShowShiftLight(const AValue: Boolean); -begin if FShowShiftLight <> AValue then begin FShowShiftLight := AValue; Redraw; end; end; +begin + if FShowShiftLight <> AValue then begin FShowShiftLight := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET ANIMATION ENABLED +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetAnimationEnabled(const AValue: Boolean); begin if FAnimationEnabled <> AValue then @@ -293,9 +478,20 @@ procedure TOBDTachometerFMX.SetAnimationEnabled(const AValue: Boolean); Redraw; end; end; + +//------------------------------------------------------------------------------ +// SET ANIMATION DURATION MS +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.SetAnimationDurationMs(const AValue: Integer); -begin if (AValue >= 0) and (FAnimationDurationMs <> AValue) then begin FAnimationDurationMs := AValue; Redraw; end; end; +begin + if (AValue >= 0) and (FAnimationDurationMs <> AValue) then begin FAnimationDurationMs := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// HANDLE DRAW +//------------------------------------------------------------------------------ procedure TOBDTachometerFMX.HandleDraw(ASender: TObject; const ACanvas: ISkCanvas; const ADest: TRectF; const AOpacity: Single); var diff --git a/src/Components/OBD.Tachometer.pas b/src/Components/OBD.Tachometer.pas index 60f1fbf0..94110616 100644 --- a/src/Components/OBD.Tachometer.pas +++ b/src/Components/OBD.Tachometer.pas @@ -138,9 +138,13 @@ TOBDTachometer = class(TOBDCustomControl) property Min: Single read FMin write SetMin; property Max: Single read FMax write SetMax; property Value: Single read FValue write SetValue; - /// RPM at which the redline arc starts. + /// + /// RPM at which the redline arc starts. + /// property RedlineFrom: Single read FRedlineFrom write SetRedlineFrom; - /// RPM at which the shift light fires. + /// + /// RPM at which the shift light fires. + /// property ShiftPoint: Single read FShiftPoint write SetShiftPoint; property StartAngle: Single read FStartAngle write SetStartAngle; @@ -148,7 +152,9 @@ TOBDTachometer = class(TOBDCustomControl) property MajorTickInterval: Single read FMajorTickInterval write SetMajorTickInterval; property MinorTickInterval: Single read FMinorTickInterval write SetMinorTickInterval; - /// Numeric divisor applied to tick labels (1000 → "0..8" instead of "0..8000"). + /// + /// Numeric divisor applied to tick labels (1000 → "0..8" instead of "0..8000"). + /// property TickLabelDivisor: Single read FTickLabelDivisor write SetTickLabelDivisor; property BackgroundColor: TColor read FBackgroundColor write SetBackgroundColor default TC_DEFAULT_BACKGROUND_COLOR; @@ -223,6 +229,9 @@ function TOBDTachometer.EaseOutCubic(T: Single): Single; Result := 1 - (T * T * T); end; +//------------------------------------------------------------------------------ +// VALUE TO FRACTION +//------------------------------------------------------------------------------ function TOBDTachometer.ValueToFraction(const AValue: Single): Single; var Span: Single; @@ -286,6 +295,9 @@ procedure TOBDTachometer.SetMin(const AValue: Single); end; end; +//------------------------------------------------------------------------------ +// SET MAX +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetMax(const AValue: Single); begin if FMax <> AValue then @@ -297,6 +309,9 @@ procedure TOBDTachometer.SetMax(const AValue: Single); end; end; +//------------------------------------------------------------------------------ +// SET VALUE +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetValue(const AValue: Single); var Clamped: Single; @@ -319,63 +334,199 @@ procedure TOBDTachometer.SetValue(const AValue: Single); Invalidate; end; +//------------------------------------------------------------------------------ +// SET REDLINE FROM +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetRedlineFrom(const AValue: Single); -begin if FRedlineFrom <> AValue then begin FRedlineFrom := AValue; Invalidate; end; end; +begin + if FRedlineFrom <> AValue then begin FRedlineFrom := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SHIFT POINT +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetShiftPoint(const AValue: Single); -begin if FShiftPoint <> AValue then begin FShiftPoint := AValue; Invalidate; end; end; +begin + if FShiftPoint <> AValue then begin FShiftPoint := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET START ANGLE +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetStartAngle(const AValue: Single); -begin if FStartAngle <> AValue then begin FStartAngle := AValue; Invalidate; end; end; +begin + if FStartAngle <> AValue then begin FStartAngle := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SWEEP ANGLE +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetSweepAngle(const AValue: Single); -begin if FSweepAngle <> AValue then begin FSweepAngle := AValue; Invalidate; end; end; +begin + if FSweepAngle <> AValue then begin FSweepAngle := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET MAJOR TICK INTERVAL +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetMajorTickInterval(const AValue: Single); -begin if (FMajorTickInterval <> AValue) and (AValue > 0) then begin FMajorTickInterval := AValue; Invalidate; end; end; +begin + if (FMajorTickInterval <> AValue) and (AValue > 0) then begin FMajorTickInterval := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET MINOR TICK INTERVAL +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetMinorTickInterval(const AValue: Single); -begin if (FMinorTickInterval <> AValue) and (AValue > 0) then begin FMinorTickInterval := AValue; Invalidate; end; end; +begin + if (FMinorTickInterval <> AValue) and (AValue > 0) then begin FMinorTickInterval := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET TICK LABEL DIVISOR +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetTickLabelDivisor(const AValue: Single); -begin if (FTickLabelDivisor <> AValue) and (AValue > 0) then begin FTickLabelDivisor := AValue; Invalidate; end; end; +begin + if (FTickLabelDivisor <> AValue) and (AValue > 0) then begin FTickLabelDivisor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetBackgroundColor(const AValue: TColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Invalidate; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET RING COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetRingColor(const AValue: TColor); -begin if FRingColor <> AValue then begin FRingColor := AValue; Invalidate; end; end; +begin + if FRingColor <> AValue then begin FRingColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetBorderColor(const AValue: TColor); -begin if FBorderColor <> AValue then begin FBorderColor := AValue; Invalidate; end; end; +begin + if FBorderColor <> AValue then begin FBorderColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET TICK COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetTickColor(const AValue: TColor); -begin if FTickColor <> AValue then begin FTickColor := AValue; Invalidate; end; end; +begin + if FTickColor <> AValue then begin FTickColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET REDLINE COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetRedlineColor(const AValue: TColor); -begin if FRedlineColor <> AValue then begin FRedlineColor := AValue; Invalidate; end; end; +begin + if FRedlineColor <> AValue then begin FRedlineColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET NEEDLE COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetNeedleColor(const AValue: TColor); -begin if FNeedleColor <> AValue then begin FNeedleColor := AValue; Invalidate; end; end; +begin + if FNeedleColor <> AValue then begin FNeedleColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetTextColor(const AValue: TColor); -begin if FTextColor <> AValue then begin FTextColor := AValue; Invalidate; end; end; +begin + if FTextColor <> AValue then begin FTextColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SHIFT LIGHT COLOR OFF +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetShiftLightColorOff(const AValue: TColor); -begin if FShiftLightColorOff <> AValue then begin FShiftLightColorOff := AValue; Invalidate; end; end; +begin + if FShiftLightColorOff <> AValue then begin FShiftLightColorOff := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SHIFT LIGHT COLOR ON +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetShiftLightColorOn(const AValue: TColor); -begin if FShiftLightColorOn <> AValue then begin FShiftLightColorOn := AValue; Invalidate; end; end; +begin + if FShiftLightColorOn <> AValue then begin FShiftLightColorOn := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET CAPTION +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetCaption(const AValue: string); -begin if FCaption <> AValue then begin FCaption := AValue; Invalidate; end; end; +begin + if FCaption <> AValue then begin FCaption := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET UNITS +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetUnits(const AValue: string); -begin if FUnits <> AValue then begin FUnits := AValue; Invalidate; end; end; +begin + if FUnits <> AValue then begin FUnits := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SHOW SHIFT LIGHT +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetShowShiftLight(const AValue: Boolean); -begin if FShowShiftLight <> AValue then begin FShowShiftLight := AValue; Invalidate; end; end; +begin + if FShowShiftLight <> AValue then begin FShowShiftLight := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET ANIMATION ENABLED +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetAnimationEnabled(const AValue: Boolean); begin if FAnimationEnabled <> AValue then @@ -386,8 +537,15 @@ procedure TOBDTachometer.SetAnimationEnabled(const AValue: Boolean); end; end; +//------------------------------------------------------------------------------ +// SET ANIMATION DURATION MS +//------------------------------------------------------------------------------ procedure TOBDTachometer.SetAnimationDurationMs(const AValue: Integer); -begin if (FAnimationDurationMs <> AValue) and (AValue >= 0) then begin FAnimationDurationMs := AValue; Invalidate; end; end; +begin + if (FAnimationDurationMs <> AValue) and (AValue >= 0) then begin FAnimationDurationMs := AValue; + Invalidate; + end; +end; //------------------------------------------------------------------------------ // PAINT SKIA diff --git a/src/Components/OBD.Terminal.FMX.pas b/src/Components/OBD.Terminal.FMX.pas index f0af00f1..cec2aee6 100644 --- a/src/Components/OBD.Terminal.FMX.pas +++ b/src/Components/OBD.Terminal.FMX.pas @@ -110,6 +110,9 @@ TOBDTerminalFMX = class(TSkPaintBox) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDTerminalFMX.Create(AOwner: TComponent); begin inherited Create(AOwner); @@ -132,27 +135,52 @@ constructor TOBDTerminalFMX.Create(AOwner: TComponent); OnDraw := HandleDraw; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDTerminalFMX.Destroy; -begin FLines.Free; inherited; end; +begin + FLines.Free; + inherited; +end; +//------------------------------------------------------------------------------ +// LINE HEIGHT +//------------------------------------------------------------------------------ function TOBDTerminalFMX.LineHeight: Integer; -begin Result := FFontSize + 4; end; +begin + Result := FFontSize + 4; +end; +//------------------------------------------------------------------------------ +// VISIBLE LINE COUNT +//------------------------------------------------------------------------------ function TOBDTerminalFMX.VisibleLineCount: Integer; begin Result := Trunc((Height - 2 * TERMFMX_DEFAULT_PADDING) / LineHeight); if Result < 0 then Result := 0; end; +//------------------------------------------------------------------------------ +// CONTENT HEIGHT +//------------------------------------------------------------------------------ function TOBDTerminalFMX.ContentHeight: Integer; -begin Result := FLines.Count * LineHeight; end; +begin + Result := FLines.Count * LineHeight; +end; +//------------------------------------------------------------------------------ +// MAX SCROLL +//------------------------------------------------------------------------------ function TOBDTerminalFMX.MaxScroll: Integer; begin Result := ContentHeight - VisibleLineCount * LineHeight; if Result < 0 then Result := 0; end; +//------------------------------------------------------------------------------ +// CLAMP SCROLL +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.ClampScroll; begin if FScrollY < 0 then FScrollY := 0; @@ -160,6 +188,9 @@ procedure TOBDTerminalFMX.ClampScroll; FFollowTail := FScrollY >= MaxScroll; end; +//------------------------------------------------------------------------------ +// APPEND LINE +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.AppendLine(const ALine: TOBDTerminalLineFMX); begin while FLines.Count >= FMaxLines do FLines.Delete(0); @@ -168,35 +199,85 @@ procedure TOBDTerminalFMX.AppendLine(const ALine: TOBDTerminalLineFMX); Redraw; end; +//------------------------------------------------------------------------------ +// LOG SENT +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.LogSent(const AText: string); -var L: TOBDTerminalLineFMX; -begin L.Direction := tdSent; L.Text := AText; L.Timestamp := Now; AppendLine(L); end; +var + L: TOBDTerminalLineFMX; +begin + L.Direction := tdSent; + L.Text := AText; + L.Timestamp := Now; + AppendLine(L); +end; +//------------------------------------------------------------------------------ +// LOG RECEIVED +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.LogReceived(const AText: string); -var L: TOBDTerminalLineFMX; -begin L.Direction := tdReceived; L.Text := AText; L.Timestamp := Now; AppendLine(L); end; +var + L: TOBDTerminalLineFMX; +begin + L.Direction := tdReceived; + L.Text := AText; + L.Timestamp := Now; + AppendLine(L); +end; +//------------------------------------------------------------------------------ +// LOG INFO +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.LogInfo(const AText: string); -var L: TOBDTerminalLineFMX; -begin L.Direction := tdInfo; L.Text := AText; L.Timestamp := Now; AppendLine(L); end; +var + L: TOBDTerminalLineFMX; +begin + L.Direction := tdInfo; + L.Text := AText; + L.Timestamp := Now; + AppendLine(L); +end; +//------------------------------------------------------------------------------ +// LOG ERROR +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.LogError(const AText: string); -var L: TOBDTerminalLineFMX; -begin L.Direction := tdError; L.Text := AText; L.Timestamp := Now; AppendLine(L); end; +var + L: TOBDTerminalLineFMX; +begin + L.Direction := tdError; + L.Text := AText; + L.Timestamp := Now; + AppendLine(L); +end; +//------------------------------------------------------------------------------ +// CLEAR LINES +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.ClearLines; begin FLines.Clear; FScrollY := 0; FFollowTail := True; Redraw; end; +//------------------------------------------------------------------------------ +// SCROLL TO TAIL +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.ScrollToTail; begin FScrollY := MaxScroll; FFollowTail := True; Redraw; end; +//------------------------------------------------------------------------------ +// LINE COUNT +//------------------------------------------------------------------------------ function TOBDTerminalFMX.LineCount: Integer; -begin Result := FLines.Count; end; +begin + Result := FLines.Count; +end; +//------------------------------------------------------------------------------ +// GET LINE +//------------------------------------------------------------------------------ function TOBDTerminalFMX.GetLine(Index: Integer): TOBDTerminalLineFMX; begin if (Index < 0) or (Index >= FLines.Count) then @@ -207,6 +288,9 @@ function TOBDTerminalFMX.GetLine(Index: Integer): TOBDTerminalLineFMX; Result := FLines[Index]; end; +//------------------------------------------------------------------------------ +// SET MAX LINES +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.SetMaxLines(const AValue: Integer); begin if (AValue >= 1) and (FMaxLines <> AValue) then @@ -217,32 +301,116 @@ procedure TOBDTerminalFMX.SetMaxLines(const AValue: Integer); Redraw; end; end; + +//------------------------------------------------------------------------------ +// SET SHOW TIMESTAMPS +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.SetShowTimestamps(const AValue: Boolean); -begin if FShowTimestamps <> AValue then begin FShowTimestamps := AValue; Redraw; end; end; +begin + if FShowTimestamps <> AValue then begin FShowTimestamps := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.SetBackgroundColor(const AValue: TAlphaColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Redraw; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.SetBorderColor(const AValue: TAlphaColor); -begin if FBorderColor <> AValue then begin FBorderColor := AValue; Redraw; end; end; +begin + if FBorderColor <> AValue then begin FBorderColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.SetTextColor(const AValue: TAlphaColor); -begin if FTextColor <> AValue then begin FTextColor := AValue; Redraw; end; end; +begin + if FTextColor <> AValue then begin FTextColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET TIMESTAMP COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.SetTimestampColor(const AValue: TAlphaColor); -begin if FTimestampColor <> AValue then begin FTimestampColor := AValue; Redraw; end; end; +begin + if FTimestampColor <> AValue then begin FTimestampColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SENT COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.SetSentColor(const AValue: TAlphaColor); -begin if FSentColor <> AValue then begin FSentColor := AValue; Redraw; end; end; +begin + if FSentColor <> AValue then begin FSentColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET RECEIVED COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.SetReceivedColor(const AValue: TAlphaColor); -begin if FReceivedColor <> AValue then begin FReceivedColor := AValue; Redraw; end; end; +begin + if FReceivedColor <> AValue then begin FReceivedColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET INFO COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.SetInfoColor(const AValue: TAlphaColor); -begin if FInfoColor <> AValue then begin FInfoColor := AValue; Redraw; end; end; +begin + if FInfoColor <> AValue then begin FInfoColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET ERROR COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.SetErrorColor(const AValue: TAlphaColor); -begin if FErrorColor <> AValue then begin FErrorColor := AValue; Redraw; end; end; +begin + if FErrorColor <> AValue then begin FErrorColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET FONT SIZE +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.SetFontSize(const AValue: Integer); begin if (AValue >= 6) and (FFontSize <> AValue) then - begin FFontSize := AValue; ClampScroll; Redraw; end; + begin + FFontSize := AValue; + ClampScroll; + Redraw; + end; end; +//------------------------------------------------------------------------------ +// MOUSE WHEEL +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.MouseWheel(Shift: TShiftState; WheelDelta: Integer; - var Handled: Boolean); + var + Handled: Boolean); const WHEEL_LINE_DELTA = 120; begin @@ -253,6 +421,9 @@ procedure TOBDTerminalFMX.MouseWheel(Shift: TShiftState; WheelDelta: Integer; Handled := True; end; +//------------------------------------------------------------------------------ +// HANDLE DRAW +//------------------------------------------------------------------------------ procedure TOBDTerminalFMX.HandleDraw(ASender: TObject; const ACanvas: ISkCanvas; const ADest: TRectF; const AOpacity: Single); var diff --git a/src/Components/OBD.Terminal.pas b/src/Components/OBD.Terminal.pas index 87302c30..e272a4a0 100644 --- a/src/Components/OBD.Terminal.pas +++ b/src/Components/OBD.Terminal.pas @@ -108,25 +108,43 @@ TOBDTerminal = class(TOBDCustomControl) constructor Create(AOwner: TComponent); override; destructor Destroy; override; - /// Append a line tagged as outbound (tester → adapter). + /// + /// Append a line tagged as outbound (tester → adapter). + /// procedure LogSent(const AText: string); - /// Append a line tagged as inbound (adapter → tester). + /// + /// Append a line tagged as inbound (adapter → tester). + /// procedure LogReceived(const AText: string); - /// Append an informational line (status, mode change). + /// + /// Append an informational line (status, mode change). + /// procedure LogInfo(const AText: string); - /// Append an error line. + /// + /// Append an error line. + /// procedure LogError(const AText: string); - /// Drop every line. + /// + /// Drop every line. + /// procedure ClearLines; - /// Force the view to the bottom of the buffer. + /// + /// Force the view to the bottom of the buffer. + /// procedure ScrollToTail; - /// True while the view is following the latest line. + /// + /// True while the view is following the latest line. + /// property FollowTail: Boolean read FFollowTail; - /// Current number of buffered lines (≤ MaxLines). + /// + /// Current number of buffered lines (≤ MaxLines). + /// function LineCount: Integer; - /// Read a buffered line. Bounds-checked. + /// + /// Read a buffered line. Bounds-checked. + /// function GetLine(Index: Integer): TOBDTerminalLine; published property MaxLines: Integer read FMaxLines write SetMaxLines default TERM_DEFAULT_MAX_LINES; @@ -170,6 +188,9 @@ constructor TOBDTerminal.Create(AOwner: TComponent); Height := 240; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDTerminal.Destroy; begin FLines.Free; @@ -191,8 +212,12 @@ procedure TOBDTerminal.AppendLine(const ALine: TOBDTerminalLine); Invalidate; end; +//------------------------------------------------------------------------------ +// LOG SENT +//------------------------------------------------------------------------------ procedure TOBDTerminal.LogSent(const AText: string); -var L: TOBDTerminalLine; +var + L: TOBDTerminalLine; begin L.Direction := tdSent; L.Text := AText; @@ -200,8 +225,12 @@ procedure TOBDTerminal.LogSent(const AText: string); AppendLine(L); end; +//------------------------------------------------------------------------------ +// LOG RECEIVED +//------------------------------------------------------------------------------ procedure TOBDTerminal.LogReceived(const AText: string); -var L: TOBDTerminalLine; +var + L: TOBDTerminalLine; begin L.Direction := tdReceived; L.Text := AText; @@ -209,8 +238,12 @@ procedure TOBDTerminal.LogReceived(const AText: string); AppendLine(L); end; +//------------------------------------------------------------------------------ +// LOG INFO +//------------------------------------------------------------------------------ procedure TOBDTerminal.LogInfo(const AText: string); -var L: TOBDTerminalLine; +var + L: TOBDTerminalLine; begin L.Direction := tdInfo; L.Text := AText; @@ -218,8 +251,12 @@ procedure TOBDTerminal.LogInfo(const AText: string); AppendLine(L); end; +//------------------------------------------------------------------------------ +// LOG ERROR +//------------------------------------------------------------------------------ procedure TOBDTerminal.LogError(const AText: string); -var L: TOBDTerminalLine; +var + L: TOBDTerminalLine; begin L.Direction := tdError; L.Text := AText; @@ -227,11 +264,17 @@ procedure TOBDTerminal.LogError(const AText: string); AppendLine(L); end; +//------------------------------------------------------------------------------ +// LINE COUNT +//------------------------------------------------------------------------------ function TOBDTerminal.LineCount: Integer; begin Result := FLines.Count; end; +//------------------------------------------------------------------------------ +// GET LINE +//------------------------------------------------------------------------------ function TOBDTerminal.GetLine(Index: Integer): TOBDTerminalLine; begin if (Index < 0) or (Index >= FLines.Count) then @@ -242,6 +285,9 @@ function TOBDTerminal.GetLine(Index: Integer): TOBDTerminalLine; Result := FLines[Index]; end; +//------------------------------------------------------------------------------ +// CLEAR LINES +//------------------------------------------------------------------------------ procedure TOBDTerminal.ClearLines; begin FLines.Clear; @@ -250,6 +296,9 @@ procedure TOBDTerminal.ClearLines; Invalidate; end; +//------------------------------------------------------------------------------ +// SCROLL TO TAIL +//------------------------------------------------------------------------------ procedure TOBDTerminal.ScrollToTail; begin FScrollY := MaxScroll; @@ -265,23 +314,35 @@ function TOBDTerminal.LineHeight: Integer; Result := FFontSize + 4; end; +//------------------------------------------------------------------------------ +// VISIBLE LINE COUNT +//------------------------------------------------------------------------------ function TOBDTerminal.VisibleLineCount: Integer; begin Result := (Height - 2 * TERM_DEFAULT_PADDING) div LineHeight; if Result < 0 then Result := 0; end; +//------------------------------------------------------------------------------ +// CONTENT HEIGHT +//------------------------------------------------------------------------------ function TOBDTerminal.ContentHeight: Integer; begin Result := FLines.Count * LineHeight; end; +//------------------------------------------------------------------------------ +// MAX SCROLL +//------------------------------------------------------------------------------ function TOBDTerminal.MaxScroll: Integer; begin Result := ContentHeight - VisibleLineCount * LineHeight; if Result < 0 then Result := 0; end; +//------------------------------------------------------------------------------ +// CLAMP SCROLL +//------------------------------------------------------------------------------ procedure TOBDTerminal.ClampScroll; begin if FScrollY < 0 then FScrollY := 0; @@ -305,33 +366,99 @@ procedure TOBDTerminal.SetMaxLines(const AValue: Integer); end; end; +//------------------------------------------------------------------------------ +// SET SHOW TIMESTAMPS +//------------------------------------------------------------------------------ procedure TOBDTerminal.SetShowTimestamps(const AValue: Boolean); -begin if FShowTimestamps <> AValue then begin FShowTimestamps := AValue; Invalidate; end; end; +begin + if FShowTimestamps <> AValue then begin FShowTimestamps := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminal.SetBackgroundColor(const AValue: TColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Invalidate; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminal.SetBorderColor(const AValue: TColor); -begin if FBorderColor <> AValue then begin FBorderColor := AValue; Invalidate; end; end; +begin + if FBorderColor <> AValue then begin FBorderColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminal.SetTextColor(const AValue: TColor); -begin if FTextColor <> AValue then begin FTextColor := AValue; Invalidate; end; end; +begin + if FTextColor <> AValue then begin FTextColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET TIMESTAMP COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminal.SetTimestampColor(const AValue: TColor); -begin if FTimestampColor <> AValue then begin FTimestampColor := AValue; Invalidate; end; end; +begin + if FTimestampColor <> AValue then begin FTimestampColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SENT COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminal.SetSentColor(const AValue: TColor); -begin if FSentColor <> AValue then begin FSentColor := AValue; Invalidate; end; end; +begin + if FSentColor <> AValue then begin FSentColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET RECEIVED COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminal.SetReceivedColor(const AValue: TColor); -begin if FReceivedColor <> AValue then begin FReceivedColor := AValue; Invalidate; end; end; +begin + if FReceivedColor <> AValue then begin FReceivedColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET INFO COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminal.SetInfoColor(const AValue: TColor); -begin if FInfoColor <> AValue then begin FInfoColor := AValue; Invalidate; end; end; +begin + if FInfoColor <> AValue then begin FInfoColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET ERROR COLOR +//------------------------------------------------------------------------------ procedure TOBDTerminal.SetErrorColor(const AValue: TColor); -begin if FErrorColor <> AValue then begin FErrorColor := AValue; Invalidate; end; end; +begin + if FErrorColor <> AValue then begin FErrorColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET FONT SIZE +//------------------------------------------------------------------------------ procedure TOBDTerminal.SetFontSize(const AValue: Integer); begin if (AValue >= 6) and (FFontSize <> AValue) then diff --git a/src/Components/OBD.Touch.Header.pas b/src/Components/OBD.Touch.Header.pas index b36137a3..d17f7307 100755 --- a/src/Components/OBD.Touch.Header.pas +++ b/src/Components/OBD.Touch.Header.pas @@ -2854,7 +2854,10 @@ TTabOverlay = record 1: begin BodyFromColor := BackButton.HotColor.FromColor; BodyToColor := BackButton.HotColor.ToColor; end; 2: begin BodyFromColor := BackButton.PressedColor.FromColor; BodyToColor := BackButton.PressedColor.ToColor; end; else - begin BodyFromColor := BackButton.NormalColor.FromColor; BodyToColor := BackButton.NormalColor.ToColor; end; + begin + BodyFromColor := BackButton.NormalColor.FromColor; + BodyToColor := BackButton.NormalColor.ToColor; + end; end; end; @@ -2916,7 +2919,10 @@ TTabOverlay = record 1: begin BodyFromColor := ActionButton.HotColor.FromColor; BodyToColor := ActionButton.HotColor.ToColor; end; 2: begin BodyFromColor := ActionButton.PressedColor.FromColor; BodyToColor := ActionButton.PressedColor.ToColor; end; else - begin BodyFromColor := ActionButton.NormalColor.FromColor; BodyToColor := ActionButton.NormalColor.ToColor; end; + begin + BodyFromColor := ActionButton.NormalColor.FromColor; + BodyToColor := ActionButton.NormalColor.ToColor; + end; end; end; @@ -3099,7 +3105,8 @@ TTabOverlay = record begin if BackHasImage and Assigned(BackButton.Image.Graphic) then begin - var BackImage := GraphicToSkImage(BackButton.Image.Graphic); + var + BackImage := GraphicToSkImage(BackButton.Image.Graphic); if Assigned(BackImage) then Canvas.DrawImageRect(BackImage, TRectF.Create( @@ -3117,7 +3124,8 @@ TTabOverlay = record begin if ActionHasImage and Assigned(ActionButton.Image.Graphic) then begin - var ActionImage := GraphicToSkImage(ActionButton.Image.Graphic); + var + ActionImage := GraphicToSkImage(ActionButton.Image.Graphic); if Assigned(ActionImage) then Canvas.DrawImageRect(ActionImage, TRectF.Create( @@ -3138,7 +3146,8 @@ TTabOverlay = record if not TabOverlays[I].Visible then Continue; if Assigned(TabOverlays[I].Image) then begin - var TabImage := GraphicToSkImage(TabOverlays[I].Image); + var + TabImage := GraphicToSkImage(TabOverlays[I].Image); if Assigned(TabImage) then Canvas.DrawImageRect(TabImage, TRectF.Create( diff --git a/src/Components/OBD.Touch.Subheader.pas b/src/Components/OBD.Touch.Subheader.pas index d7a7e0e6..5bad1a6c 100755 --- a/src/Components/OBD.Touch.Subheader.pas +++ b/src/Components/OBD.Touch.Subheader.pas @@ -322,6 +322,9 @@ procedure TOBDTouchSubheaderBackground.SetFromColor(Value: TColor); end; end; +//------------------------------------------------------------------------------ +// SET TO COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderBackground.SetToColor(Value: TColor); begin if FToColor <> Value then @@ -367,6 +370,9 @@ procedure TOBDTouchSubheaderBorder.SetFromColor(Value: TColor); end; end; +//------------------------------------------------------------------------------ +// SET TO COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderBorder.SetToColor(Value: TColor); begin if FToColor <> Value then @@ -376,6 +382,9 @@ procedure TOBDTouchSubheaderBorder.SetToColor(Value: TColor); end; end; +//------------------------------------------------------------------------------ +// SET HEIGHT +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderBorder.SetHeight(Value: Integer); begin if FHeight <> Value then @@ -423,6 +432,9 @@ procedure TOBDTouchSubheaderBatteryIndicator.SetVisible(Value: Boolean); end; end; +//------------------------------------------------------------------------------ +// SET SIZE +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderBatteryIndicator.SetSize(Value: Integer); begin if FSize <> Value then @@ -432,6 +444,9 @@ procedure TOBDTouchSubheaderBatteryIndicator.SetSize(Value: Integer); end; end; +//------------------------------------------------------------------------------ +// SET FROM COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderBatteryIndicator.SetFromColor(Value: TColor); begin if FFromColor <> Value then @@ -441,6 +456,9 @@ procedure TOBDTouchSubheaderBatteryIndicator.SetFromColor(Value: TColor); end; end; +//------------------------------------------------------------------------------ +// SET TO COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderBatteryIndicator.SetToColor(Value: TColor); begin if FToColor <> Value then @@ -450,6 +468,9 @@ procedure TOBDTouchSubheaderBatteryIndicator.SetToColor(Value: TColor); end; end; +//------------------------------------------------------------------------------ +// SET BORDER WIDTH +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderBatteryIndicator.SetBorderWidth(Value: Single); begin if FBorderWidth <> Value then @@ -459,6 +480,9 @@ procedure TOBDTouchSubheaderBatteryIndicator.SetBorderWidth(Value: Single); end; end; +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderBatteryIndicator.SetBorderColor(Value: TColor); begin if FBorderColor <> Value then @@ -468,11 +492,17 @@ procedure TOBDTouchSubheaderBatteryIndicator.SetBorderColor(Value: TColor); end; end; +//------------------------------------------------------------------------------ +// SET FONT +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderBatteryIndicator.SetFont(Value: TFont); begin FFont.Assign(Value); end; +//------------------------------------------------------------------------------ +// SET FORMAT +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderBatteryIndicator.SetFormat(const Value: string); begin if FFormat <> Value then @@ -482,6 +512,9 @@ procedure TOBDTouchSubheaderBatteryIndicator.SetFormat(const Value: string); end; end; +//------------------------------------------------------------------------------ +// SET VOLTAGE +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderBatteryIndicator.SetVoltage(Value: Single); begin if FVoltage <> Value then @@ -491,6 +524,9 @@ procedure TOBDTouchSubheaderBatteryIndicator.SetVoltage(Value: Single); end; end; +//------------------------------------------------------------------------------ +// FONT CHANGED +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderBatteryIndicator.FontChanged(Sender: TObject); begin if Assigned(FOnChange) then FOnChange(Self); @@ -556,6 +592,9 @@ procedure TOBDTouchSubheaderVciIndicator.SetVisible(Value: Boolean); end; end; +//------------------------------------------------------------------------------ +// SET SIZE +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderVciIndicator.SetSize(Value: Integer); begin if FSize <> Value then @@ -565,6 +604,9 @@ procedure TOBDTouchSubheaderVciIndicator.SetSize(Value: Integer); end; end; +//------------------------------------------------------------------------------ +// SET FROM COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderVciIndicator.SetFromColor(Value: TColor); begin if FFromColor <> Value then @@ -574,6 +616,9 @@ procedure TOBDTouchSubheaderVciIndicator.SetFromColor(Value: TColor); end; end; +//------------------------------------------------------------------------------ +// SET TO COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderVciIndicator.SetToColor(Value: TColor); begin if FToColor <> Value then @@ -583,6 +628,9 @@ procedure TOBDTouchSubheaderVciIndicator.SetToColor(Value: TColor); end; end; +//------------------------------------------------------------------------------ +// SET BORDER WIDTH +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderVciIndicator.SetBorderWidth(Value: Single); begin if FBorderWidth <> Value then @@ -592,6 +640,9 @@ procedure TOBDTouchSubheaderVciIndicator.SetBorderWidth(Value: Single); end; end; +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderVciIndicator.SetBorderColor(Value: TColor); begin if FBorderColor <> Value then @@ -601,11 +652,17 @@ procedure TOBDTouchSubheaderVciIndicator.SetBorderColor(Value: TColor); end; end; +//------------------------------------------------------------------------------ +// SET FONT +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderVciIndicator.SetFont(Value: TFont); begin FFont.Assign(Value); end; +//------------------------------------------------------------------------------ +// SET CAPTION +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderVciIndicator.SetCaption(const Value: string); begin if FCaption <> Value then @@ -615,6 +672,9 @@ procedure TOBDTouchSubheaderVciIndicator.SetCaption(const Value: string); end; end; +//------------------------------------------------------------------------------ +// FONT CHANGED +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderVciIndicator.FontChanged(Sender: TObject); begin if Assigned(FOnChange) then FOnChange(Self); @@ -678,6 +738,9 @@ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetVisible(Value: Boolea end; end; +//------------------------------------------------------------------------------ +// SET SIZE +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetSize(Value: Integer); begin if FSize <> Value then @@ -687,6 +750,9 @@ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetSize(Value: Integer); end; end; +//------------------------------------------------------------------------------ +// SET FROM COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetFromColor(Value: TColor); begin if FFromColor <> Value then @@ -696,6 +762,9 @@ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetFromColor(Value: TCol end; end; +//------------------------------------------------------------------------------ +// SET TO COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetToColor(Value: TColor); begin if FToColor <> Value then @@ -705,6 +774,9 @@ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetToColor(Value: TColor end; end; +//------------------------------------------------------------------------------ +// SET BORDER WIDTH +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetBorderWidth(Value: Single); begin if FBorderWidth <> Value then @@ -714,6 +786,9 @@ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetBorderWidth(Value: Si end; end; +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetBorderColor(Value: TColor); begin if FBorderColor <> Value then @@ -723,11 +798,17 @@ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetBorderColor(Value: TC end; end; +//------------------------------------------------------------------------------ +// SET FONT +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetFont(Value: TFont); begin FFont.Assign(Value); end; +//------------------------------------------------------------------------------ +// SET CAPTION +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetCaption(const Value: string); begin if FCaption <> Value then @@ -737,6 +818,9 @@ procedure TOBDTouchSubheaderInternetConnectionIndicator.SetCaption(const Value: end; end; +//------------------------------------------------------------------------------ +// FONT CHANGED +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderInternetConnectionIndicator.FontChanged(Sender: TObject); begin if Assigned(FOnChange) then FOnChange(Self); @@ -800,6 +884,9 @@ procedure TOBDTouchSubheaderProtocolIndicator.SetVisible(Value: Boolean); end; end; +//------------------------------------------------------------------------------ +// SET SIZE +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderProtocolIndicator.SetSize(Value: Integer); begin if FSize <> Value then @@ -809,6 +896,9 @@ procedure TOBDTouchSubheaderProtocolIndicator.SetSize(Value: Integer); end; end; +//------------------------------------------------------------------------------ +// SET FROM COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderProtocolIndicator.SetFromColor(Value: TColor); begin if FFromColor <> Value then @@ -818,6 +908,9 @@ procedure TOBDTouchSubheaderProtocolIndicator.SetFromColor(Value: TColor); end; end; +//------------------------------------------------------------------------------ +// SET TO COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderProtocolIndicator.SetToColor(Value: TColor); begin if FToColor <> Value then @@ -827,6 +920,9 @@ procedure TOBDTouchSubheaderProtocolIndicator.SetToColor(Value: TColor); end; end; +//------------------------------------------------------------------------------ +// SET BORDER WIDTH +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderProtocolIndicator.SetBorderWidth(Value: Single); begin if FBorderWidth <> Value then @@ -836,6 +932,9 @@ procedure TOBDTouchSubheaderProtocolIndicator.SetBorderWidth(Value: Single); end; end; +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderProtocolIndicator.SetBorderColor(Value: TColor); begin if FBorderColor <> Value then @@ -845,11 +944,17 @@ procedure TOBDTouchSubheaderProtocolIndicator.SetBorderColor(Value: TColor); end; end; +//------------------------------------------------------------------------------ +// SET FONT +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderProtocolIndicator.SetFont(Value: TFont); begin FFont.Assign(Value); end; +//------------------------------------------------------------------------------ +// SET CAPTION +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderProtocolIndicator.SetCaption(const Value: string); begin if FCaption <> Value then @@ -859,6 +964,9 @@ procedure TOBDTouchSubheaderProtocolIndicator.SetCaption(const Value: string); end; end; +//------------------------------------------------------------------------------ +// FONT CHANGED +//------------------------------------------------------------------------------ procedure TOBDTouchSubheaderProtocolIndicator.FontChanged(Sender: TObject); begin if Assigned(FOnChange) then FOnChange(Self); @@ -918,31 +1026,49 @@ procedure TOBDTouchSubheader.SetBackground(Value: TOBDTouchSubheaderBackground); FBackground.Assign(Value); end; +//------------------------------------------------------------------------------ +// SET BORDER +//------------------------------------------------------------------------------ procedure TOBDTouchSubheader.SetBorder(Value: TOBDTouchSubheaderBorder); begin FBorder.Assign(Value); end; +//------------------------------------------------------------------------------ +// SET BATTERY INDICATOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheader.SetBatteryIndicator(Value: TOBDTouchSubheaderBatteryIndicator); begin FBatteryIndicator.Assign(Value); end; +//------------------------------------------------------------------------------ +// SET VCI INDICATOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheader.SetVciIndicator(Value: TOBDTouchSubheaderVciIndicator); begin FVciIndicator.Assign(Value); end; +//------------------------------------------------------------------------------ +// SET INTERNET CONNECTION INDICATOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheader.SetInternetConnectionIndicator(Value: TOBDTouchSubheaderInternetConnectionIndicator); begin FInternetConnectionIndicator.Assign(Value); end; +//------------------------------------------------------------------------------ +// SET PROTOCOL INDICATOR +//------------------------------------------------------------------------------ procedure TOBDTouchSubheader.SetProtocolIndicator(Value: TOBDTouchSubheaderProtocolIndicator); begin FProtocolIndicator.Assign(Value); end; +//------------------------------------------------------------------------------ +// SET AUTO APPLY CONNECTION DETAILS +//------------------------------------------------------------------------------ procedure TOBDTouchSubheader.SetAutoApplyConnectionDetails(Value: Boolean); begin if FAutoApplyConnectionDetails <> Value then @@ -953,6 +1079,9 @@ procedure TOBDTouchSubheader.SetAutoApplyConnectionDetails(Value: Boolean); end; end; +//------------------------------------------------------------------------------ +// SET CONNECTION COMPONENT +//------------------------------------------------------------------------------ procedure TOBDTouchSubheader.SetConnectionComponent(Value: TOBDConnectionComponent); begin if FConnectionComponent <> Value then diff --git a/src/Components/OBD.TrendGraph.FMX.pas b/src/Components/OBD.TrendGraph.FMX.pas index 9f9ae098..4934589d 100644 --- a/src/Components/OBD.TrendGraph.FMX.pas +++ b/src/Components/OBD.TrendGraph.FMX.pas @@ -28,7 +28,9 @@ interface TGFMX_DEFAULT_TEXT = TAlphaColors.White; type - /// One series for the FMX graph. Mirrors the VCL `TOBDTrendSeries`. + /// + /// One series for the FMX graph. Mirrors the VCL `TOBDTrendSeries`. + /// TOBDTrendSeriesFMX = class strict private FName: string; @@ -112,6 +114,10 @@ implementation //============================================================================== // TOBDTrendSeriesFMX — same ring-buffer mechanics as the VCL series. //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDTrendSeriesFMX.Create(const AName: string; AColor: TAlphaColor; AMin, AMax: Single; ACapacity: Integer); begin @@ -124,6 +130,9 @@ constructor TOBDTrendSeriesFMX.Create(const AName: string; AColor: TAlphaColor; SetLength(FValues, ACapacity); end; +//------------------------------------------------------------------------------ +// PUSH +//------------------------------------------------------------------------------ procedure TOBDTrendSeriesFMX.Push(const AValue: Single); begin FValues[FHead] := AValue; @@ -131,9 +140,18 @@ procedure TOBDTrendSeriesFMX.Push(const AValue: Single); if FCount < Length(FValues) then Inc(FCount); end; +//------------------------------------------------------------------------------ +// CLEAR +//------------------------------------------------------------------------------ procedure TOBDTrendSeriesFMX.Clear; -begin FHead := 0; FCount := 0; end; +begin + FHead := 0; + FCount := 0; +end; +//------------------------------------------------------------------------------ +// RESIZE +//------------------------------------------------------------------------------ procedure TOBDTrendSeriesFMX.Resize(NewCapacity: Integer); var Old: TArray; @@ -151,20 +169,33 @@ procedure TOBDTrendSeriesFMX.Resize(NewCapacity: Integer); for I := ReadIndex to OldCount - 1 do Push(Old[I]); end; +//------------------------------------------------------------------------------ +// GET VALUE +//------------------------------------------------------------------------------ function TOBDTrendSeriesFMX.GetValue(LogicalIndex: Integer): Single; -var Phys: Integer; +var + Phys: Integer; begin if (LogicalIndex < 0) or (LogicalIndex >= FCount) then Exit(FMin); Phys := (FHead - FCount + LogicalIndex + Length(FValues)) mod Length(FValues); Result := FValues[Phys]; end; +//------------------------------------------------------------------------------ +// GET CAPACITY +//------------------------------------------------------------------------------ function TOBDTrendSeriesFMX.GetCapacity: Integer; -begin Result := Length(FValues); end; +begin + Result := Length(FValues); +end; //============================================================================== // TOBDTrendGraphFMX //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDTrendGraphFMX.Create(AOwner: TComponent); begin inherited Create(AOwner); @@ -184,18 +215,31 @@ constructor TOBDTrendGraphFMX.Create(AOwner: TComponent); OnDraw := HandleDraw; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDTrendGraphFMX.Destroy; -begin FSeries.Free; inherited; end; +begin + FSeries.Free; + inherited; +end; +//------------------------------------------------------------------------------ +// ADD SERIES +//------------------------------------------------------------------------------ function TOBDTrendGraphFMX.AddSeries(const AName: string; AColor: TAlphaColor; AMin, AMax: Single): Integer; -var S: TOBDTrendSeriesFMX; +var + S: TOBDTrendSeriesFMX; begin S := TOBDTrendSeriesFMX.Create(AName, AColor, AMin, AMax, FMaxSamples); Result := FSeries.Add(S); Redraw; end; +//------------------------------------------------------------------------------ +// REMOVE SERIES +//------------------------------------------------------------------------------ procedure TOBDTrendGraphFMX.RemoveSeries(Index: Integer); begin if (Index < 0) or (Index >= FSeries.Count) then Exit; @@ -203,13 +247,20 @@ procedure TOBDTrendGraphFMX.RemoveSeries(Index: Integer); Redraw; end; +//------------------------------------------------------------------------------ +// CLEAR SAMPLES +//------------------------------------------------------------------------------ procedure TOBDTrendGraphFMX.ClearSamples; -var S: TOBDTrendSeriesFMX; +var + S: TOBDTrendSeriesFMX; begin for S in FSeries do S.Clear; Redraw; end; +//------------------------------------------------------------------------------ +// PUSH VALUE +//------------------------------------------------------------------------------ procedure TOBDTrendGraphFMX.PushValue(SeriesIndex: Integer; const AValue: Single); begin if (SeriesIndex < 0) or (SeriesIndex >= FSeries.Count) then Exit; @@ -217,14 +268,28 @@ procedure TOBDTrendGraphFMX.PushValue(SeriesIndex: Integer; const AValue: Single Redraw; end; +//------------------------------------------------------------------------------ +// GET SERIES COUNT +//------------------------------------------------------------------------------ function TOBDTrendGraphFMX.GetSeriesCount: Integer; -begin Result := FSeries.Count; end; +begin + Result := FSeries.Count; +end; +//------------------------------------------------------------------------------ +// GET SERIES +//------------------------------------------------------------------------------ function TOBDTrendGraphFMX.GetSeries(Index: Integer): TOBDTrendSeriesFMX; -begin Result := FSeries[Index]; end; +begin + Result := FSeries[Index]; +end; +//------------------------------------------------------------------------------ +// SET MAX SAMPLES +//------------------------------------------------------------------------------ procedure TOBDTrendGraphFMX.SetMaxSamples(const AValue: Integer); -var S: TOBDTrendSeriesFMX; +var + S: TOBDTrendSeriesFMX; begin if (AValue >= 2) and (FMaxSamples <> AValue) then begin @@ -234,23 +299,89 @@ procedure TOBDTrendGraphFMX.SetMaxSamples(const AValue: Integer); end; end; +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDTrendGraphFMX.SetBackgroundColor(const AValue: TAlphaColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Redraw; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET GRID COLOR +//------------------------------------------------------------------------------ procedure TOBDTrendGraphFMX.SetGridColor(const AValue: TAlphaColor); -begin if FGridColor <> AValue then begin FGridColor := AValue; Redraw; end; end; +begin + if FGridColor <> AValue then begin FGridColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDTrendGraphFMX.SetBorderColor(const AValue: TAlphaColor); -begin if FBorderColor <> AValue then begin FBorderColor := AValue; Redraw; end; end; +begin + if FBorderColor <> AValue then begin FBorderColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDTrendGraphFMX.SetTextColor(const AValue: TAlphaColor); -begin if FTextColor <> AValue then begin FTextColor := AValue; Redraw; end; end; +begin + if FTextColor <> AValue then begin FTextColor := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SHOW GRID +//------------------------------------------------------------------------------ procedure TOBDTrendGraphFMX.SetShowGrid(const AValue: Boolean); -begin if FShowGrid <> AValue then begin FShowGrid := AValue; Redraw; end; end; +begin + if FShowGrid <> AValue then begin FShowGrid := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SHOW LEGEND +//------------------------------------------------------------------------------ procedure TOBDTrendGraphFMX.SetShowLegend(const AValue: Boolean); -begin if FShowLegend <> AValue then begin FShowLegend := AValue; Redraw; end; end; +begin + if FShowLegend <> AValue then begin FShowLegend := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET SHOW BORDER +//------------------------------------------------------------------------------ procedure TOBDTrendGraphFMX.SetShowBorder(const AValue: Boolean); -begin if FShowBorder <> AValue then begin FShowBorder := AValue; Redraw; end; end; +begin + if FShowBorder <> AValue then begin FShowBorder := AValue; + Redraw; + end; +end; + +//------------------------------------------------------------------------------ +// SET STROKE WIDTH +//------------------------------------------------------------------------------ procedure TOBDTrendGraphFMX.SetStrokeWidth(const AValue: Single); -begin if (AValue > 0) and (FStrokeWidth <> AValue) then begin FStrokeWidth := AValue; Redraw; end; end; +begin + if (AValue > 0) and (FStrokeWidth <> AValue) then begin FStrokeWidth := AValue; + Redraw; + end; +end; +//------------------------------------------------------------------------------ +// HANDLE DRAW +//------------------------------------------------------------------------------ procedure TOBDTrendGraphFMX.HandleDraw(ASender: TObject; const ACanvas: ISkCanvas; const ADest: TRectF; const AOpacity: Single); var diff --git a/src/Components/OBD.TrendGraph.pas b/src/Components/OBD.TrendGraph.pas index 8c5b240e..ac559221 100644 --- a/src/Components/OBD.TrendGraph.pas +++ b/src/Components/OBD.TrendGraph.pas @@ -63,22 +63,34 @@ TOBDTrendSeries = class constructor Create(const AName: string; AColor: TColor; AMin, AMax: Single; ACapacity: Integer); - /// Push a new sample. The oldest sample drops off when full. + /// + /// Push a new sample. The oldest sample drops off when full. + /// procedure Push(const AValue: Single); - /// Reset the buffer to empty. + /// + /// Reset the buffer to empty. + /// procedure Clear; - /// Re-allocate the ring buffer to a new capacity (preserves recent samples). + /// + /// Re-allocate the ring buffer to a new capacity (preserves recent samples). + /// procedure Resize(NewCapacity: Integer); property Name: string read FName write FName; property Color: TColor read FColor write FColor; property Min: Single read FMin write FMin; property Max: Single read FMax write FMax; - /// Number of valid samples (≤ Capacity). + /// + /// Number of valid samples (≤ Capacity). + /// property Count: Integer read FCount; - /// Ring-buffer capacity. + /// + /// Ring-buffer capacity. + /// property Capacity: Integer read GetCapacity; - /// Sample at logical index 0 = oldest, Count-1 = newest. + /// + /// Sample at logical index 0 = oldest, Count-1 = newest. + /// property Values[LogicalIndex: Integer]: Single read GetValue; end; @@ -127,18 +139,26 @@ TOBDTrendGraph = class(TOBDCustomControl) /// function AddSeries(const AName: string; AColor: TColor; AMin, AMax: Single): Integer; - /// Remove the series at the given index. + /// + /// Remove the series at the given index. + /// procedure RemoveSeries(Index: Integer); - /// Drop all samples from every series (does not remove the series). + /// + /// Drop all samples from every series (does not remove the series). + /// procedure ClearSamples; - /// Push a value onto the named series. + /// + /// Push a value onto the named series. + /// procedure PushValue(SeriesIndex: Integer; const AValue: Single); property SeriesCount: Integer read GetSeriesCount; property Series[Index: Integer]: TOBDTrendSeries read GetSeries; published - /// Number of samples retained per series. + /// + /// Number of samples retained per series. + /// property MaxSamples: Integer read FMaxSamples write SetMaxSamples default TG_DEFAULT_MAX_SAMPLES; property BackgroundColor: TColor read FBackgroundColor write SetBackgroundColor default TG_DEFAULT_BACKGROUND; property GridColor: TColor read FGridColor write SetGridColor default TG_DEFAULT_GRID_COLOR; @@ -156,6 +176,9 @@ implementation // TOBDTrendSeries //============================================================================== +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDTrendSeries.Create(const AName: string; AColor: TColor; AMin, AMax: Single; ACapacity: Integer); begin @@ -170,6 +193,9 @@ constructor TOBDTrendSeries.Create(const AName: string; AColor: TColor; FCount := 0; end; +//------------------------------------------------------------------------------ +// PUSH +//------------------------------------------------------------------------------ procedure TOBDTrendSeries.Push(const AValue: Single); begin FValues[FHead] := AValue; @@ -177,12 +203,18 @@ procedure TOBDTrendSeries.Push(const AValue: Single); if FCount < Length(FValues) then Inc(FCount); end; +//------------------------------------------------------------------------------ +// CLEAR +//------------------------------------------------------------------------------ procedure TOBDTrendSeries.Clear; begin FHead := 0; FCount := 0; end; +//------------------------------------------------------------------------------ +// RESIZE +//------------------------------------------------------------------------------ procedure TOBDTrendSeries.Resize(NewCapacity: Integer); var OldValues: TArray; @@ -209,11 +241,17 @@ procedure TOBDTrendSeries.Resize(NewCapacity: Integer); Push(OldValues[I]); end; +//------------------------------------------------------------------------------ +// GET CAPACITY +//------------------------------------------------------------------------------ function TOBDTrendSeries.GetCapacity: Integer; begin Result := Length(FValues); end; +//------------------------------------------------------------------------------ +// GET VALUE +//------------------------------------------------------------------------------ function TOBDTrendSeries.GetValue(LogicalIndex: Integer): Single; var PhysIndex: Integer; @@ -229,6 +267,9 @@ function TOBDTrendSeries.GetValue(LogicalIndex: Integer): Single; // TOBDTrendGraph //============================================================================== +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDTrendGraph.Create(AOwner: TComponent); begin inherited Create(AOwner); @@ -247,6 +288,9 @@ constructor TOBDTrendGraph.Create(AOwner: TComponent); Height := 180; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDTrendGraph.Destroy; begin FSeries.Free; @@ -266,6 +310,9 @@ function TOBDTrendGraph.AddSeries(const AName: string; AColor: TColor; Invalidate; end; +//------------------------------------------------------------------------------ +// REMOVE SERIES +//------------------------------------------------------------------------------ procedure TOBDTrendGraph.RemoveSeries(Index: Integer); begin if (Index < 0) or (Index >= FSeries.Count) then Exit; @@ -273,6 +320,9 @@ procedure TOBDTrendGraph.RemoveSeries(Index: Integer); Invalidate; end; +//------------------------------------------------------------------------------ +// CLEAR SAMPLES +//------------------------------------------------------------------------------ procedure TOBDTrendGraph.ClearSamples; var S: TOBDTrendSeries; @@ -281,6 +331,9 @@ procedure TOBDTrendGraph.ClearSamples; Invalidate; end; +//------------------------------------------------------------------------------ +// PUSH VALUE +//------------------------------------------------------------------------------ procedure TOBDTrendGraph.PushValue(SeriesIndex: Integer; const AValue: Single); begin if (SeriesIndex < 0) or (SeriesIndex >= FSeries.Count) then Exit; @@ -288,11 +341,17 @@ procedure TOBDTrendGraph.PushValue(SeriesIndex: Integer; const AValue: Single); Invalidate; end; +//------------------------------------------------------------------------------ +// GET SERIES COUNT +//------------------------------------------------------------------------------ function TOBDTrendGraph.GetSeriesCount: Integer; begin Result := FSeries.Count; end; +//------------------------------------------------------------------------------ +// GET SERIES +//------------------------------------------------------------------------------ function TOBDTrendGraph.GetSeries(Index: Integer): TOBDTrendSeries; begin Result := FSeries[Index]; @@ -313,29 +372,85 @@ procedure TOBDTrendGraph.SetMaxSamples(const AValue: Integer); end; end; +//------------------------------------------------------------------------------ +// SET BACKGROUND COLOR +//------------------------------------------------------------------------------ procedure TOBDTrendGraph.SetBackgroundColor(const AValue: TColor); -begin if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; Invalidate; end; end; +begin + if FBackgroundColor <> AValue then begin FBackgroundColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET GRID COLOR +//------------------------------------------------------------------------------ procedure TOBDTrendGraph.SetGridColor(const AValue: TColor); -begin if FGridColor <> AValue then begin FGridColor := AValue; Invalidate; end; end; +begin + if FGridColor <> AValue then begin FGridColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET BORDER COLOR +//------------------------------------------------------------------------------ procedure TOBDTrendGraph.SetBorderColor(const AValue: TColor); -begin if FBorderColor <> AValue then begin FBorderColor := AValue; Invalidate; end; end; +begin + if FBorderColor <> AValue then begin FBorderColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET TEXT COLOR +//------------------------------------------------------------------------------ procedure TOBDTrendGraph.SetTextColor(const AValue: TColor); -begin if FTextColor <> AValue then begin FTextColor := AValue; Invalidate; end; end; +begin + if FTextColor <> AValue then begin FTextColor := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SHOW GRID +//------------------------------------------------------------------------------ procedure TOBDTrendGraph.SetShowGrid(const AValue: Boolean); -begin if FShowGrid <> AValue then begin FShowGrid := AValue; Invalidate; end; end; +begin + if FShowGrid <> AValue then begin FShowGrid := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SHOW LEGEND +//------------------------------------------------------------------------------ procedure TOBDTrendGraph.SetShowLegend(const AValue: Boolean); -begin if FShowLegend <> AValue then begin FShowLegend := AValue; Invalidate; end; end; +begin + if FShowLegend <> AValue then begin FShowLegend := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET SHOW BORDER +//------------------------------------------------------------------------------ procedure TOBDTrendGraph.SetShowBorder(const AValue: Boolean); -begin if FShowBorder <> AValue then begin FShowBorder := AValue; Invalidate; end; end; +begin + if FShowBorder <> AValue then begin FShowBorder := AValue; + Invalidate; + end; +end; +//------------------------------------------------------------------------------ +// SET STROKE WIDTH +//------------------------------------------------------------------------------ procedure TOBDTrendGraph.SetStrokeWidth(const AValue: Single); -begin if (AValue > 0) and (FStrokeWidth <> AValue) then begin FStrokeWidth := AValue; Invalidate; end; end; +begin + if (AValue > 0) and (FStrokeWidth <> AValue) then begin FStrokeWidth := AValue; + Invalidate; + end; +end; //------------------------------------------------------------------------------ //------------------------------------------------------------------------------ diff --git a/src/Connection/OBD.Connection.Async.pas b/src/Connection/OBD.Connection.Async.pas index c6915a5d..672c3752 100644 --- a/src/Connection/OBD.Connection.Async.pas +++ b/src/Connection/OBD.Connection.Async.pas @@ -45,7 +45,9 @@ TOBDAsyncRequest = class constructor Create(const APromise: IOBDPromise; const ATerminator: string; ATimeoutMs: Cardinal; const AToken: IOBDCancellationToken); - /// Append received bytes; returns True when terminator seen. + /// + /// Append received bytes; returns True when terminator seen. + /// function Feed(const Bytes: TBytes): Boolean; function ExpiredAt(Tick: UInt64): Boolean; procedure ResolveTimeout; @@ -115,7 +117,9 @@ TOBDConnectionAsync = class /// function PendingCount: Integer; - /// The wrapped connection — owned by the caller. + /// + /// The wrapped connection — owned by the caller. + /// property Connection: IOBDConnection read FConnection; end; @@ -127,6 +131,10 @@ implementation //============================================================================== // TOBDAsyncRequest //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDAsyncRequest.Create(const APromise: IOBDPromise; const ATerminator: string; ATimeoutMs: Cardinal; const AToken: IOBDCancellationToken); @@ -141,6 +149,9 @@ constructor TOBDAsyncRequest.Create(const APromise: IOBDPromise; FDeadlineTick := GetTickCount64 + ATimeoutMs; end; +//------------------------------------------------------------------------------ +// FEED +//------------------------------------------------------------------------------ function TOBDAsyncRequest.Feed(const Bytes: TBytes): Boolean; var Chunk: string; @@ -170,16 +181,25 @@ function TOBDAsyncRequest.Feed(const Bytes: TBytes): Boolean; end; end; +//------------------------------------------------------------------------------ +// EXPIRED AT +//------------------------------------------------------------------------------ function TOBDAsyncRequest.ExpiredAt(Tick: UInt64): Boolean; begin Result := (FDeadlineTick <> High(UInt64)) and (Tick >= FDeadlineTick); end; +//------------------------------------------------------------------------------ +// RESOLVE TIMEOUT +//------------------------------------------------------------------------------ procedure TOBDAsyncRequest.ResolveTimeout; begin FPromise.SetError(EOBDFutureTimeout.Create('Adapter response timed out')); end; +//------------------------------------------------------------------------------ +// RESOLVE CANCELLED +//------------------------------------------------------------------------------ procedure TOBDAsyncRequest.ResolveCancelled; begin FPromise.SignalCancelled; @@ -188,6 +208,10 @@ procedure TOBDAsyncRequest.ResolveCancelled; //============================================================================== // TOBDConnectionAsync //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDConnectionAsync.Create(const AConnection: IOBDConnection); begin inherited Create; @@ -199,6 +223,9 @@ constructor TOBDConnectionAsync.Create(const AConnection: IOBDConnection); InstallReceiver; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDConnectionAsync.Destroy; begin CancelAll; @@ -208,6 +235,9 @@ destructor TOBDConnectionAsync.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// INSTALL RECEIVER +//------------------------------------------------------------------------------ procedure TOBDConnectionAsync.InstallReceiver; begin // Preserve any handler the caller already attached so this wrapper can @@ -217,6 +247,9 @@ procedure TOBDConnectionAsync.InstallReceiver; FInstalledOnReceive := True; end; +//------------------------------------------------------------------------------ +// UNINSTALL RECEIVER +//------------------------------------------------------------------------------ procedure TOBDConnectionAsync.UninstallReceiver; begin if not FInstalledOnReceive then Exit; @@ -225,6 +258,9 @@ procedure TOBDConnectionAsync.UninstallReceiver; FInstalledOnReceive := False; end; +//------------------------------------------------------------------------------ +// HANDLE RECEIVE +//------------------------------------------------------------------------------ procedure TOBDConnectionAsync.HandleReceive(Sender: TObject; DataPtr: Pointer; DataSize: DWORD); var @@ -272,6 +308,9 @@ procedure TOBDConnectionAsync.HandleReceive(Sender: TObject; DataPtr: Pointer; SweepDeadlines; end; +//------------------------------------------------------------------------------ +// SWEEP DEADLINES +//------------------------------------------------------------------------------ procedure TOBDConnectionAsync.SweepDeadlines; var Now: UInt64; @@ -304,6 +343,9 @@ procedure TOBDConnectionAsync.SweepDeadlines; end; end; +//------------------------------------------------------------------------------ +// ENQUEUE REQUEST +//------------------------------------------------------------------------------ function TOBDConnectionAsync.EnqueueRequest(const Terminator: string; TimeoutMs: Cardinal; const Token: IOBDCancellationToken): IOBDFuture; var @@ -322,6 +364,9 @@ function TOBDConnectionAsync.EnqueueRequest(const Terminator: string; Result := Promise; end; +//------------------------------------------------------------------------------ +// SEND ASYNC +//------------------------------------------------------------------------------ function TOBDConnectionAsync.SendAsync(const Cmd: string; TimeoutMs: Cardinal; const Terminator: string; const Token: IOBDCancellationToken): IOBDFuture; @@ -358,6 +403,9 @@ function TOBDConnectionAsync.SendAsync(const Cmd: string; end; end; +//------------------------------------------------------------------------------ +// ATASYNC +//------------------------------------------------------------------------------ function TOBDConnectionAsync.ATAsync(const AT: string; TimeoutMs: Cardinal; const Token: IOBDCancellationToken): IOBDFuture; var @@ -370,12 +418,18 @@ function TOBDConnectionAsync.ATAsync(const AT: string; Result := SendAsync(Cmd, TimeoutMs, ELM_PROMPT, Token); end; +//------------------------------------------------------------------------------ +// OBDASYNC +//------------------------------------------------------------------------------ function TOBDConnectionAsync.OBDAsync(const HexCommand: string; TimeoutMs: Cardinal; const Token: IOBDCancellationToken): IOBDFuture; begin Result := SendAsync(HexCommand, TimeoutMs, ELM_PROMPT, Token); end; +//------------------------------------------------------------------------------ +// CANCEL ALL +//------------------------------------------------------------------------------ procedure TOBDConnectionAsync.CancelAll; var Req: TOBDAsyncRequest; @@ -392,6 +446,9 @@ procedure TOBDConnectionAsync.CancelAll; end; end; +//------------------------------------------------------------------------------ +// PENDING COUNT +//------------------------------------------------------------------------------ function TOBDConnectionAsync.PendingCount: Integer; begin FQueueLock.Enter; diff --git a/src/Connection/OBD.Connection.BLE.pas b/src/Connection/OBD.Connection.BLE.pas index e596e94b..83715403 100644 --- a/src/Connection/OBD.Connection.BLE.pas +++ b/src/Connection/OBD.Connection.BLE.pas @@ -486,11 +486,17 @@ function TBluetoothLE.SendByte(Value: Byte): Boolean; Result := SendData(@Value, 1) = 1; end; +//------------------------------------------------------------------------------ +// SEND CHAR +//------------------------------------------------------------------------------ function TBluetoothLE.SendChar(Value: AnsiChar): Boolean; begin Result := SendData(@Value, 1) = 1; end; +//------------------------------------------------------------------------------ +// SEND STRING +//------------------------------------------------------------------------------ function TBluetoothLE.SendString(const S: AnsiString): Boolean; var L: DWORD; @@ -542,12 +548,18 @@ procedure TBluetoothLEOBDConnection.OnReceiveData(Sender: TObject; InvokeDataReceived(DataPtr, DataSize); end; +//------------------------------------------------------------------------------ +// ON SEND DATA +//------------------------------------------------------------------------------ procedure TBluetoothLEOBDConnection.OnSendData(Sender: TObject; DataPtr: Pointer; DataSize: DWORD); begin InvokeDataSend(DataPtr, DataSize); end; +//------------------------------------------------------------------------------ +// ON CONNECTION ERROR +//------------------------------------------------------------------------------ procedure TBluetoothLEOBDConnection.OnConnectionError(Sender: TObject; ErrorCode: Integer; ErrorMessage: string); begin @@ -618,6 +630,9 @@ function TBluetoothLEOBDConnection.WriteATCommand( Result := FBluetoothLE.SendString(AnsiString(S + #13)); end; +//------------------------------------------------------------------------------ +// WRITE STCOMMAND +//------------------------------------------------------------------------------ function TBluetoothLEOBDConnection.WriteSTCommand( const STCommand: string): Boolean; var @@ -628,6 +643,9 @@ function TBluetoothLEOBDConnection.WriteSTCommand( Result := FBluetoothLE.SendString(AnsiString(S + #13)); end; +//------------------------------------------------------------------------------ +// WRITE OBDCOMMAND +//------------------------------------------------------------------------------ function TBluetoothLEOBDConnection.WriteOBDCommand( const OBDCommand: string): Boolean; begin diff --git a/src/Connection/OBD.Connection.Component.pas b/src/Connection/OBD.Connection.Component.pas index 51dc9fc4..b415123c 100755 --- a/src/Connection/OBD.Connection.Component.pas +++ b/src/Connection/OBD.Connection.Component.pas @@ -258,6 +258,9 @@ implementation { TOBDConnectionComponent } +//------------------------------------------------------------------------------ +// APPLY EVENT HANDLERS +//------------------------------------------------------------------------------ procedure TOBDConnectionComponent.ApplyEventHandlers; begin TMonitor.Enter(FEventLock); @@ -273,6 +276,9 @@ procedure TOBDConnectionComponent.ApplyEventHandlers; end; end; +//------------------------------------------------------------------------------ +// BUILD CONNECTION +//------------------------------------------------------------------------------ function TOBDConnectionComponent.BuildConnection: IOBDConnection; begin case FConnectionType of @@ -291,6 +297,9 @@ function TOBDConnectionComponent.BuildConnection: IOBDConnection; end; end; +//------------------------------------------------------------------------------ +// BUILD PARAMS +//------------------------------------------------------------------------------ function TOBDConnectionComponent.BuildParams: TOBDConnectionParams; begin FillChar(Result, SizeOf(Result), 0); @@ -327,6 +336,9 @@ function TOBDConnectionComponent.BuildParams: TOBDConnectionParams; end; end; +//------------------------------------------------------------------------------ +// NOTIFY CONNECTION STATE CHANGED +//------------------------------------------------------------------------------ procedure TOBDConnectionComponent.NotifyConnectionStateChanged(const AConnected: Boolean); var Handler: TConnectionStateChangedEvent; @@ -342,6 +354,9 @@ procedure TOBDConnectionComponent.NotifyConnectionStateChanged(const AConnected: Handler(Self, AConnected, FConnectionType); end; +//------------------------------------------------------------------------------ +// GET CONNECTION INSTANCE +//------------------------------------------------------------------------------ function TOBDConnectionComponent.GetConnectionInstance: IOBDConnection; begin TMonitor.Enter(FEventLock); @@ -352,6 +367,9 @@ function TOBDConnectionComponent.GetConnectionInstance: IOBDConnection; end; end; +//------------------------------------------------------------------------------ +// CONNECT +//------------------------------------------------------------------------------ function TOBDConnectionComponent.Connect: Boolean; var Params: TOBDConnectionParams; @@ -381,6 +399,9 @@ function TOBDConnectionComponent.Connect: Boolean; end; end; +//------------------------------------------------------------------------------ +// CONNECTED +//------------------------------------------------------------------------------ function TOBDConnectionComponent.Connected: Boolean; begin if Assigned(FConnection) then @@ -389,6 +410,9 @@ function TOBDConnectionComponent.Connected: Boolean; Result := False; end; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDConnectionComponent.Create(AOwner: TComponent); begin inherited Create(AOwner); @@ -399,6 +423,9 @@ constructor TOBDConnectionComponent.Create(AOwner: TComponent); FPort := 35000; end; +//------------------------------------------------------------------------------ +// DISCONNECT +//------------------------------------------------------------------------------ function TOBDConnectionComponent.Disconnect: Boolean; begin Result := False; @@ -409,6 +436,9 @@ function TOBDConnectionComponent.Disconnect: Boolean; end; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDConnectionComponent.Destroy; begin Disconnect; @@ -422,6 +452,9 @@ destructor TOBDConnectionComponent.Destroy; inherited Destroy; end; +//------------------------------------------------------------------------------ +// SET CONNECTION TYPE +//------------------------------------------------------------------------------ procedure TOBDConnectionComponent.SetConnectionType(const Value: TOBDConnectionType); begin if FConnectionType <> Value then @@ -436,6 +469,9 @@ procedure TOBDConnectionComponent.SetConnectionType(const Value: TOBDConnectionT end; end; +//------------------------------------------------------------------------------ +// SET ON DATA RECEIVED +//------------------------------------------------------------------------------ procedure TOBDConnectionComponent.SetOnDataReceived(const Value: TDataReceivedEvent); begin TMonitor.Enter(FEventLock); @@ -447,6 +483,9 @@ procedure TOBDConnectionComponent.SetOnDataReceived(const Value: TDataReceivedEv end; end; +//------------------------------------------------------------------------------ +// SET ON DATA SEND +//------------------------------------------------------------------------------ procedure TOBDConnectionComponent.SetOnDataSend(const Value: TDataSendEvent); begin TMonitor.Enter(FEventLock); @@ -458,6 +497,9 @@ procedure TOBDConnectionComponent.SetOnDataSend(const Value: TDataSendEvent); end; end; +//------------------------------------------------------------------------------ +// SET ON ERROR +//------------------------------------------------------------------------------ procedure TOBDConnectionComponent.SetOnError(const Value: TErrorEvent); begin TMonitor.Enter(FEventLock); diff --git a/src/CustomControls/OBD.CustomControl.Register.FMX.pas b/src/CustomControls/OBD.CustomControl.Register.FMX.pas index 6205f04c..47bc547c 100644 --- a/src/CustomControls/OBD.CustomControl.Register.FMX.pas +++ b/src/CustomControls/OBD.CustomControl.Register.FMX.pas @@ -29,6 +29,9 @@ implementation OBD.DtcList.FMX, OBD.Terminal.FMX, OBD.Knob.FMX, OBD.SegmentedSwitch.FMX, OBD.LED.FMX; +//------------------------------------------------------------------------------ +// REGISTER +//------------------------------------------------------------------------------ procedure Register; begin RegisterComponents(ComponentPage, [ diff --git a/src/CustomControls/OBD.Render.DtcList.pas b/src/CustomControls/OBD.Render.DtcList.pas index 8067b867..15411dbb 100644 --- a/src/CustomControls/OBD.Render.DtcList.pas +++ b/src/CustomControls/OBD.Render.DtcList.pas @@ -22,7 +22,9 @@ interface TOBDDtcSeverity = (dsInfo, dsWarning, dsCritical); TOBDDtcStatus = (dsActive, dsPending, dsPermanent, dsHistory); - /// Flat row passed to the renderer; bindings copy from their own item types. + /// + /// Flat row passed to the renderer; bindings copy from their own item types. + /// TOBDDtcRowView = record Code: string; Description: string; @@ -60,22 +62,34 @@ procedure RenderDtcList(const Canvas: ISkCanvas; implementation +//------------------------------------------------------------------------------ +// LIST AREA TOP +//------------------------------------------------------------------------------ function ListAreaTop(const State: TOBDDtcListRenderState): Single; begin if State.ShowHeader then Result := State.HeaderHeight else Result := 0; end; +//------------------------------------------------------------------------------ +// CONTENT HEIGHT +//------------------------------------------------------------------------------ function ContentHeight(const State: TOBDDtcListRenderState): Single; begin Result := Length(State.Rows) * State.RowHeight; end; +//------------------------------------------------------------------------------ +// LIST AREA HEIGHT +//------------------------------------------------------------------------------ function ListAreaHeight(const State: TOBDDtcListRenderState): Single; begin Result := State.Height - ListAreaTop(State); if Result < 0 then Result := 0; end; +//------------------------------------------------------------------------------ +// COLOR FOR SEVERITY +//------------------------------------------------------------------------------ function ColorForSeverity(const State: TOBDDtcListRenderState; S: TOBDDtcSeverity): TAlphaColor; begin @@ -87,6 +101,9 @@ function ColorForSeverity(const State: TOBDDtcListRenderState; end; end; +//------------------------------------------------------------------------------ +// STATUS LABEL +//------------------------------------------------------------------------------ function StatusLabel(S: TOBDDtcStatus): string; begin case S of @@ -98,6 +115,9 @@ function StatusLabel(S: TOBDDtcStatus): string; end; end; +//------------------------------------------------------------------------------ +// DRAW ROW +//------------------------------------------------------------------------------ procedure DrawRow(const Canvas: ISkCanvas; const State: TOBDDtcListRenderState; const Row: TOBDDtcRowView; const RowRect: TRectF; const Selected, Alternate: Boolean; const Font, MonoFont: ISkFont); @@ -167,6 +187,9 @@ procedure DrawRow(const Canvas: ISkCanvas; const State: TOBDDtcListRenderState; end; end; +//------------------------------------------------------------------------------ +// RENDER DTC LIST +//------------------------------------------------------------------------------ procedure RenderDtcList(const Canvas: ISkCanvas; const State: TOBDDtcListRenderState); var diff --git a/src/CustomControls/OBD.Render.Knob.pas b/src/CustomControls/OBD.Render.Knob.pas index bf060891..2baf7fbf 100644 --- a/src/CustomControls/OBD.Render.Knob.pas +++ b/src/CustomControls/OBD.Render.Knob.pas @@ -36,8 +36,12 @@ procedure RenderKnob(const Canvas: ISkCanvas; implementation +//------------------------------------------------------------------------------ +// KNOB VALUE FRACTION +//------------------------------------------------------------------------------ function KnobValueFraction(const Min, Max, Value: Single): Single; -var Span: Single; +var + Span: Single; begin Span := Max - Min; if Span <= 0 then Exit(0); @@ -46,6 +50,9 @@ function KnobValueFraction(const Min, Max, Value: Single): Single; if Result > 1 then Result := 1; end; +//------------------------------------------------------------------------------ +// RENDER KNOB +//------------------------------------------------------------------------------ procedure RenderKnob(const Canvas: ISkCanvas; const State: TOBDKnobRenderState); var diff --git a/src/CustomControls/OBD.Render.LED.pas b/src/CustomControls/OBD.Render.LED.pas index 64a21502..f695c759 100644 --- a/src/CustomControls/OBD.Render.LED.pas +++ b/src/CustomControls/OBD.Render.LED.pas @@ -44,6 +44,9 @@ procedure RenderLED(const Canvas: ISkCanvas; implementation +//------------------------------------------------------------------------------ +// RENDER LED +//------------------------------------------------------------------------------ procedure RenderLED(const Canvas: ISkCanvas; const State: TOBDLedRenderState); var diff --git a/src/CustomControls/OBD.Render.LinearGauge.pas b/src/CustomControls/OBD.Render.LinearGauge.pas index de02391c..c3bab3e9 100644 --- a/src/CustomControls/OBD.Render.LinearGauge.pas +++ b/src/CustomControls/OBD.Render.LinearGauge.pas @@ -68,6 +68,9 @@ procedure RenderLinearGauge(const Canvas: ISkCanvas; implementation +//------------------------------------------------------------------------------ +// LINEAR VALUE FRACTION +//------------------------------------------------------------------------------ function LinearValueFraction(const Min, Max, Value: Single): Single; var Span: Single; @@ -79,6 +82,9 @@ function LinearValueFraction(const Min, Max, Value: Single): Single; if Result > 1 then Result := 1; end; +//------------------------------------------------------------------------------ +// RENDER LINEAR GAUGE +//------------------------------------------------------------------------------ procedure RenderLinearGauge(const Canvas: ISkCanvas; const State: TOBDLinearGaugeRenderState); var diff --git a/src/CustomControls/OBD.Render.SegmentedSwitch.pas b/src/CustomControls/OBD.Render.SegmentedSwitch.pas index 9d36745e..cd06cb2b 100644 --- a/src/CustomControls/OBD.Render.SegmentedSwitch.pas +++ b/src/CustomControls/OBD.Render.SegmentedSwitch.pas @@ -31,6 +31,9 @@ procedure RenderSegmentedSwitch(const Canvas: ISkCanvas; implementation +//------------------------------------------------------------------------------ +// RENDER SEGMENTED SWITCH +//------------------------------------------------------------------------------ procedure RenderSegmentedSwitch(const Canvas: ISkCanvas; const State: TOBDSegmentedSwitchRenderState); var diff --git a/src/CustomControls/OBD.Render.Tachometer.pas b/src/CustomControls/OBD.Render.Tachometer.pas index 0edf19aa..f1e7dd49 100644 --- a/src/CustomControls/OBD.Render.Tachometer.pas +++ b/src/CustomControls/OBD.Render.Tachometer.pas @@ -50,8 +50,12 @@ procedure RenderTachometer(const Canvas: ISkCanvas; implementation +//------------------------------------------------------------------------------ +// TACHOMETER VALUE FRACTION +//------------------------------------------------------------------------------ function TachometerValueFraction(const Min, Max, Value: Single): Single; -var Span: Single; +var + Span: Single; begin Span := Max - Min; if Span <= 0 then Exit(0); @@ -60,11 +64,17 @@ function TachometerValueFraction(const Min, Max, Value: Single): Single; if Result > 1 then Result := 1; end; +//------------------------------------------------------------------------------ +// TACHOMETER SHIFT LIGHT ACTIVE +//------------------------------------------------------------------------------ function TachometerShiftLightActive(const State: TOBDTachometerRenderState): Boolean; begin Result := State.ShowShiftLight and (State.DisplayValue >= State.ShiftPoint); end; +//------------------------------------------------------------------------------ +// RENDER TACHOMETER +//------------------------------------------------------------------------------ procedure RenderTachometer(const Canvas: ISkCanvas; const State: TOBDTachometerRenderState); var diff --git a/src/CustomControls/OBD.Render.Terminal.pas b/src/CustomControls/OBD.Render.Terminal.pas index 08d4c6b0..bf22c80b 100644 --- a/src/CustomControls/OBD.Render.Terminal.pas +++ b/src/CustomControls/OBD.Render.Terminal.pas @@ -48,6 +48,9 @@ procedure RenderTerminal(const Canvas: ISkCanvas; implementation +//------------------------------------------------------------------------------ +// COLOR FOR DIRECTION +//------------------------------------------------------------------------------ function ColorForDirection(const State: TOBDTerminalRenderState; D: TOBDTerminalDirection): TAlphaColor; begin @@ -60,6 +63,9 @@ function ColorForDirection(const State: TOBDTerminalRenderState; end; end; +//------------------------------------------------------------------------------ +// PREFIX FOR DIRECTION +//------------------------------------------------------------------------------ function PrefixForDirection(D: TOBDTerminalDirection): string; begin case D of @@ -71,6 +77,9 @@ function PrefixForDirection(D: TOBDTerminalDirection): string; end; end; +//------------------------------------------------------------------------------ +// RENDER TERMINAL +//------------------------------------------------------------------------------ procedure RenderTerminal(const Canvas: ISkCanvas; const State: TOBDTerminalRenderState); var diff --git a/src/CustomControls/OBD.Render.TrendGraph.pas b/src/CustomControls/OBD.Render.TrendGraph.pas index 71062774..cb8c2ccc 100644 --- a/src/CustomControls/OBD.Render.TrendGraph.pas +++ b/src/CustomControls/OBD.Render.TrendGraph.pas @@ -23,9 +23,13 @@ TOBDTrendSeriesView = record Color: TAlphaColor; Min: Single; Max: Single; - /// Samples in oldest-to-newest order. + /// + /// Samples in oldest-to-newest order. + /// Samples: TArray; - /// Ring-buffer capacity (so right-edge alignment matches across series). + /// + /// Ring-buffer capacity (so right-edge alignment matches across series). + /// Capacity: Integer; end; @@ -52,6 +56,9 @@ procedure RenderTrendGraph(const Canvas: ISkCanvas; implementation +//------------------------------------------------------------------------------ +// DRAW GRID +//------------------------------------------------------------------------------ procedure DrawGrid(const Canvas: ISkCanvas; const State: TOBDTrendGraphRenderState; const Plot: TRectF); const @@ -83,6 +90,9 @@ procedure DrawGrid(const Canvas: ISkCanvas; const State: TOBDTrendGraphRenderSta end; end; +//------------------------------------------------------------------------------ +// DRAW SERIES +//------------------------------------------------------------------------------ procedure DrawSeries(const Canvas: ISkCanvas; const State: TOBDTrendGraphRenderState; const Plot: TRectF; const Series: TOBDTrendSeriesView); var @@ -129,6 +139,9 @@ procedure DrawSeries(const Canvas: ISkCanvas; const State: TOBDTrendGraphRenderS Canvas.DrawPath(Path.Detach, Paint); end; +//------------------------------------------------------------------------------ +// DRAW LEGEND +//------------------------------------------------------------------------------ procedure DrawLegend(const Canvas: ISkCanvas; const State: TOBDTrendGraphRenderState; const Bounds: TRectF; const Font: ISkFont); var @@ -161,6 +174,9 @@ procedure DrawLegend(const Canvas: ISkCanvas; const State: TOBDTrendGraphRenderS end; end; +//------------------------------------------------------------------------------ +// RENDER TREND GRAPH +//------------------------------------------------------------------------------ procedure RenderTrendGraph(const Canvas: ISkCanvas; const State: TOBDTrendGraphRenderState); var diff --git a/src/CustomControls/OBD.Theme.pas b/src/CustomControls/OBD.Theme.pas index 6ad17a99..d3effd0d 100644 --- a/src/CustomControls/OBD.Theme.pas +++ b/src/CustomControls/OBD.Theme.pas @@ -64,9 +64,13 @@ TOBDTheme = class // ---- Selection / hover ---- Selection: TColor; - /// Built-in dark theme. + /// + /// Built-in dark theme. + /// class function Dark: TOBDTheme; - /// Built-in light theme. + /// + /// Built-in light theme. + /// class function Light: TOBDTheme; procedure Apply(C: TOBDLinearGauge); overload; diff --git a/src/Protocol/OBD.J1939.PGNs.pas b/src/Protocol/OBD.J1939.PGNs.pas new file mode 100644 index 00000000..fa491fd4 --- /dev/null +++ b/src/Protocol/OBD.J1939.PGNs.pas @@ -0,0 +1,251 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.J1939.PGNs.pas +// CONTENTS : SAE J1939-71/73/75 named-PGN catalog +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.J1939.PGNs; + +interface + +uses + System.SysUtils, System.Generics.Collections, System.Generics.Defaults; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + TJ1939PGNDescriptor = record + /// + /// Pgn. + /// + PGN: UInt32; + /// + /// Mnemonic. + /// + Mnemonic: string; + /// + /// Name. + /// + Name: string; + /// + /// Length bytes. + /// + LengthBytes: Integer; + /// + /// Default priority. + /// + DefaultPriority: Byte; + /// + /// Tx rate ms. + /// + TxRateMs: Integer; + /// + /// Spec section. + /// + SpecSection: string; + end; + +/// +/// Look up a PGN by id. Returns a zero record when not found; +/// callers can check Result.PGN <> 0. +/// +function FindPGN(const PGN: UInt32): TJ1939PGNDescriptor; + +/// +/// Register a custom PGN (e.g. for OEM-specific extensions). +/// Replaces an existing entry with the same id. +/// +procedure RegisterJ1939PGN(const Desc: TJ1939PGNDescriptor); + +/// +/// Total entries in the registry (catalog + registered). +/// +function J1939PGNCount: Integer; + +/// +/// Iterate all PGNs in ascending order. +/// +function J1939PGNAll: TArray; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +uses + System.Classes, System.JSON, + OBD.Catalog.Path; + +const + CatalogFileName = 'j1939-pgns.json'; + +var + GPGNs: TList; + +//------------------------------------------------------------------------------ +// FIND PGNINDEX +//------------------------------------------------------------------------------ +function FindPGNIndex(PGN: UInt32; out Idx: Integer): Boolean; +var + Lo, Hi, Mid: Integer; + V: UInt32; +begin + Lo := 0; Hi := GPGNs.Count - 1; + while Lo <= Hi do + begin + Mid := (Lo + Hi) shr 1; + V := GPGNs[Mid].PGN; + if V = PGN then begin Idx := Mid; Exit(True); end + else if V < PGN then Lo := Mid + 1 + else Hi := Mid - 1; + end; + Idx := -1; + // Initialize result + Result := False; +end; + +//------------------------------------------------------------------------------ +// SORT BY PGN +//------------------------------------------------------------------------------ +procedure SortByPGN; +begin + GPGNs.Sort(TComparer.Construct( + function(const A, B: TJ1939PGNDescriptor): Integer + begin + if A.PGN < B.PGN then Result := -1 + else if A.PGN > B.PGN then Result := 1 + else Result := 0; + end)); +end; + +//------------------------------------------------------------------------------ +// PARSE HEX UINT32 +//------------------------------------------------------------------------------ +function ParseHexUInt32(const S: string; out V: UInt32): Boolean; +var + T: string; + I64: Int64; +begin + T := S; + if T.StartsWith('0x', True) then T := '$' + T.Substring(2); + Result := TryStrToInt64(T, I64) and (I64 >= 0) and (I64 <= $FFFFFFFF); + if Result then V := UInt32(I64); +end; + +//------------------------------------------------------------------------------ +// LOAD CATALOG +//------------------------------------------------------------------------------ +procedure LoadCatalog; +var + Path, Raw: string; + Doc: TJSONValue; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + D: TJ1939PGNDescriptor; + Stream: TStringStream; +begin + Path := ResolveCatalogPath(CatalogFileName); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + D := Default(TJ1939PGNDescriptor); + if not ParseHexUInt32(Obj.GetValue('pgn', ''), D.PGN) then Continue; + D.Mnemonic := Obj.GetValue('mnemonic', ''); + D.Name := Obj.GetValue('name', ''); + D.LengthBytes := Obj.GetValue('length_bytes', 0); + D.DefaultPriority := Byte(Obj.GetValue('default_priority', 6)); + D.TxRateMs := Obj.GetValue('tx_rate_ms', 0); + D.SpecSection := Obj.GetValue('spec_section', ''); + GPGNs.Add(D); + end; + finally + // Free the document + Doc.Free; + end; +end; + +//------------------------------------------------------------------------------ +// FIND PGN +//------------------------------------------------------------------------------ +function FindPGN(const PGN: UInt32): TJ1939PGNDescriptor; +var + Idx: Integer; +begin + if FindPGNIndex(PGN, Idx) then Result := GPGNs[Idx] + else Result := Default(TJ1939PGNDescriptor); +end; + +//------------------------------------------------------------------------------ +// REGISTER J1939 PGN +//------------------------------------------------------------------------------ +procedure RegisterJ1939PGN(const Desc: TJ1939PGNDescriptor); +var + Idx: Integer; +begin + if FindPGNIndex(Desc.PGN, Idx) then GPGNs[Idx] := Desc + else + begin + GPGNs.Add(Desc); + SortByPGN; + end; +end; + +//------------------------------------------------------------------------------ +// J1939 PGNCOUNT +//------------------------------------------------------------------------------ +function J1939PGNCount: Integer; +begin + Result := GPGNs.Count; +end; + +//------------------------------------------------------------------------------ +// J1939 PGNALL +//------------------------------------------------------------------------------ +function J1939PGNAll: TArray; +begin + Result := GPGNs.ToArray; +end; + +initialization + // Create GPGNs + GPGNs := TList.Create; + LoadCatalog; + SortByPGN; + +finalization + // Free GPGNs + GPGNs.Free; + +end. diff --git a/src/Protocol/OBD.Protocol.Async.pas b/src/Protocol/OBD.Protocol.Async.pas index bb75cb51..8ef82f71 100644 --- a/src/Protocol/OBD.Protocol.Async.pas +++ b/src/Protocol/OBD.Protocol.Async.pas @@ -66,6 +66,9 @@ implementation uses System.StrUtils; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDProtocolAsync.Create(const AProtocol: IOBDProtocol; const AConnection: TOBDConnectionAsync); begin @@ -78,6 +81,9 @@ constructor TOBDProtocolAsync.Create(const AProtocol: IOBDProtocol; FConnection := AConnection; end; +//------------------------------------------------------------------------------ +// PARSE RESPONSE +//------------------------------------------------------------------------------ function TOBDProtocolAsync.ParseResponse(const RawText: string): TArray; var Lines: TStringList; @@ -96,6 +102,9 @@ function TOBDProtocolAsync.ParseResponse(const RawText: string): TArray>; @@ -103,6 +112,9 @@ function TOBDProtocolAsync.RequestAsync(Service, PID: Byte; Result := RequestRawAsync(Format('%.2X %.2X', [Service, PID]), TimeoutMs, Token); end; +//------------------------------------------------------------------------------ +// REQUEST RAW ASYNC +//------------------------------------------------------------------------------ function TOBDProtocolAsync.RequestRawAsync(const HexCommand: string; TimeoutMs: Cardinal; const Token: IOBDCancellationToken ): IOBDFuture>; @@ -141,6 +153,9 @@ function TOBDProtocolAsync.RequestRawAsync(const HexCommand: string; end); end; +//------------------------------------------------------------------------------ +// POLL ASYNC +//------------------------------------------------------------------------------ function TOBDProtocolAsync.PollAsync(const PIDs: TArray; TimeoutMsPerCall: Cardinal; const Token: IOBDCancellationToken ): IOBDFuture>>; diff --git a/src/Protocol/OBD.Protocol.DoIP.Discovery.pas b/src/Protocol/OBD.Protocol.DoIP.Discovery.pas new file mode 100644 index 00000000..0bc0c35c --- /dev/null +++ b/src/Protocol/OBD.Protocol.DoIP.Discovery.pas @@ -0,0 +1,312 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.Protocol.DoIP.Discovery.pas +// CONTENTS : ISO 13400-2 UDP discovery and AliveCheck +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.Protocol.DoIP.Discovery; + +interface + +uses + System.SysUtils; + +//------------------------------------------------------------------------------ +// CONSTANTS +//------------------------------------------------------------------------------ +const + DOIP_UDP_PORT_DISCOVERY = 13400; + DOIP_PROTOCOL_VERSION_2012 = $02; + DOIP_PROTOCOL_VERSION_2019 = $03; + + DOIP_PT_HEADER_NACK = $0000; + DOIP_PT_VEHICLE_IDENT_REQ = $0001; + DOIP_PT_VEHICLE_IDENT_REQ_EID = $0002; + DOIP_PT_VEHICLE_IDENT_REQ_VIN = $0003; + DOIP_PT_VEHICLE_ANNOUNCE = $0004; // also identification response + DOIP_PT_ALIVE_CHECK_REQUEST = $0007; + DOIP_PT_ALIVE_CHECK_RESPONSE = $0008; + + // ISO 13400-2 §6 NACK codes (the ones the discovery layer can emit) + DOIP_NACK_INCORRECT_PATTERN = $00; + DOIP_NACK_UNKNOWN_PAYLOAD_TYPE = $01; + DOIP_NACK_MESSAGE_TOO_LARGE = $02; + DOIP_NACK_OUT_OF_MEMORY = $03; + DOIP_NACK_INVALID_PAYLOAD = $04; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDDoIPDiscovery = class(Exception); + + /// + /// One DoIP frame as carried over UDP. Header (8 bytes) + + /// payload bytes. Build via the helper functions; parse via + /// ParseDoIPHeader / ParseVehicleAnnouncement. + /// + TDoIPFrame = record + /// + /// Protocol version. + /// + ProtocolVersion: Byte; + /// + /// Inverse protocol version. + /// + InverseProtocolVersion: Byte; + /// + /// Payload type. + /// + PayloadType: Word; + /// + /// Payload. + /// + Payload: TBytes; + end; + + /// + /// Decoded Vehicle Announcement / Identification Response + /// payload (ISO 13400-2 §5.5.1). All multi-byte fields are big- + /// endian on the wire; we expose them in host order. + /// + TDoIPVehicleAnnouncement = record + VIN: string; // 17 ASCII characters + LogicalAddress: Word; // 2 bytes + EID: TBytes; // 6 bytes (typically MAC of the gateway) + GID: TBytes; // 6 bytes + FurtherActionRequired: Byte; // 0x00 = none; 0x10 = central security + SyncStatus: Byte; // optional in 2012; mandatory in 2019 + HasSyncStatus: Boolean; // true when payload included it + end; + +/// +/// Build a DoIP UDP frame: 4-byte header + 4-byte payload- +/// length + payload. The protocol version byte is followed by its +/// bitwise NOT for header validation. +/// +function BuildDoIPFrame(PayloadType: Word; const Payload: TBytes; + ProtocolVersion: Byte = DOIP_PROTOCOL_VERSION_2019): TBytes; + +/// +/// Builder shortcut for a Vehicle Identification Request +/// (no EID / no VIN). The payload is empty. +/// +function BuildVehicleIdentRequest( + ProtocolVersion: Byte = DOIP_PROTOCOL_VERSION_2019): TBytes; + +/// +/// Builder shortcut for VIN-targeted discovery. +/// +function BuildVehicleIdentRequestVIN(const VIN: string; + ProtocolVersion: Byte = DOIP_PROTOCOL_VERSION_2019): TBytes; + +/// +/// Builder shortcut for EID-targeted discovery (6 bytes). +/// +function BuildVehicleIdentRequestEID(const EID: TBytes; + ProtocolVersion: Byte = DOIP_PROTOCOL_VERSION_2019): TBytes; + +/// +/// AliveCheck request (empty payload). +/// +function BuildAliveCheckRequest( + ProtocolVersion: Byte = DOIP_PROTOCOL_VERSION_2019): TBytes; + +/// +/// AliveCheck response — payload carries the gateway's +/// 2-byte logical source address. +/// +function BuildAliveCheckResponse(SourceAddress: Word; + ProtocolVersion: Byte = DOIP_PROTOCOL_VERSION_2019): TBytes; + +/// +/// Parse the 8-byte DoIP header. Verifies the +/// protocol-version / inverse pairing and the declared payload-length. +/// Raises EOBDDoIPDiscovery on malformed input. +/// +function ParseDoIPHeader(const Bytes: TBytes): TDoIPFrame; + +/// +/// Parse a Vehicle Announcement / Identification Response +/// payload (ISO 13400-2 §5.5.1). +/// +function ParseVehicleAnnouncement(const Frame: TDoIPFrame): + TDoIPVehicleAnnouncement; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// BUILD DO IPFRAME +//------------------------------------------------------------------------------ +function BuildDoIPFrame(PayloadType: Word; const Payload: TBytes; + ProtocolVersion: Byte): TBytes; +var + Out_: TBytes; + PayloadLen: UInt32; +begin + PayloadLen := UInt32(Length(Payload)); + // Allocate Out_ + SetLength(Out_, 8 + Length(Payload)); + Out_[0] := ProtocolVersion; + Out_[1] := Byte(not ProtocolVersion); + Out_[2] := Byte(PayloadType shr 8); + Out_[3] := Byte(PayloadType and $FF); + Out_[4] := Byte(PayloadLen shr 24); + Out_[5] := Byte(PayloadLen shr 16); + Out_[6] := Byte(PayloadLen shr 8); + Out_[7] := Byte(PayloadLen); + if Length(Payload) > 0 then + Move(Payload[0], Out_[8], Length(Payload)); + Result := Out_; +end; + +//------------------------------------------------------------------------------ +// BUILD VEHICLE IDENT REQUEST +//------------------------------------------------------------------------------ +function BuildVehicleIdentRequest(ProtocolVersion: Byte): TBytes; +begin + Result := BuildDoIPFrame(DOIP_PT_VEHICLE_IDENT_REQ, nil, ProtocolVersion); +end; + +//------------------------------------------------------------------------------ +// BUILD VEHICLE IDENT REQUEST VIN +//------------------------------------------------------------------------------ +function BuildVehicleIdentRequestVIN(const VIN: string; + ProtocolVersion: Byte): TBytes; +var + Payload: TBytes; + I: Integer; +begin + if Length(VIN) <> 17 then + raise EOBDDoIPDiscovery.CreateFmt( + 'VIN must be exactly 17 characters, got %d', [Length(VIN)]); + // Allocate Payload + SetLength(Payload, 17); + for I := 0 to 16 do + Payload[I] := Byte(Ord(VIN[I + 1])); + Result := BuildDoIPFrame(DOIP_PT_VEHICLE_IDENT_REQ_VIN, Payload, + ProtocolVersion); +end; + +//------------------------------------------------------------------------------ +// BUILD VEHICLE IDENT REQUEST EID +//------------------------------------------------------------------------------ +function BuildVehicleIdentRequestEID(const EID: TBytes; + ProtocolVersion: Byte): TBytes; +begin + if Length(EID) <> 6 then + raise EOBDDoIPDiscovery.CreateFmt( + 'EID must be exactly 6 bytes, got %d', [Length(EID)]); + Result := BuildDoIPFrame(DOIP_PT_VEHICLE_IDENT_REQ_EID, EID, + ProtocolVersion); +end; + +//------------------------------------------------------------------------------ +// BUILD ALIVE CHECK REQUEST +//------------------------------------------------------------------------------ +function BuildAliveCheckRequest(ProtocolVersion: Byte): TBytes; +begin + Result := BuildDoIPFrame(DOIP_PT_ALIVE_CHECK_REQUEST, nil, ProtocolVersion); +end; + +//------------------------------------------------------------------------------ +// BUILD ALIVE CHECK RESPONSE +//------------------------------------------------------------------------------ +function BuildAliveCheckResponse(SourceAddress: Word; + ProtocolVersion: Byte): TBytes; +var + Payload: TBytes; +begin + // Allocate Payload + SetLength(Payload, 2); + Payload[0] := Byte(SourceAddress shr 8); + Payload[1] := Byte(SourceAddress and $FF); + Result := BuildDoIPFrame(DOIP_PT_ALIVE_CHECK_RESPONSE, Payload, + ProtocolVersion); +end; + +//------------------------------------------------------------------------------ +// PARSE DO IPHEADER +//------------------------------------------------------------------------------ +function ParseDoIPHeader(const Bytes: TBytes): TDoIPFrame; +var + PayloadLen: UInt32; +begin + if Length(Bytes) < 8 then + raise EOBDDoIPDiscovery.Create( + 'DoIP frame shorter than 8-byte header'); + Result.ProtocolVersion := Bytes[0]; + Result.InverseProtocolVersion := Bytes[1]; + if Bytes[1] <> Byte(not Bytes[0]) then + raise EOBDDoIPDiscovery.CreateFmt( + 'DoIP header inverse mismatch: 0x%.2x / 0x%.2x', + [Bytes[0], Bytes[1]]); + Result.PayloadType := (UInt32(Bytes[2]) shl 8) or Bytes[3]; + PayloadLen := (UInt32(Bytes[4]) shl 24) or (UInt32(Bytes[5]) shl 16) + or (UInt32(Bytes[6]) shl 8) or UInt32(Bytes[7]); + if 8 + PayloadLen > UInt32(Length(Bytes)) then + raise EOBDDoIPDiscovery.CreateFmt( + 'DoIP payload truncated: declared %d, actual %d', + [PayloadLen, Length(Bytes) - 8]); + // Allocate Result.Payload + SetLength(Result.Payload, PayloadLen); + if PayloadLen > 0 then + Move(Bytes[8], Result.Payload[0], PayloadLen); +end; + +//------------------------------------------------------------------------------ +// PARSE VEHICLE ANNOUNCEMENT +//------------------------------------------------------------------------------ +function ParseVehicleAnnouncement(const Frame: TDoIPFrame): + TDoIPVehicleAnnouncement; +var + P: TBytes; + I: Integer; +begin + if (Frame.PayloadType <> DOIP_PT_VEHICLE_ANNOUNCE) then + raise EOBDDoIPDiscovery.Create( + 'Frame is not a Vehicle Announcement / Identification Response'); + P := Frame.Payload; + if Length(P) < 32 then + raise EOBDDoIPDiscovery.CreateFmt( + 'Vehicle Announcement payload too short: %d (expected >= 32)', + [Length(P)]); + + Result := Default(TDoIPVehicleAnnouncement); + + // VIN — 17 ASCII bytes, offset 0. + SetLength(Result.VIN, 17); + for I := 0 to 16 do + Result.VIN[I + 1] := Char(P[I]); + + // Logical address — uint16 BE at offset 17. + Result.LogicalAddress := (UInt32(P[17]) shl 8) or P[18]; + + // EID — 6 bytes at offset 19. + SetLength(Result.EID, 6); + Move(P[19], Result.EID[0], 6); + + // GID — 6 bytes at offset 25. + SetLength(Result.GID, 6); + Move(P[25], Result.GID[0], 6); + + // Further action required — uint8 at offset 31. + Result.FurtherActionRequired := P[31]; + + // Sync status — uint8 at offset 32, optional in 2012, mandatory in 2019. + if Length(P) >= 33 then + begin + Result.SyncStatus := P[32]; + Result.HasSyncStatus := True; + end; +end; + +end. diff --git a/src/Protocol/OBD.Protocol.DoIP.Session.Cross.pas b/src/Protocol/OBD.Protocol.DoIP.Session.Cross.pas index dc0ad78a..874b8d70 100644 --- a/src/Protocol/OBD.Protocol.DoIP.Session.Cross.pas +++ b/src/Protocol/OBD.Protocol.DoIP.Session.Cross.pas @@ -41,9 +41,11 @@ EOBDDoIPCrossRoutingError = class(EOBDDoIPCrossError); EOBDDoIPCrossTransportError = class(EOBDDoIPCrossError); EOBDDoIPCrossTimeoutError = class(EOBDDoIPCrossError); - /// Cross-platform DoIP TCP session. One instance ↔ one - /// connected ECU/gateway. Methods are not thread-safe; serialise - /// access externally if you share the session. + /// + /// Cross-platform DoIP TCP session. One instance ↔ one + /// connected ECU/gateway. Methods are not thread-safe; serialise + /// access externally if you share the session. + /// TDoIPSessionCross = class strict private FSocket: TSocket; @@ -89,6 +91,9 @@ implementation const MAX_FRAMES_PER_CALL = 16; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TDoIPSessionCross.Create; var Lines: TStringList; @@ -104,6 +109,9 @@ constructor TDoIPSessionCross.Create; FReceiveTimeoutMs := 1500; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TDoIPSessionCross.Destroy; begin Disconnect; @@ -111,6 +119,9 @@ destructor TDoIPSessionCross.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// CONNECT +//------------------------------------------------------------------------------ procedure TDoIPSessionCross.Connect(const Host: string; Port: Word; ConnectTimeoutMs: Cardinal); var @@ -132,6 +143,9 @@ procedure TDoIPSessionCross.Connect(const Host: string; Port: Word; end; end; +//------------------------------------------------------------------------------ +// SEND BYTES +//------------------------------------------------------------------------------ procedure TDoIPSessionCross.SendBytes(const Bytes: TBytes); begin if not FConnected then @@ -139,6 +153,9 @@ procedure TDoIPSessionCross.SendBytes(const Bytes: TBytes); FSocket.Send(Bytes); end; +//------------------------------------------------------------------------------ +// RECEIVE EXACT +//------------------------------------------------------------------------------ function TDoIPSessionCross.ReceiveExact(Count: Integer; TimeoutMs: Cardinal): TBytes; var @@ -173,6 +190,9 @@ function TDoIPSessionCross.ReceiveExact(Count: Integer; Result := Acc; end; +//------------------------------------------------------------------------------ +// RECEIVE DO IPMESSAGE +//------------------------------------------------------------------------------ function TDoIPSessionCross.ReceiveDoIPMessage(TimeoutMs: Cardinal): TBytes; var HeaderBytes, PayloadBytes: TBytes; @@ -195,6 +215,9 @@ function TDoIPSessionCross.ReceiveDoIPMessage(TimeoutMs: Cardinal): TBytes; Move(PayloadBytes[0], Result[8], Need); end; +//------------------------------------------------------------------------------ +// HANDLE ALIVE CHECK REQUEST +//------------------------------------------------------------------------------ procedure TDoIPSessionCross.HandleAliveCheckRequest; var Resp: TBytes; @@ -205,6 +228,9 @@ procedure TDoIPSessionCross.HandleAliveCheckRequest; FOnAliveCheck(Self); end; +//------------------------------------------------------------------------------ +// ACTIVATE ROUTING +//------------------------------------------------------------------------------ procedure TDoIPSessionCross.ActivateRouting(SourceAddress: Word; TargetAddress: Word; ActivationType: Byte); @@ -232,6 +258,9 @@ procedure TDoIPSessionCross.ActivateRouting(SourceAddress: Word; FProtocol.RoutingActivated := True; end; +//------------------------------------------------------------------------------ +// SEND RECEIVE +//------------------------------------------------------------------------------ function TDoIPSessionCross.SendReceive(const UdsRequest: TBytes; TimeoutMs: Cardinal): TBytes; var @@ -280,6 +309,9 @@ function TDoIPSessionCross.SendReceive(const UdsRequest: TBytes; [MAX_FRAMES_PER_CALL]); end; +//------------------------------------------------------------------------------ +// DISCONNECT +//------------------------------------------------------------------------------ procedure TDoIPSessionCross.Disconnect; begin if Assigned(FSocket) then diff --git a/src/Protocol/OBD.Protocol.DoIP.Session.TLS.pas b/src/Protocol/OBD.Protocol.DoIP.Session.TLS.pas index 4271aa32..75e0af48 100644 --- a/src/Protocol/OBD.Protocol.DoIP.Session.TLS.pas +++ b/src/Protocol/OBD.Protocol.DoIP.Session.TLS.pas @@ -33,7 +33,9 @@ interface OBD.Protocol.DoIP, OBD.Protocol.Types; const - /// ISO 13400-3 §7 TLS port. + /// + /// ISO 13400-3 §7 TLS port. + /// DOIP_TLS_DATA_PORT = 3496; type @@ -43,37 +45,53 @@ EOBDDoIPTLSRouting = class(EOBDDoIPTLSError); EOBDDoIPTLSTransport = class(EOBDDoIPTLSError); EOBDDoIPTLSTimeout = class(EOBDDoIPTLSError); - /// Credentials and policy for a TLS DoIP session. All - /// path fields are optional — leave empty to skip the - /// corresponding feature. + /// + /// Credentials and policy for a TLS DoIP session. All + /// path fields are optional — leave empty to skip the + /// corresponding feature. + /// TDoIPTLSCredentials = record - /// PEM file containing the trusted-CA bundle. If - /// empty, the host's default trust store is used (verification - /// still happens unless VerifyPeer is False). + /// + /// PEM file containing the trusted-CA bundle. If + /// empty, the host's default trust store is used (verification + /// still happens unless VerifyPeer is False). + /// RootCAFile: string; - /// Client certificate (PEM) presented during mTLS - /// handshake. Empty disables client-side authentication. + /// + /// Client certificate (PEM) presented during mTLS + /// handshake. Empty disables client-side authentication. + /// ClientCertFile: string; - /// Client private key (PEM). Required when - /// ClientCertFile is set. + /// + /// Client private key (PEM). Required when + /// ClientCertFile is set. + /// ClientKeyFile: string; - /// Passphrase for the encrypted private key. Empty if - /// the key is unencrypted. + /// + /// Passphrase for the encrypted private key. Empty if + /// the key is unencrypted. + /// ClientKeyPassword: string; - /// If True, the server's certificate must chain to a - /// trusted root and the hostname must match. Default True. - /// Set to False only for closed bench/lab setups. + /// + /// If True, the server's certificate must chain to a + /// trusted root and the hostname must match. Default True. + /// Set to False only for closed bench/lab setups. + /// VerifyPeer: Boolean; - /// OpenSSL cipher-list string. Empty uses the Indy - /// default. ISO 13400-3 lists no required suites; OEMs publish - /// their own — override if you have a policy. + /// + /// OpenSSL cipher-list string. Empty uses the Indy + /// default. ISO 13400-3 lists no required suites; OEMs publish + /// their own — override if you have a policy. + /// CipherList: string; end; - /// TLS-wrapped DoIP TCP session. One instance ↔ one - /// connected ECU/gateway. Methods are not thread-safe; - /// serialise externally if you share the session across - /// threads. + /// + /// TLS-wrapped DoIP TCP session. One instance ↔ one + /// connected ECU/gateway. Methods are not thread-safe; + /// serialise externally if you share the session across + /// threads. + /// TDoIPSessionTLS = class strict private FClient: TIdTCPClient; @@ -100,8 +118,10 @@ TDoIPSessionTLS = class constructor Create; destructor Destroy; override; - /// Configure TLS credentials. Call before - /// Connect. + /// + /// Configure TLS credentials. Call before + /// Connect. + /// procedure SetCredentials(const Creds: TDoIPTLSCredentials); procedure Connect(const Host: string; Port: Word = DOIP_TLS_DATA_PORT; @@ -123,8 +143,10 @@ TDoIPSessionTLS = class read FReceiveTimeoutMs write FReceiveTimeoutMs; end; -/// Build a credentials record with sensible defaults -/// (verify on, no client cert, no custom cipher list). +/// +/// Build a credentials record with sensible defaults +/// (verify on, no client cert, no custom cipher list). +/// function DefaultTLSCredentials: TDoIPTLSCredentials; implementation @@ -136,6 +158,9 @@ implementation const MAX_FRAMES_PER_CALL = 16; +//------------------------------------------------------------------------------ +// DEFAULT TLSCREDENTIALS +//------------------------------------------------------------------------------ function DefaultTLSCredentials: TDoIPTLSCredentials; begin Result.RootCAFile := ''; @@ -146,6 +171,9 @@ function DefaultTLSCredentials: TDoIPTLSCredentials; Result.CipherList := ''; end; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TDoIPSessionTLS.Create; var Lines: TStringList; @@ -174,6 +202,9 @@ constructor TDoIPSessionTLS.Create; FClient.IOHandler := FSSLHandler; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TDoIPSessionTLS.Destroy; begin Disconnect; @@ -183,6 +214,9 @@ destructor TDoIPSessionTLS.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// SET CREDENTIALS +//------------------------------------------------------------------------------ procedure TDoIPSessionTLS.SetCredentials(const Creds: TDoIPTLSCredentials); begin if FConnected then @@ -191,6 +225,9 @@ procedure TDoIPSessionTLS.SetCredentials(const Creds: TDoIPTLSCredentials); FCredentials := Creds; end; +//------------------------------------------------------------------------------ +// DO VERIFY PEER +//------------------------------------------------------------------------------ function TDoIPSessionTLS.DoVerifyPeer(Certificate: TIdX509; AOk: Boolean; ADepth, AError: Integer): Boolean; begin @@ -203,12 +240,18 @@ function TDoIPSessionTLS.DoVerifyPeer(Certificate: TIdX509; AOk: Boolean; Result := AOk; end; +//------------------------------------------------------------------------------ +// GET PASSWORD HANDLER +//------------------------------------------------------------------------------ procedure TDoIPSessionTLS.GetPasswordHandler(var Password: string; const IsWrite: Boolean); begin Password := FCredentials.ClientKeyPassword; end; +//------------------------------------------------------------------------------ +// CONNECT +//------------------------------------------------------------------------------ procedure TDoIPSessionTLS.Connect(const Host: string; Port: Word; ConnectTimeoutMs: Cardinal); begin @@ -256,6 +299,9 @@ procedure TDoIPSessionTLS.Connect(const Host: string; Port: Word; FRemotePort := Port; end; +//------------------------------------------------------------------------------ +// SEND BYTES +//------------------------------------------------------------------------------ procedure TDoIPSessionTLS.SendBytes(const Bytes: TBytes); var Buffer: TIdBytes; @@ -274,6 +320,9 @@ procedure TDoIPSessionTLS.SendBytes(const Bytes: TBytes); end; end; +//------------------------------------------------------------------------------ +// RECEIVE EXACT +//------------------------------------------------------------------------------ function TDoIPSessionTLS.ReceiveExact(Count: Integer; TimeoutMs: Cardinal): TBytes; var @@ -311,6 +360,9 @@ function TDoIPSessionTLS.ReceiveExact(Count: Integer; Result[I] := Buffer[I]; end; +//------------------------------------------------------------------------------ +// RECEIVE DO IPMESSAGE +//------------------------------------------------------------------------------ function TDoIPSessionTLS.ReceiveDoIPMessage(TimeoutMs: Cardinal): TBytes; var HeaderBytes, PayloadBytes: TBytes; @@ -333,6 +385,9 @@ function TDoIPSessionTLS.ReceiveDoIPMessage(TimeoutMs: Cardinal): TBytes; Move(PayloadBytes[0], Result[8], Need); end; +//------------------------------------------------------------------------------ +// HANDLE ALIVE CHECK REQUEST +//------------------------------------------------------------------------------ procedure TDoIPSessionTLS.HandleAliveCheckRequest; var Resp: TBytes; @@ -343,6 +398,9 @@ procedure TDoIPSessionTLS.HandleAliveCheckRequest; FOnAliveCheck(Self); end; +//------------------------------------------------------------------------------ +// ACTIVATE ROUTING +//------------------------------------------------------------------------------ procedure TDoIPSessionTLS.ActivateRouting(SourceAddress: Word; TargetAddress: Word; ActivationType: Byte); @@ -370,6 +428,9 @@ procedure TDoIPSessionTLS.ActivateRouting(SourceAddress: Word; FProtocol.RoutingActivated := True; end; +//------------------------------------------------------------------------------ +// SEND RECEIVE +//------------------------------------------------------------------------------ function TDoIPSessionTLS.SendReceive(const UdsRequest: TBytes; TimeoutMs: Cardinal): TBytes; var @@ -418,6 +479,9 @@ function TDoIPSessionTLS.SendReceive(const UdsRequest: TBytes; [MAX_FRAMES_PER_CALL]); end; +//------------------------------------------------------------------------------ +// DISCONNECT +//------------------------------------------------------------------------------ procedure TDoIPSessionTLS.Disconnect; begin if FConnected then diff --git a/src/Protocol/OBD.Protocol.DoIP.Session.pas b/src/Protocol/OBD.Protocol.DoIP.Session.pas index c26c2346..63c0ea1a 100644 --- a/src/Protocol/OBD.Protocol.DoIP.Session.pas +++ b/src/Protocol/OBD.Protocol.DoIP.Session.pas @@ -38,12 +38,18 @@ interface OBD.Protocol.DoIP, OBD.Protocol.Types; const - /// Standard DoIP UDP discovery port (ISO 13400-2 §7). + /// + /// Standard DoIP UDP discovery port (ISO 13400-2 §7). + /// DOIP_UDP_DISCOVERY_PORT = 13400; - /// Standard DoIP TCP diagnostic-data port. + /// + /// Standard DoIP TCP diagnostic-data port. + /// DOIP_TCP_DATA_PORT = 13400; - /// Reserved DoIP TLS port (ISO 13400-3 §7) — not yet - /// implemented by this unit. + /// + /// Reserved DoIP TLS port (ISO 13400-3 §7) — not yet + /// implemented by this unit. + /// DOIP_TLS_DATA_PORT = 3496; type @@ -53,39 +59,65 @@ EOBDDoIPRoutingError = class(EOBDDoIPSessionError); EOBDDoIPTransportError = class(EOBDDoIPSessionError); EOBDDoIPTimeoutError = class(EOBDDoIPSessionError); - /// One announcement received during UDP discovery. + /// + /// One announcement received during UDP discovery. + /// TDoIPVehicle = record - /// Sender IP in dotted-quad form ("192.168.0.10"). + /// + /// Sender IP in dotted-quad form ("192.168.0.10"). + /// Address: string; - /// 17-character VIN. + /// + /// 17-character VIN. + /// VIN: string; - /// 6-byte Entity ID (typically MAC address). + /// + /// 6-byte Entity ID (typically MAC address). + /// EID: TBytes; - /// 6-byte Group ID (vehicle group identifier). + /// + /// 6-byte Group ID (vehicle group identifier). + /// GID: TBytes; - /// Logical address of the announcing entity. + /// + /// Logical address of the announcing entity. + /// LogicalAddress: Word; - /// Further-action flag (0 = no further action). + /// + /// Further-action flag (0 = no further action). + /// FurtherAction: Byte; - /// Sync status (only valid for VIN/GID sync). + /// + /// Sync status (only valid for VIN/GID sync). + /// SyncStatus: Byte; end; - /// UDP discovery options. + /// + /// UDP discovery options. + /// TDoIPDiscoveryOptions = record - /// Local interface to bind on, or empty to use any. + /// + /// Local interface to bind on, or empty to use any. + /// LocalAddress: string; - /// Total wait window in milliseconds. The function - /// collects announcements until this elapses. + /// + /// Total wait window in milliseconds. The function + /// collects announcements until this elapses. + /// TimeoutMs: Cardinal; - /// Broadcast destination — usually 255.255.255.255 or - /// the local subnet's broadcast address. + /// + /// Broadcast destination — usually 255.255.255.255 or + /// the local subnet's broadcast address. + /// BroadcastAddress: string; end; - /// Active DoIP TCP session. One instance ↔ one - /// connected ECU/gateway. Methods are not thread-safe; callers - /// who need concurrency should serialize access externally. + /// + /// Active DoIP TCP session. One instance ↔ one + /// connected ECU/gateway. Methods are not thread-safe; callers + /// who need concurrency should serialize access externally. + /// TDoIPSession = class strict private FSocket: TSocket; @@ -109,24 +141,32 @@ TDoIPSession = class constructor Create; destructor Destroy; override; - /// Connect TCP to Host on Port. Doesn't - /// activate routing yet; call ActivateRouting next. + /// + /// Connect TCP to Host on Port. Doesn't + /// activate routing yet; call ActivateRouting next. + /// procedure Connect(const Host: string; Port: Word = DOIP_TCP_DATA_PORT; ConnectTimeoutMs: Cardinal = 3000); - /// Send a routing-activation request and wait for - /// the response. Raises EOBDDoIPRoutingError on - /// non-success codes. + /// + /// Send a routing-activation request and wait for + /// the response. Raises EOBDDoIPRoutingError on + /// non-success codes. + /// procedure ActivateRouting(SourceAddress: Word; TargetAddress: Word; ActivationType: Byte = DOIP_ROUTING_ACTIVATION_TYPE_DEFAULT); - /// Send one UDS request, return the UDS response. - /// Handles inline alive-check requests transparently. + /// + /// Send one UDS request, return the UDS response. + /// Handles inline alive-check requests transparently. + /// function SendReceive(const UdsRequest: TBytes; TimeoutMs: Cardinal = 1500): TBytes; - /// Disconnect cleanly. Idempotent. + /// + /// Disconnect cleanly. Idempotent. + /// procedure Disconnect; property Connected: Boolean read FConnected; @@ -135,21 +175,27 @@ TDoIPSession = class property RemotePort: Word read FRemotePort; property SourceAddress: Word read FSourceAddress; property TargetAddress: Word read FTargetAddress; - /// Idle interval after which the session sends an - /// alive-check (ISO 13400-2 §8.2) — 0 disables. + /// + /// Idle interval after which the session sends an + /// alive-check (ISO 13400-2 §8.2) — 0 disables. + /// property AliveCheckIntervalMs: Cardinal read FAliveCheckIntervalMs write FAliveCheckIntervalMs; property OnAliveCheck: TNotifyEvent read FOnAliveCheck write FOnAliveCheck; end; -/// Default discovery options (broadcast 255.255.255.255, -/// 1500 ms window). +/// +/// Default discovery options (broadcast 255.255.255.255, +/// 1500 ms window). +/// function DefaultDiscoveryOptions: TDoIPDiscoveryOptions; -/// Broadcast a Vehicle Identification Request and collect -/// announcements within the supplied time window. Returns one entry -/// per responding ECU/gateway. Uses winsock2 directly. Raises -/// EOBDDoIPDiscoveryError on socket setup failure. +/// +/// Broadcast a Vehicle Identification Request and collect +/// announcements within the supplied time window. Returns one entry +/// per responding ECU/gateway. Uses winsock2 directly. Raises +/// EOBDDoIPDiscoveryError on socket setup failure. +/// function DiscoverVehicles(const Options: TDoIPDiscoveryOptions ): TArray; @@ -166,6 +212,9 @@ implementation // Helpers //============================================================================== +//------------------------------------------------------------------------------ +// INIT WIN SOCK IF NEEDED +//------------------------------------------------------------------------------ procedure InitWinSockIfNeeded; var WSAData: TWSAData; @@ -175,11 +224,17 @@ procedure InitWinSockIfNeeded; 'WSAStartup failed: %d', [WSAGetLastError]); end; +//------------------------------------------------------------------------------ +// CLEANUP WIN SOCK +//------------------------------------------------------------------------------ procedure CleanupWinSock; begin WSACleanup; end; +//------------------------------------------------------------------------------ +// SOCK ADDR FROM HOST +//------------------------------------------------------------------------------ function SockAddrFromHost(const Host: string; Port: Word): TSockAddrIn; begin FillChar(Result, SizeOf(Result), 0); @@ -191,6 +246,9 @@ function SockAddrFromHost(const Host: string; Port: Word): TSockAddrIn; 'invalid IPv4 address: %s', [Host]); end; +//------------------------------------------------------------------------------ +// VINFROM BYTES +//------------------------------------------------------------------------------ function VINFromBytes(const B: TBytes; Offset: Integer): string; var AnsiVin: AnsiString; @@ -209,6 +267,9 @@ function VINFromBytes(const B: TBytes; Offset: Integer): string; // UDP discovery //============================================================================== +//------------------------------------------------------------------------------ +// DEFAULT DISCOVERY OPTIONS +//------------------------------------------------------------------------------ function DefaultDiscoveryOptions: TDoIPDiscoveryOptions; begin Result.LocalAddress := ''; @@ -216,6 +277,9 @@ function DefaultDiscoveryOptions: TDoIPDiscoveryOptions; Result.BroadcastAddress := '255.255.255.255'; end; +//------------------------------------------------------------------------------ +// DISCOVER VEHICLES +//------------------------------------------------------------------------------ function DiscoverVehicles(const Options: TDoIPDiscoveryOptions ): TArray; var @@ -360,6 +424,9 @@ function DiscoverVehicles(const Options: TDoIPDiscoveryOptions // TDoIPSession //============================================================================== +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TDoIPSession.Create; var Lines: TStringList; @@ -380,6 +447,9 @@ constructor TDoIPSession.Create; FAliveCheckIntervalMs := 0; // disabled by default end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TDoIPSession.Destroy; begin Disconnect; @@ -387,12 +457,18 @@ destructor TDoIPSession.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// RAISE LAST SOCKET ERROR +//------------------------------------------------------------------------------ procedure TDoIPSession.RaiseLastSocketError(const Action: string); begin raise EOBDDoIPTransportError.CreateFmt( '%s failed: WinSock %d', [Action, WSAGetLastError]); end; +//------------------------------------------------------------------------------ +// SEND BYTES +//------------------------------------------------------------------------------ procedure TDoIPSession.SendBytes(const Bytes: TBytes); var Sent: Integer; @@ -405,6 +481,9 @@ procedure TDoIPSession.SendBytes(const Bytes: TBytes); FLastSendTime := Now; end; +//------------------------------------------------------------------------------ +// RECEIVE BYTES +//------------------------------------------------------------------------------ function TDoIPSession.ReceiveBytes(MaxBytes: Integer): TBytes; var Buf: array[0..16383] of Byte; @@ -429,6 +508,9 @@ function TDoIPSession.ReceiveBytes(MaxBytes: Integer): TBytes; Move(Buf[0], Result[0], Got); end; +//------------------------------------------------------------------------------ +// RECEIVE DO IPMESSAGE +//------------------------------------------------------------------------------ function TDoIPSession.ReceiveDoIPMessage: TBytes; var HeaderBytes, PayloadBytes: TBytes; @@ -459,6 +541,9 @@ function TDoIPSession.ReceiveDoIPMessage: TBytes; Move(PayloadBytes[0], Result[8], Need); end; +//------------------------------------------------------------------------------ +// HANDLE ALIVE CHECK REQUEST +//------------------------------------------------------------------------------ procedure TDoIPSession.HandleAliveCheckRequest; var Response: TBytes; @@ -469,6 +554,9 @@ procedure TDoIPSession.HandleAliveCheckRequest; FOnAliveCheck(Self); end; +//------------------------------------------------------------------------------ +// CONNECT +//------------------------------------------------------------------------------ procedure TDoIPSession.Connect(const Host: string; Port: Word; ConnectTimeoutMs: Cardinal); var @@ -505,6 +593,9 @@ procedure TDoIPSession.Connect(const Host: string; Port: Word; end; end; +//------------------------------------------------------------------------------ +// ACTIVATE ROUTING +//------------------------------------------------------------------------------ procedure TDoIPSession.ActivateRouting(SourceAddress: Word; TargetAddress: Word; ActivationType: Byte); @@ -532,6 +623,9 @@ procedure TDoIPSession.ActivateRouting(SourceAddress: Word; FProtocol.RoutingActivated := True; end; +//------------------------------------------------------------------------------ +// SEND RECEIVE +//------------------------------------------------------------------------------ function TDoIPSession.SendReceive(const UdsRequest: TBytes; TimeoutMs: Cardinal): TBytes; const @@ -592,6 +686,9 @@ function TDoIPSession.SendReceive(const UdsRequest: TBytes; [MAX_FRAMES_PER_CALL]); end; +//------------------------------------------------------------------------------ +// DISCONNECT +//------------------------------------------------------------------------------ procedure TDoIPSession.Disconnect; begin if FSocket <> INVALID_SOCKET then diff --git a/src/Protocol/OBD.Protocol.IsoTp.Timing.pas b/src/Protocol/OBD.Protocol.IsoTp.Timing.pas new file mode 100644 index 00000000..9cfa174b --- /dev/null +++ b/src/Protocol/OBD.Protocol.IsoTp.Timing.pas @@ -0,0 +1,277 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.Protocol.IsoTp.Timing.pas +// CONTENTS : ISO 15765-2 STmin/BS timing audit harness +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.Protocol.IsoTp.Timing; + +interface + +uses + System.SysUtils, System.Generics.Collections; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDIsoTpTiming = class(Exception); + + TIsoTpFrameKind = ( + iftSingle, // SF — single frame + iftFirst, // FF — first frame of a multi-frame transmission + iftConsecutive, // CF — consecutive frame + iftFlowControl // FC — flow control (sender -> receiver, BS + STmin) + ); + + /// + /// One observed frame on the bus or in a capture. + /// + TIsoTpFrameObservation = record + /// + /// Kind. + /// + Kind: TIsoTpFrameKind; + /// + /// Wall-clock time of the frame in microseconds since + /// some arbitrary t0. Resolution must be at least 1 ms. + /// + TimestampMicros: Int64; + /// + /// Direction. True = tester->ECU, False = ECU->tester. + /// STmin checks apply to the consecutive-frame stream from the + /// sender on whichever side the FC frame came from. + /// + SenderIsTester: Boolean; + end; + + TIsoTpTimingViolationKind = ( + itvIntraGapTooSmall, + itvBlockSizeExceeded, + itvUnexpectedFrameKind + ); + + TIsoTpTimingViolation = record + /// + /// Kind. + /// + Kind: TIsoTpTimingViolationKind; + /// + /// Frame index. + /// + FrameIndex: Integer; + /// + /// Detail. + /// + Detail: string; + end; + + TIsoTpTimingResult = record + /// + /// Compliant. + /// + Compliant: Boolean; + /// + /// Declared stmin micros. + /// + DeclaredStminMicros: Integer; + /// + /// Declared block size. + /// + DeclaredBlockSize: Integer; + /// + /// Violations. + /// + Violations: TArray; + end; + + TOBDIsoTpTimingChecker = class + private + FStminMicros: Integer; + FBlockSize: Integer; + FToleranceMicros: Integer; + /// + /// Note. + /// + procedure Note(var Result: TIsoTpTimingResult; + Kind: TIsoTpTimingViolationKind; FrameIndex: Integer; + const Detail: string); + public + /// + /// Create. + /// + constructor Create; + /// + /// Configure the checker from the FC byte values + /// observed on the wire (STmin: 0x00..0x7F = ms; 0xF1..0xF9 = + /// 100..900 us; BS: 0x00 = unlimited else count). + /// + procedure ApplyFlowControl(const StminByte, BlockSizeByte: Byte); + /// + /// Allow up to this much under-shoot per inter-frame gap + /// before counting as a violation. Default 200 us — within scope + /// timer jitter on a typical adapter. + /// + property ToleranceMicros: Integer read FToleranceMicros write FToleranceMicros; + + /// + /// Audit. + /// + function Audit(const Frames: TArray): TIsoTpTimingResult; + end; + +/// +/// Decode the STmin byte to microseconds. Raises on reserved +/// values (0x80..0xF0 + 0xFA..0xFF). +/// +function DecodeStminMicros(const StminByte: Byte): Integer; + +/// +/// Encode microseconds back to the STmin byte. Quantises to +/// the nearest representable value: 1 ms granularity in [0..127] ms, +/// 100 us granularity in [100..900] us. Out-of-range raises. +/// +function EncodeStminMicros(const Micros: Integer): Byte; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// DECODE STMIN MICROS +//------------------------------------------------------------------------------ +function DecodeStminMicros(const StminByte: Byte): Integer; +begin + if StminByte <= $7F then + Exit(Integer(StminByte) * 1000); + if (StminByte >= $F1) and (StminByte <= $F9) then + Exit(Integer(StminByte - $F0) * 100); + raise EOBDIsoTpTiming.CreateFmt( + 'Reserved STmin byte 0x%.2x', [StminByte]); +end; + +//------------------------------------------------------------------------------ +// ENCODE STMIN MICROS +//------------------------------------------------------------------------------ +function EncodeStminMicros(const Micros: Integer): Byte; +var + Ms: Integer; +begin + if Micros < 0 then + raise EOBDIsoTpTiming.Create('STmin must be non-negative'); + if (Micros >= 100) and (Micros <= 900) and (Micros mod 100 = 0) then + Exit(Byte($F0 + (Micros div 100))); + if Micros mod 1000 = 0 then + begin + Ms := Micros div 1000; + if (Ms >= 0) and (Ms <= $7F) then + Exit(Byte(Ms)); + end; + raise EOBDIsoTpTiming.CreateFmt( + 'STmin %d microseconds is not representable: must be 0..127 ms ' + + 'or 100..900 us in 100us steps', [Micros]); +end; + +{ TOBDIsoTpTimingChecker } + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDIsoTpTimingChecker.Create; +begin + // Call the inherited handler + inherited; + FStminMicros := 0; + FBlockSize := 0; + FToleranceMicros := 200; +end; + +//------------------------------------------------------------------------------ +// APPLY FLOW CONTROL +//------------------------------------------------------------------------------ +procedure TOBDIsoTpTimingChecker.ApplyFlowControl( + const StminByte, BlockSizeByte: Byte); +begin + FStminMicros := DecodeStminMicros(StminByte); + FBlockSize := BlockSizeByte; +end; + +//------------------------------------------------------------------------------ +// NOTE +//------------------------------------------------------------------------------ +procedure TOBDIsoTpTimingChecker.Note(var Result: TIsoTpTimingResult; + Kind: TIsoTpTimingViolationKind; FrameIndex: Integer; + const Detail: string); +var + V: TIsoTpTimingViolation; +begin + Result.Compliant := False; + V.Kind := Kind; + V.FrameIndex := FrameIndex; + V.Detail := Detail; + Result.Violations := Result.Violations + [V]; +end; + +//------------------------------------------------------------------------------ +// AUDIT +//------------------------------------------------------------------------------ +function TOBDIsoTpTimingChecker.Audit( + const Frames: TArray): TIsoTpTimingResult; +var + I: Integer; + PrevCfTimestamp: Int64; + HasPrevCf: Boolean; + CfCountSinceFC: Integer; + Gap: Int64; +begin + Result := Default(TIsoTpTimingResult); + Result.Compliant := True; + Result.DeclaredStminMicros := FStminMicros; + Result.DeclaredBlockSize := FBlockSize; + + HasPrevCf := False; + PrevCfTimestamp := 0; + CfCountSinceFC := 0; + + for I := 0 to High(Frames) do + begin + case Frames[I].Kind of + iftFlowControl: + begin + HasPrevCf := False; + CfCountSinceFC := 0; + end; + iftConsecutive: + begin + if HasPrevCf then + begin + Gap := Frames[I].TimestampMicros - PrevCfTimestamp; + if Gap + FToleranceMicros < FStminMicros then + Note(Result, itvIntraGapTooSmall, I, + Format('gap=%d us < STmin=%d us (tolerance=%d)', + [Gap, FStminMicros, FToleranceMicros])); + end; + PrevCfTimestamp := Frames[I].TimestampMicros; + HasPrevCf := True; + Inc(CfCountSinceFC); + if (FBlockSize > 0) and (CfCountSinceFC > FBlockSize) then + Note(Result, itvBlockSizeExceeded, I, + Format('CF count %d exceeded BlockSize %d without intervening FC', + [CfCountSinceFC, FBlockSize])); + end; + iftFirst, iftSingle: + begin + HasPrevCf := False; + CfCountSinceFC := 0; + end; + end; + end; +end; + +end. diff --git a/src/Protocol/OBD.Protocol.SecOC.pas b/src/Protocol/OBD.Protocol.SecOC.pas new file mode 100644 index 00000000..7b44ca80 --- /dev/null +++ b/src/Protocol/OBD.Protocol.SecOC.pas @@ -0,0 +1,257 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.Protocol.SecOC.pas +// CONTENTS : AUTOSAR SecOC framing and authentication +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.Protocol.SecOC; + +interface + +uses + System.SysUtils, System.Hash; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDSecOC = class(Exception); + EOBDSecOCAlgorithmNotAvailable = class(EOBDSecOC); + EOBDSecOCAuthenticationFailed = class(EOBDSecOC); + + TSecOCProfile = ( + secocProfile1, // CMAC-AES-128, 24-bit FV, 24-bit truncated MAC + secocProfile2, // CMAC-AES-128, full 64-bit FV + secocProfile3 // HMAC-SHA-256 + ); + + TSecOCContext = record + /// + /// Profile. + /// + Profile: TSecOCProfile; + /// + /// Key id. + /// + KeyId: Word; + Key: TBytes; // 16 bytes for CMAC-AES-128, any length for HMAC + /// + /// Freshness value. + /// + FreshnessValue: UInt64; + AuthenticatorBits: Integer; // typically 24 (Profile 1) or 32 / 64 + end; + + /// + /// Compute a SecOC authenticator over Payload bound to + /// FreshnessValue and KeyId. Length of the returned bytes is + /// Ctx.AuthenticatorBits / 8 (rounded up). + /// + function SecOCComputeAuthenticator(const Ctx: TSecOCContext; + const Payload: TBytes): TBytes; + + /// + /// True iff Authenticator matches the expected value for + /// Payload + Ctx. Callers should treat False as a hard failure. + /// + function SecOCVerifyAuthenticator(const Ctx: TSecOCContext; + const Payload, Authenticator: TBytes): Boolean; + + /// + /// Encode the SecOC PDU envelope: + /// uint16 KeyId + /// varbytes FreshnessValue (per-profile width) + /// bytes Payload + /// bytes Authenticator + /// + function SecOCEncodePDU(const Ctx: TSecOCContext; + const Payload, Authenticator: TBytes): TBytes; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// CONSTANTS +//------------------------------------------------------------------------------ +const + SHA256_DIGEST_BYTES = 32; + CMAC_AES_BLOCK_BYTES = 16; + +//------------------------------------------------------------------------------ +// FV WIDTH BYTES +//------------------------------------------------------------------------------ +function FvWidthBytes(P: TSecOCProfile): Integer; +begin + case P of + secocProfile1: Result := 3; // 24 bits + secocProfile2: Result := 8; // 64 bits + secocProfile3: Result := 8; // configurable; 64 is the common default + else + Result := 8; + end; +end; + +//------------------------------------------------------------------------------ +// AUTH LEN BYTES +//------------------------------------------------------------------------------ +function AuthLenBytes(const Ctx: TSecOCContext): Integer; +begin + Result := (Ctx.AuthenticatorBits + 7) div 8; + if Result <= 0 then + raise EOBDSecOC.CreateFmt( + 'AuthenticatorBits must be > 0 (got %d)', [Ctx.AuthenticatorBits]); +end; + +//------------------------------------------------------------------------------ +// FV TO BYTES +//------------------------------------------------------------------------------ +function FvToBytes(P: TSecOCProfile; FV: UInt64): TBytes; +var + Width, I: Integer; +begin + Width := FvWidthBytes(P); + // Allocate Result + SetLength(Result, Width); + for I := 0 to Width - 1 do + Result[Width - 1 - I] := Byte((FV shr (I * 8)) and $FF); +end; + +//------------------------------------------------------------------------------ +// CONCAT BYTES +//------------------------------------------------------------------------------ +function ConcatBytes(const A, B, C: TBytes): TBytes; +var + Off: Integer; +begin + // Allocate Result + SetLength(Result, Length(A) + Length(B) + Length(C)); + Off := 0; + if Length(A) > 0 then + begin + Move(A[0], Result[Off], Length(A)); + Inc(Off, Length(A)); + end; + if Length(B) > 0 then + begin + Move(B[0], Result[Off], Length(B)); + Inc(Off, Length(B)); + end; + if Length(C) > 0 then Move(C[0], Result[Off], Length(C)); +end; + +//------------------------------------------------------------------------------ +// HMAC SHA256 OF MESSAGE +//------------------------------------------------------------------------------ +function HmacSha256OfMessage(const Key, Msg: TBytes): TBytes; +var + Hex: string; + I: Integer; +begin + Hex := THashSHA2.GetHMAC(TEncoding.UTF8.GetString(Msg), + TEncoding.UTF8.GetString(Key), + SHA256); + // GetHMAC returns the digest as hex; turn it back into bytes. We + // don't reuse the existing HexDecode helper to avoid a coupling + // back into OEM.Coding from a protocol-layer unit. + if Length(Hex) <> SHA256_DIGEST_BYTES * 2 then + raise EOBDSecOC.CreateFmt( + 'HMAC-SHA-256 unexpected length %d hex chars', [Length(Hex)]); + // Allocate Result + SetLength(Result, SHA256_DIGEST_BYTES); + for I := 0 to SHA256_DIGEST_BYTES - 1 do + Result[I] := StrToInt('$' + Copy(Hex, I * 2 + 1, 2)); +end; + +//------------------------------------------------------------------------------ +// SEC OCCOMPUTE AUTHENTICATOR +//------------------------------------------------------------------------------ +function SecOCComputeAuthenticator(const Ctx: TSecOCContext; + const Payload: TBytes): TBytes; +var + KeyIdBytes, FvBytes, Msg, Mac: TBytes; + Want: Integer; +begin + if Length(Ctx.Key) = 0 then + raise EOBDSecOC.Create('SecOC context requires a non-empty Key'); + Want := AuthLenBytes(Ctx); + // Allocate KeyIdBytes + SetLength(KeyIdBytes, 2); + KeyIdBytes[0] := Byte(Ctx.KeyId shr 8); + KeyIdBytes[1] := Byte(Ctx.KeyId and $FF); + FvBytes := FvToBytes(Ctx.Profile, Ctx.FreshnessValue); + Msg := ConcatBytes(KeyIdBytes, FvBytes, Payload); + case Ctx.Profile of + secocProfile3: + begin + Mac := HmacSha256OfMessage(Ctx.Key, Msg); + end; + secocProfile1, secocProfile2: + begin + // CMAC-AES-128: not in Delphi RTL. Production binding goes + // through OpenSSL EVP_MAC; until that lands we fail closed. + raise EOBDSecOCAlgorithmNotAvailable.Create( + 'CMAC-AES-128 (SecOC profile 1/2) requires the OpenSSL EVP_MAC ' + + 'binding; not available in this build (see docs/DATA_GAPS.md).'); + end; + end; + if Want > Length(Mac) then + raise EOBDSecOC.CreateFmt( + 'AuthenticatorBits %d exceeds MAC width %d', + [Ctx.AuthenticatorBits, Length(Mac) * 8]); + SetLength(Result, Want); + Move(Mac[0], Result[0], Want); +end; + +//------------------------------------------------------------------------------ +// SEC OCVERIFY AUTHENTICATOR +//------------------------------------------------------------------------------ +function SecOCVerifyAuthenticator(const Ctx: TSecOCContext; + const Payload, Authenticator: TBytes): Boolean; +var + Expected: TBytes; + I: Integer; + Acc: Byte; +begin + Expected := SecOCComputeAuthenticator(Ctx, Payload); + if Length(Authenticator) <> Length(Expected) then Exit(False); + // Constant-time compare to avoid timing-channel leak on MAC value. + Acc := 0; + for I := 0 to High(Expected) do + Acc := Acc or (Expected[I] xor Authenticator[I]); + Result := Acc = 0; +end; + +//------------------------------------------------------------------------------ +// SEC OCENCODE PDU +//------------------------------------------------------------------------------ +function SecOCEncodePDU(const Ctx: TSecOCContext; + const Payload, Authenticator: TBytes): TBytes; +var + KeyIdBytes, FvBytes: TBytes; +begin + // Allocate KeyIdBytes + SetLength(KeyIdBytes, 2); + KeyIdBytes[0] := Byte(Ctx.KeyId shr 8); + KeyIdBytes[1] := Byte(Ctx.KeyId and $FF); + FvBytes := FvToBytes(Ctx.Profile, Ctx.FreshnessValue); + SetLength(Result, Length(KeyIdBytes) + Length(FvBytes) + + Length(Payload) + Length(Authenticator)); + Move(KeyIdBytes[0], Result[0], 2); + if Length(FvBytes) > 0 then + Move(FvBytes[0], Result[2], Length(FvBytes)); + if Length(Payload) > 0 then + Move(Payload[0], Result[2 + Length(FvBytes)], Length(Payload)); + if Length(Authenticator) > 0 then + Move(Authenticator[0], + Result[2 + Length(FvBytes) + Length(Payload)], + Length(Authenticator)); +end; + +end. diff --git a/src/Protocol/OBD.Protocol.WWHOBD.Readiness.pas b/src/Protocol/OBD.Protocol.WWHOBD.Readiness.pas new file mode 100644 index 00000000..f98dbecd --- /dev/null +++ b/src/Protocol/OBD.Protocol.WWHOBD.Readiness.pas @@ -0,0 +1,375 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.Protocol.WWHOBD.Readiness.pas +// CONTENTS : WWH-OBD readiness monitor decoder (ISO 27145-3) +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.Protocol.WWHOBD.Readiness; + +interface + +uses + System.SysUtils; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDWWHOBDReadiness = class(Exception); + + /// + /// One monitor's state. Supported = the ECU has the monitor; + /// Complete = the monitor has run and reported a result this drive + /// cycle. + /// + TWWHOBDMonitorState = record + /// + /// Supported. + /// + Supported: Boolean; + /// + /// Complete. + /// + Complete: Boolean; + end; + + /// + /// Full readiness picture decoded from the FD05 payload. + /// + TWWHOBDReadinessSet = record + /// + /// Mil active. + /// + MILActive: Boolean; + DTCCount: Byte; // 0..127 + + // Continuous monitors (ISO 15031-5 §8.6.1 byte B) + /// + /// Misfire. + /// + Misfire: TWWHOBDMonitorState; + /// + /// Fuel system. + /// + FuelSystem: TWWHOBDMonitorState; + /// + /// Comprehensive. + /// + Comprehensive: TWWHOBDMonitorState; + + // Non-continuous monitors (ISO 27145-3 §6.4 + 15031-5 §8.6.1) + /// + /// Catalyst. + /// + Catalyst: TWWHOBDMonitorState; + /// + /// Heated catalyst. + /// + HeatedCatalyst: TWWHOBDMonitorState; + /// + /// Evaporative system. + /// + EvaporativeSystem: TWWHOBDMonitorState; + /// + /// Secondary air system. + /// + SecondaryAirSystem: TWWHOBDMonitorState; + /// + /// Ac refrigerant. + /// + ACRefrigerant: TWWHOBDMonitorState; + /// + /// Oxygen sensor. + /// + OxygenSensor: TWWHOBDMonitorState; + /// + /// Oxygen sensor heater. + /// + OxygenSensorHeater: TWWHOBDMonitorState; + /// + /// Eg ror vvt system. + /// + EGRorVVTSystem: TWWHOBDMonitorState; + + // ISO 27145-3 additions for diesel / Euro 6+ + /// + /// Nmhc catalyst. + /// + NMHCCatalyst: TWWHOBDMonitorState; + /// + /// N ox aftertreatment. + /// + NOxAftertreatment: TWWHOBDMonitorState; + /// + /// Boost pressure system. + /// + BoostPressureSystem: TWWHOBDMonitorState; + /// + /// Exhaust gas sensor. + /// + ExhaustGasSensor: TWWHOBDMonitorState; + /// + /// Pm filter. + /// + PMFilter: TWWHOBDMonitorState; + /// + /// Egr system. + /// + EGRSystem: TWWHOBDMonitorState; + + /// + /// True iff every supported monitor reports Complete. + /// + function AllReady: Boolean; + /// + /// List of monitor short-names that are supported but + /// not yet complete (the workshop "drive cycle" target list). + /// + function PendingMonitors: TArray; + end; + +/// +/// Decode a 4-byte readiness payload. Spark-ignition (SI) and +/// compression-ignition (CI) layouts share the continuous-monitor byte +/// but differ on the non-continuous one; this decoder produces both +/// fleet sets and the caller picks per-vehicle. +/// +function DecodeWWHOBDReadiness(const Bytes: TBytes): TWWHOBDReadinessSet; + +/// +/// Inverse encoder for round-trip / fixture testing. +/// +function EncodeWWHOBDReadiness(const Set_: TWWHOBDReadinessSet): TBytes; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// CONSTANTS +//------------------------------------------------------------------------------ +const + // ISO 27145-3 §6.4 / ISO 15031-5 §8.6.1 — non-continuous monitor + // bit positions, byte 2 (Supported) / byte 3 (NotComplete = bit set + // means NOT complete; we invert when storing as Complete). + BIT_CATALYST = 0; + BIT_HEATED_CATALYST = 1; + BIT_EVAP = 2; + BIT_SECONDARY_AIR = 3; + BIT_AC_REFRIGERANT = 4; + BIT_OXYGEN_SENSOR = 5; + BIT_OXYGEN_SENSOR_HEAT = 6; + BIT_EGR_OR_VVT = 7; + + // ISO 27145-3 §6.4 extension byte (would be byte 4 on 5-byte form). + // For the 4-byte form, the diesel monitors share bits with the SI + // monitors per the engine-type indicator. We expose them as + // separate fields so the caller decides which to surface. + +//------------------------------------------------------------------------------ +// SET MONITOR +//------------------------------------------------------------------------------ +procedure SetMonitor(var M: TWWHOBDMonitorState; SupportByte, StatusByte: Byte; + Bit: Integer); +begin + M.Supported := (SupportByte and (1 shl Bit)) <> 0; + // In the spec, status bit set means NOT complete. + M.Complete := M.Supported and ((StatusByte and (1 shl Bit)) = 0); +end; + +//------------------------------------------------------------------------------ +// PACK MONITOR +//------------------------------------------------------------------------------ +function PackMonitor(const M: TWWHOBDMonitorState; Bit: Integer; + var + SupportByte, StatusByte: Byte): Boolean; +begin + if M.Supported then + begin + SupportByte := SupportByte or Byte(1 shl Bit); + if not M.Complete then + StatusByte := StatusByte or Byte(1 shl Bit); + end; + // Initialize result + Result := True; +end; + +{ TWWHOBDReadinessSet } + +//------------------------------------------------------------------------------ +// ALL READY +//------------------------------------------------------------------------------ +function TWWHOBDReadinessSet.AllReady: Boolean; + + function MonitorReady(const M: TWWHOBDMonitorState): Boolean; + begin + Result := (not M.Supported) or M.Complete; + end; + +begin + Result := + MonitorReady(Misfire) and MonitorReady(FuelSystem) and + MonitorReady(Comprehensive) and MonitorReady(Catalyst) and + MonitorReady(HeatedCatalyst) and MonitorReady(EvaporativeSystem) and + MonitorReady(SecondaryAirSystem) and MonitorReady(ACRefrigerant) and + MonitorReady(OxygenSensor) and MonitorReady(OxygenSensorHeater) and + MonitorReady(EGRorVVTSystem) and MonitorReady(NMHCCatalyst) and + MonitorReady(NOxAftertreatment) and MonitorReady(BoostPressureSystem) and + MonitorReady(ExhaustGasSensor) and MonitorReady(PMFilter) and + MonitorReady(EGRSystem); +end; + +//------------------------------------------------------------------------------ +// PENDING MONITORS +//------------------------------------------------------------------------------ +function TWWHOBDReadinessSet.PendingMonitors: TArray; + + procedure AddIfPending(var Out_: TArray; + const M: TWWHOBDMonitorState; const Name: string); + begin + if M.Supported and (not M.Complete) then + Out_ := Out_ + [Name]; + end; + +begin + AddIfPending(Result, Misfire, 'Misfire'); + AddIfPending(Result, FuelSystem, 'FuelSystem'); + AddIfPending(Result, Comprehensive, 'Comprehensive'); + AddIfPending(Result, Catalyst, 'Catalyst'); + AddIfPending(Result, HeatedCatalyst, 'HeatedCatalyst'); + AddIfPending(Result, EvaporativeSystem, 'EvaporativeSystem'); + AddIfPending(Result, SecondaryAirSystem, 'SecondaryAirSystem'); + AddIfPending(Result, ACRefrigerant, 'ACRefrigerant'); + AddIfPending(Result, OxygenSensor, 'OxygenSensor'); + AddIfPending(Result, OxygenSensorHeater, 'OxygenSensorHeater'); + AddIfPending(Result, EGRorVVTSystem, 'EGRorVVTSystem'); + AddIfPending(Result, NMHCCatalyst, 'NMHCCatalyst'); + AddIfPending(Result, NOxAftertreatment, 'NOxAftertreatment'); + AddIfPending(Result, BoostPressureSystem, 'BoostPressureSystem'); + AddIfPending(Result, ExhaustGasSensor, 'ExhaustGasSensor'); + AddIfPending(Result, PMFilter, 'PMFilter'); + AddIfPending(Result, EGRSystem, 'EGRSystem'); +end; + +//------------------------------------------------------------------------------ +// DECODE WWHOBDREADINESS +//------------------------------------------------------------------------------ +function DecodeWWHOBDReadiness(const Bytes: TBytes): TWWHOBDReadinessSet; +var + ContByte, NCSupport, NCStatus: Byte; +begin + if Length(Bytes) < 4 then + raise EOBDWWHOBDReadiness.CreateFmt( + 'Readiness payload must be >= 4 bytes (got %d)', [Length(Bytes)]); + + Result := Default(TWWHOBDReadinessSet); + Result.MILActive := (Bytes[0] and $80) <> 0; + Result.DTCCount := Bytes[0] and $7F; + + ContByte := Bytes[1]; + // Continuous monitor encoding per ISO 15031-5 §8.6.1: bit positions + // 0/1/2 = supported (Misfire/FuelSystem/Comprehensive), + // 4/5/6 = NOT-complete. + Result.Misfire.Supported := (ContByte and $01) <> 0; + Result.FuelSystem.Supported := (ContByte and $02) <> 0; + Result.Comprehensive.Supported := (ContByte and $04) <> 0; + Result.Misfire.Complete := Result.Misfire.Supported and ((ContByte and $10) = 0); + Result.FuelSystem.Complete := Result.FuelSystem.Supported and ((ContByte and $20) = 0); + Result.Comprehensive.Complete := Result.Comprehensive.Supported and ((ContByte and $40) = 0); + + NCSupport := Bytes[2]; + NCStatus := Bytes[3]; + SetMonitor(Result.Catalyst, NCSupport, NCStatus, BIT_CATALYST); + SetMonitor(Result.HeatedCatalyst, NCSupport, NCStatus, BIT_HEATED_CATALYST); + SetMonitor(Result.EvaporativeSystem, NCSupport, NCStatus, BIT_EVAP); + SetMonitor(Result.SecondaryAirSystem, NCSupport, NCStatus, BIT_SECONDARY_AIR); + SetMonitor(Result.ACRefrigerant, NCSupport, NCStatus, BIT_AC_REFRIGERANT); + SetMonitor(Result.OxygenSensor, NCSupport, NCStatus, BIT_OXYGEN_SENSOR); + SetMonitor(Result.OxygenSensorHeater, NCSupport, NCStatus, BIT_OXYGEN_SENSOR_HEAT); + SetMonitor(Result.EGRorVVTSystem, NCSupport, NCStatus, BIT_EGR_OR_VVT); + + // ISO 27145-3 §6.4 diesel monitors: when the payload extends to 5+ + // bytes the dedicated extension byte at offset 4 carries the diesel + // monitors; for the 4-byte form the spec overlays them onto the + // existing slots per engine-type indicator. We expose both groups + // and let the caller pick per vehicle. + if Length(Bytes) >= 6 then + begin + SetMonitor(Result.NMHCCatalyst, Bytes[4], Bytes[5], 0); + SetMonitor(Result.NOxAftertreatment, Bytes[4], Bytes[5], 1); + SetMonitor(Result.BoostPressureSystem, Bytes[4], Bytes[5], 2); + SetMonitor(Result.ExhaustGasSensor, Bytes[4], Bytes[5], 3); + SetMonitor(Result.PMFilter, Bytes[4], Bytes[5], 4); + SetMonitor(Result.EGRSystem, Bytes[4], Bytes[5], 5); + end; +end; + +//------------------------------------------------------------------------------ +// ENCODE WWHOBDREADINESS +//------------------------------------------------------------------------------ +function EncodeWWHOBDReadiness(const Set_: TWWHOBDReadinessSet): TBytes; +var + ContByte, NCSupport, NCStatus, NCSupport2, NCStatus2: Byte; + HasDieselGroup: Boolean; + + procedure PackContMonitor(const M: TWWHOBDMonitorState; + SupportBit, StatusBit: Integer); + begin + if M.Supported then ContByte := ContByte or Byte(1 shl SupportBit); + if M.Supported and (not M.Complete) then + ContByte := ContByte or Byte(1 shl StatusBit); + end; + +begin + ContByte := 0; + NCSupport := 0; NCStatus := 0; + NCSupport2 := 0; NCStatus2 := 0; + + PackContMonitor(Set_.Misfire, 0, 4); + PackContMonitor(Set_.FuelSystem, 1, 5); + PackContMonitor(Set_.Comprehensive, 2, 6); + + PackMonitor(Set_.Catalyst, BIT_CATALYST, NCSupport, NCStatus); + PackMonitor(Set_.HeatedCatalyst, BIT_HEATED_CATALYST, NCSupport, NCStatus); + PackMonitor(Set_.EvaporativeSystem, BIT_EVAP, NCSupport, NCStatus); + PackMonitor(Set_.SecondaryAirSystem, BIT_SECONDARY_AIR, NCSupport, NCStatus); + PackMonitor(Set_.ACRefrigerant, BIT_AC_REFRIGERANT, NCSupport, NCStatus); + PackMonitor(Set_.OxygenSensor, BIT_OXYGEN_SENSOR, NCSupport, NCStatus); + PackMonitor(Set_.OxygenSensorHeater, BIT_OXYGEN_SENSOR_HEAT, NCSupport, NCStatus); + PackMonitor(Set_.EGRorVVTSystem, BIT_EGR_OR_VVT, NCSupport, NCStatus); + + HasDieselGroup := + Set_.NMHCCatalyst.Supported or Set_.NOxAftertreatment.Supported or + Set_.BoostPressureSystem.Supported or Set_.ExhaustGasSensor.Supported or + Set_.PMFilter.Supported or Set_.EGRSystem.Supported; + if HasDieselGroup then + begin + PackMonitor(Set_.NMHCCatalyst, 0, NCSupport2, NCStatus2); + PackMonitor(Set_.NOxAftertreatment, 1, NCSupport2, NCStatus2); + PackMonitor(Set_.BoostPressureSystem, 2, NCSupport2, NCStatus2); + PackMonitor(Set_.ExhaustGasSensor, 3, NCSupport2, NCStatus2); + PackMonitor(Set_.PMFilter, 4, NCSupport2, NCStatus2); + PackMonitor(Set_.EGRSystem, 5, NCSupport2, NCStatus2); + // Allocate Result + SetLength(Result, 6); + Result[4] := NCSupport2; + Result[5] := NCStatus2; + end + else + // Allocate Result + SetLength(Result, 4); + + Result[0] := (Set_.DTCCount and $7F); + if Set_.MILActive then Result[0] := Result[0] or $80; + Result[1] := ContByte; + Result[2] := NCSupport; + Result[3] := NCStatus; +end; + +end. diff --git a/src/Protocol/OBD.Protocol.WWHOBD.pas b/src/Protocol/OBD.Protocol.WWHOBD.pas new file mode 100644 index 00000000..aa709626 --- /dev/null +++ b/src/Protocol/OBD.Protocol.WWHOBD.pas @@ -0,0 +1,287 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.Protocol.WWHOBD.pas +// CONTENTS : WWH-OBD support (UN GTR No.5 / ISO 27145) +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.Protocol.WWHOBD; + +interface + +uses + System.SysUtils, System.Generics.Collections; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDWWHOBD = class(Exception); + + /// + /// One DTC packed in J1939-FMI form (4 bytes). + /// + TWWHDtc = record + SPN: UInt32; // 0..524287 (19-bit field) + FMI: Byte; // 0..31 (5-bit field) + OccurrenceCount: Byte; // 0..127 + ConversionMethod: Byte; // 0 = J1939-73 §5.7.1, 1 = §5.7.2 + /// + /// As string. + /// + function AsString: string; // 'SPN 4794, FMI 4 (CM=0, OC=12)' + end; + + /// + /// Standard WWH-OBD DIDs from ISO 27145-3 + UN GTR No.5 + /// Annex A. The values are spec-stable; the host fetches them via + /// UDS 0x22 ReadDataByIdentifier. + /// + TWWHOBDDataIdentifier = record + /// + /// Did. + /// + DID: Word; + /// + /// Name. + /// + Name: string; + /// + /// Description. + /// + Description: string; + end; + +//------------------------------------------------------------------------------ +// CONSTANTS +//------------------------------------------------------------------------------ +const + // ISO 27145-3 Table 1 — Universal WWH-OBD DIDs. + WWHOBD_DID_VIN = $F190; + WWHOBD_DID_VEHICLE_FAMILY_ID = $F197; + WWHOBD_DID_CALIBRATION_ID = $F198; + WWHOBD_DID_CALIBRATION_VERIFICATION = $F199; + WWHOBD_DID_ECU_NAME = $F19A; + WWHOBD_DID_REPAIR_SHOP_CODE = $F198; // overlap; see §6.1.2 note + WWHOBD_DID_PROGRAMMING_DATE = $F184; + WWHOBD_DID_ACTIVE_DIAG_SESSION = $F186; + WWHOBD_DID_PROTOCOL_VERSION = $FD00; + WWHOBD_DID_OBD_REQUIREMENT = $FD01; + WWHOBD_DID_OBDMID_LIST = $FD02; + WWHOBD_DID_DTC_DATA = $FD03; // active DTCs + WWHOBD_DID_PERMANENT_DTC_DATA = $FD04; + WWHOBD_DID_READINESS = $FD05; + WWHOBD_DID_LIVE_DATA = $FD06; + WWHOBD_DID_FREEZE_FRAME = $FD07; + WWHOBD_DID_VEHICLE_MFR_SOFTWARE_NAME = $FD08; + WWHOBD_DID_VEHICLE_MFR_HARDWARE_NUM = $FD09; + WWHOBD_DID_DISTANCE_WITH_MIL_ON = $FD0A; + WWHOBD_DID_DISTANCE_SINCE_DTC_CLEAR = $FD0B; + WWHOBD_DID_TIME_WITH_MIL_ON = $FD0C; + WWHOBD_DID_TIME_SINCE_DTC_CLEAR = $FD0D; + WWHOBD_DID_NUMBER_OF_WARMUPS = $FD0E; + +/// +/// Pack a TWWHDtc into 4 wire bytes per ISO 15031-5 §7. +/// +function PackWWHDtc(const Dtc: TWWHDtc): TBytes; + +/// +/// Unpack 4 wire bytes back into a TWWHDtc. Raises on bad +/// length or out-of-range fields. +/// +function UnpackWWHDtc(const Bytes: TBytes): TWWHDtc; + +/// +/// Convenience: parse a stream of N x 4 DTC blobs. +/// +function UnpackWWHDtcStream(const Bytes: TBytes): TArray; + +/// +/// Look up the human-readable name + description for one of +/// the WWH-OBD DIDs above. Falls back to a synthetic 'DID 0xXXXX' +/// for unknown ids; never raises. +/// +function FindWWHOBDDataIdentifier(DID: Word): TWWHOBDDataIdentifier; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +uses + System.Classes, System.JSON, + OBD.Catalog.Path; + +const + CatalogFileName = 'wwhobd-dids.json'; + +var + GDIDs: TDictionary = nil; + +//------------------------------------------------------------------------------ +// PARSE HEX WORD +//------------------------------------------------------------------------------ +function ParseHexWord(const S: string; out W: Word): Boolean; +var + T: string; + V: Integer; +begin + T := S; + if T.StartsWith('0x', True) then T := '$' + T.Substring(2); + Result := TryStrToInt(T, V) and (V >= 0) and (V <= $FFFF); + if Result then W := Word(V); +end; + +//------------------------------------------------------------------------------ +// LOAD DIDCATALOG +//------------------------------------------------------------------------------ +procedure LoadDIDCatalog; +var + Path, Raw: string; + Doc: TJSONValue; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + D: TWWHOBDDataIdentifier; + Stream: TStringStream; +begin + Path := ResolveCatalogPath(CatalogFileName); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + if not ParseHexWord(Obj.GetValue('did', ''), D.DID) then Continue; + D.Name := Obj.GetValue('name', ''); + D.Description := Obj.GetValue('description', ''); + GDIDs.AddOrSetValue(D.DID, D); + end; + finally + // Free the document + Doc.Free; + end; +end; + +{ TWWHDtc } + +//------------------------------------------------------------------------------ +// AS STRING +//------------------------------------------------------------------------------ +function TWWHDtc.AsString: string; +begin + Result := Format('SPN %d, FMI %d (CM=%d, OC=%d)', + [SPN, FMI, ConversionMethod, OccurrenceCount]); +end; + +//------------------------------------------------------------------------------ +// PACK WWHDTC +//------------------------------------------------------------------------------ +function PackWWHDtc(const Dtc: TWWHDtc): TBytes; +begin + if Dtc.SPN > $7FFFF then + raise EOBDWWHOBD.CreateFmt('SPN %d exceeds 19-bit field', [Dtc.SPN]); + if Dtc.FMI > $1F then + raise EOBDWWHOBD.CreateFmt('FMI %d exceeds 5-bit field', [Dtc.FMI]); + if Dtc.OccurrenceCount > $7F then + raise EOBDWWHOBD.CreateFmt('OC %d exceeds 7-bit field', + [Dtc.OccurrenceCount]); + if Dtc.ConversionMethod > 1 then + raise EOBDWWHOBD.CreateFmt('CM %d not in {0,1}', + [Dtc.ConversionMethod]); + // Allocate Result + SetLength(Result, 4); + Result[0] := Byte(Dtc.SPN and $FF); + Result[1] := Byte((Dtc.SPN shr 8) and $FF); + Result[2] := Byte(((Dtc.SPN shr 16) and $07) shl 5) + or (Dtc.FMI and $1F); + Result[3] := Byte((Dtc.ConversionMethod and $01) shl 7) + or (Dtc.OccurrenceCount and $7F); +end; + +//------------------------------------------------------------------------------ +// UNPACK WWHDTC +//------------------------------------------------------------------------------ +function UnpackWWHDtc(const Bytes: TBytes): TWWHDtc; +var + SpnHi3: Byte; +begin + if Length(Bytes) <> 4 then + raise EOBDWWHOBD.Create('WWH-OBD DTC must be exactly 4 bytes'); + SpnHi3 := (Bytes[2] shr 5) and $07; + Result.SPN := UInt32(Bytes[0]) or (UInt32(Bytes[1]) shl 8) + or (UInt32(SpnHi3) shl 16); + Result.FMI := Bytes[2] and $1F; + Result.ConversionMethod := (Bytes[3] shr 7) and $01; + Result.OccurrenceCount := Bytes[3] and $7F; +end; + +//------------------------------------------------------------------------------ +// UNPACK WWHDTC STREAM +//------------------------------------------------------------------------------ +function UnpackWWHDtcStream(const Bytes: TBytes): TArray; +var + Count, I: Integer; + Slice: TBytes; +begin + if Length(Bytes) mod 4 <> 0 then + raise EOBDWWHOBD.CreateFmt( + 'DTC stream must be multiple of 4 bytes (got %d)', [Length(Bytes)]); + Count := Length(Bytes) div 4; + // Allocate Result + SetLength(Result, Count); + for I := 0 to Count - 1 do + begin + // Allocate Slice + SetLength(Slice, 4); + Move(Bytes[I * 4], Slice[0], 4); + Result[I] := UnpackWWHDtc(Slice); + end; +end; + +//------------------------------------------------------------------------------ +// FIND WWHOBDDATA IDENTIFIER +//------------------------------------------------------------------------------ +function FindWWHOBDDataIdentifier(DID: Word): TWWHOBDDataIdentifier; +begin + if (GDIDs <> nil) and GDIDs.TryGetValue(DID, Result) then Exit; + Result.DID := DID; + Result.Name := Format('DID 0x%.4X', [DID]); + Result.Description := 'Unknown WWH-OBD DID'; +end; + +initialization + GDIDs := TDictionary.Create; + LoadDIDCatalog; + +finalization + GDIDs.Free; + +end. diff --git a/src/Protocol/OBD.Protocol.pas b/src/Protocol/OBD.Protocol.pas index af7b2d21..92530dd7 100644 --- a/src/Protocol/OBD.Protocol.pas +++ b/src/Protocol/OBD.Protocol.pas @@ -258,6 +258,9 @@ procedure TOBDProtocol.BucketizeLines(const Lines: TStrings; out OBDLines, NonOB end; end; +//------------------------------------------------------------------------------ +// INVOKE +//------------------------------------------------------------------------------ function TOBDProtocol.Invoke(const Lines: TStrings): TArray; var diff --git a/src/RadioCode/OBD.RadioCode.Acura.Advanced.pas b/src/RadioCode/OBD.RadioCode.Acura.Advanced.pas index cbed2d96..ca795da1 100644 --- a/src/RadioCode/OBD.RadioCode.Acura.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Acura.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeAcuraAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeAcuraAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeAcuraAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeAcuraAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeAcuraAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeAcuraAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeAcuraAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -154,6 +166,9 @@ function TOBDRadioCodeAcuraAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeAcuraAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -170,6 +185,9 @@ function TOBDRadioCodeAcuraAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeAcuraAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -186,11 +204,17 @@ function TOBDRadioCodeAcuraAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeAcuraAdvanced.GetDescription: string; begin Result := 'Advanced Acura Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeAcuraAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -202,6 +226,9 @@ procedure TOBDRadioCodeAcuraAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeAcuraAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -211,16 +238,25 @@ procedure TOBDRadioCodeAcuraAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeAcuraAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeAcuraAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeAcuraAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -252,6 +288,9 @@ function TOBDRadioCodeAcuraAdvanced.Validate(const Input: string; var ErrorMessa end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeAcuraAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.AlfaRomeo.Advanced.pas b/src/RadioCode/OBD.RadioCode.AlfaRomeo.Advanced.pas index cfae1cfa..106ec737 100644 --- a/src/RadioCode/OBD.RadioCode.AlfaRomeo.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.AlfaRomeo.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeAlfaRomeoAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeAlfaRomeoAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeAlfaRomeoAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeAlfaRomeoAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeAlfaRomeoAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeAlfaRomeoAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeAlfaRomeoAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -167,6 +179,9 @@ function TOBDRadioCodeAlfaRomeoAdvanced.CalculateV1(const Serial: string): strin Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeAlfaRomeoAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -183,6 +198,9 @@ function TOBDRadioCodeAlfaRomeoAdvanced.CalculateV2(const Serial: string): strin Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeAlfaRomeoAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -199,11 +217,17 @@ function TOBDRadioCodeAlfaRomeoAdvanced.CalculateV3(const Serial: string): strin Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeAlfaRomeoAdvanced.GetDescription: string; begin Result := 'Advanced Alfa Romeo Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeAlfaRomeoAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -215,6 +239,9 @@ procedure TOBDRadioCodeAlfaRomeoAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeAlfaRomeoAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -224,16 +251,25 @@ procedure TOBDRadioCodeAlfaRomeoAdvanced.SetVariant(const Region: TRadioCodeRegi FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeAlfaRomeoAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeAlfaRomeoAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeAlfaRomeoAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -274,6 +310,9 @@ function TOBDRadioCodeAlfaRomeoAdvanced.Validate(const Input: string; var ErrorM end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeAlfaRomeoAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Alpine.Advanced.pas b/src/RadioCode/OBD.RadioCode.Alpine.Advanced.pas index 66d2e01d..7c504481 100644 --- a/src/RadioCode/OBD.RadioCode.Alpine.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Alpine.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeAlpineAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeAlpineAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeAlpineAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeAlpineAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeAlpineAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeAlpineAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeAlpineAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -144,6 +156,9 @@ function TOBDRadioCodeAlpineAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeAlpineAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -160,6 +175,9 @@ function TOBDRadioCodeAlpineAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeAlpineAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -176,11 +194,17 @@ function TOBDRadioCodeAlpineAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeAlpineAdvanced.GetDescription: string; begin Result := 'Advanced Alpine Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeAlpineAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -192,6 +216,9 @@ procedure TOBDRadioCodeAlpineAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeAlpineAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -201,16 +228,25 @@ procedure TOBDRadioCodeAlpineAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeAlpineAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeAlpineAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeAlpineAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -226,6 +262,9 @@ function TOBDRadioCodeAlpineAdvanced.Validate(const Input: string; var ErrorMess Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeAlpineAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Audi.Concert.Advanced.pas b/src/RadioCode/OBD.RadioCode.Audi.Concert.Advanced.pas index 6172c151..6379211b 100644 --- a/src/RadioCode/OBD.RadioCode.Audi.Concert.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Audi.Concert.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeAudiConcertAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeAudiConcertAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeAudiConcertAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeAudiConcertAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeAudiConcertAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeAudiConcertAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeAudiConcertAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -176,6 +188,9 @@ function TOBDRadioCodeAudiConcertAdvanced.CalculateV1(const Serial: string): str Output := Format('%d%d%d%d', [Code1, Code2, Code3, Code4]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeAudiConcertAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -192,6 +207,9 @@ function TOBDRadioCodeAudiConcertAdvanced.CalculateV2(const Serial: string): str Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeAudiConcertAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -208,11 +226,17 @@ function TOBDRadioCodeAudiConcertAdvanced.CalculateV3(const Serial: string): str Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeAudiConcertAdvanced.GetDescription: string; begin Result := 'Advanced Audi Concert Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeAudiConcertAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -224,6 +248,9 @@ procedure TOBDRadioCodeAudiConcertAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeAudiConcertAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -233,16 +260,25 @@ procedure TOBDRadioCodeAudiConcertAdvanced.SetVariant(const Region: TRadioCodeRe FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeAudiConcertAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeAudiConcertAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeAudiConcertAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -294,6 +330,9 @@ function TOBDRadioCodeAudiConcertAdvanced.Validate(const Input: string; var Erro end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeAudiConcertAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.BMW.Advanced.pas b/src/RadioCode/OBD.RadioCode.BMW.Advanced.pas index a7154a68..b9b487c9 100644 --- a/src/RadioCode/OBD.RadioCode.BMW.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.BMW.Advanced.pas @@ -276,6 +276,9 @@ procedure TOBDRadioCodeBMWAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeBMWAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; diff --git a/src/RadioCode/OBD.RadioCode.Becker.Advanced.pas b/src/RadioCode/OBD.RadioCode.Becker.Advanced.pas index 6d8bda31..8b7a5674 100644 --- a/src/RadioCode/OBD.RadioCode.Becker.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Becker.Advanced.pas @@ -205,6 +205,9 @@ procedure TOBDRadioCodeBeckerAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeBeckerAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; diff --git a/src/RadioCode/OBD.RadioCode.Becker4.pas b/src/RadioCode/OBD.RadioCode.Becker4.pas index bca1937e..89b17a41 100644 --- a/src/RadioCode/OBD.RadioCode.Becker4.pas +++ b/src/RadioCode/OBD.RadioCode.Becker4.pas @@ -1,19 +1,20 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // UNIT : OBD.RadioCode.Becker4.pas // CONTENTS : Becker Radio Code Calculator (4 Digits) // VERSION : 1.0 // TARGET : Embarcadero Delphi 11 or higher // AUTHOR : Ernst Reidinga (ERDesigns) // STATUS : Open source under Apache 2.0 library -// COMPATIBILITY : Windows 7, 8/8.1, 10, 11 +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android // RELEASE DATE : 13/04/2024 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) //------------------------------------------------------------------------------ unit OBD.RadioCode.Becker4; interface uses - WinApi.Windows, System.SysUtils, + System.SysUtils, OBD.RadioCode; @@ -22,1051 +23,82 @@ interface //------------------------------------------------------------------------------ type /// - /// OBD Becker RadioCode Calculator (4 Digits) + /// OBD Becker RadioCode Calculator (4 Digits). The serial-to-code + /// table (10,000 entries) is loaded from catalogs/radiocode-becker4.json + /// at unit init so a corrected entry can be shipped without recompiling. /// TOBDRadioCodeBecker4 = class(TOBDRadioCode) - private const - /// - /// Database containing all Becker (4 Digits) codes from serial code - /// 0001 until 9999 - /// - Database: array[0..9999] of AnsiString = ( - '1010', '1108', '1016', '1024', '1032', '1050', '1048', '1056', '1064', '1072', - '1090', '1098', '1096', '0104', '0212', '0130', '0128', '0136', '0154', '0152', - '0170', '0168', '0176', '0184', '0192', '0210', '0208', '0216', '0324', '0242', - '0250', '0248', '0256', '0264', '0282', '0290', '0298', '0296', '0304', '0312', - '0320', '0328', '0436', '0354', '0352', '0360', '0368', '0376', '0384', '0392', - '0410', '0408', '0416', '0424', '0432', '0540', '0548', '0456', '0474', '0472', - '0490', '0498', '0496', '0504', '4096', '4104', '4212', '4130', '4128', '4136', - '4154', '4152', '4170', '4168', '4176', '4184', '4192', '4210', '4208', '4216', - '4324', '4242', '4250', '4248', '4256', '4264', '4282', '4290', '4298', '4296', - '4304', '4312', '4320', '4328', '4436', '4354', '4352', '4360', '4368', '4376', - '4384', '4392', '5410', '5408', '5416', '5424', '5432', '5540', '5548', '5456', - '5474', '5472', '5490', '5498', '5496', '4504', '4512', '4530', '4528', '4536', - '4654', '4652', '4560', '4568', '4576', '4584', '4592', '4610', '8192', '8210', - '8208', '8216', '8324', '8242', '8250', '8248', '8256', '8264', '8282', '8290', - '8298', '8296', '8304', '8312', '8320', '8328', '8436', '8354', '8352', '8360', - '8368', '8376', '8384', '8392', '8410', '8408', '8416', '8424', '8432', '8540', - '8548', '8456', '8474', '8472', '8490', '8498', '8496', '8504', '8512', '8530', - '8528', '8536', '8654', '8652', '8560', '8568', '8576', '8584', '8592', '8610', - '8608', '8616', '8624', '8642', '8640', '8648', '8656', '8764', '8672', '8690', - '8698', '8696', '3289', '3297', '2305', '2313', '2321', '2329', '2437', '2345', - '2353', '2361', '2369', '2387', '2385', '2393', '2401', '2409', '2417', '2425', - '2443', '2541', '2549', '2457', '2465', '2473', '2481', '2489', '2497', '2505', - '2513', '2521', '2529', '2537', '2545', '2653', '2561', '2569', '2587', '2585', - '2593', '2601', '2609', '2617', '2625', '2643', '2641', '2649', '2657', '2765', - '2673', '2681', '2689', '2697', '2705', '2713', '2721', '2729', '2737', '2745', - '2753', '2761', '2769', '2787', '2785', '2793', '6385', '6393', '6401', '6409', - '6417', '6425', '6443', '6541', '6549', '6457', '6465', '6473', '6481', '6489', - '6497', '6505', '6513', '6521', '6529', '6537', '6545', '6653', '6561', '6569', - '6587', '6585', '6593', '7601', '7609', '7617', '7625', '7643', '7641', '7649', - '7657', '7765', '7673', '7681', '7689', '7697', '6705', '6713', '6721', '6729', - '6737', '6745', '6753', '6761', '6769', '6787', '6785', '6793', '6801', '6809', - '6817', '6825', '6843', '6841', '6849', '6857', '6865', '6873', '6981', '6989', - '0482', '0510', '0498', '0506', '0514', '0532', '0530', '0538', '0546', '0654', - '0562', '0570', '0578', '0586', '0594', '0602', '0620', '0618', '0626', '0634', - '0642', '0650', '0658', '0676', '0674', '0682', '0710', '0698', '0706', '0714', - '0732', '0730', '0738', '0746', '0754', '0762', '0870', '0878', '0786', '0794', - '0802', '0820', '0818', '0826', '0834', '0842', '0860', '0858', '0876', '0874', - '0982', '0890', '0908', '0906', '0914', '0932', '0930', '0938', '0946', '0954', - '0962', '0970', '0978', '0986', '4578', '4586', '4594', '4602', '4620', '4618', - '4626', '4634', '4642', '4650', '4658', '4676', '4674', '4682', '4710', '4698', - '4706', '4714', '4732', '4730', '4738', '4746', '4754', '4762', '4870', '4878', - '4786', '4794', '4802', '4820', '4818', '4826', '4834', '4842', '4860', '4858', - '4876', '4874', '4982', '4890', '4908', '4906', '4914', '4932', '4930', '4938', - '4946', '4954', '4962', '4970', '4978', '4986', '5094', '5102', '5020', '5018', - '5026', '5034', '5042', '5060', '5058', '5076', '5074', '5082', '8674', '8682', - '8710', '8698', '8706', '8714', '8732', '8730', '8738', '8746', '8754', '8762', - '8870', '8878', '8786', '8794', '9802', '9820', '9818', '9826', '9834', '9842', - '9860', '9858', '9876', '9874', '9982', '9890', '9908', '8906', '8914', '8932', - '8930', '8938', '8946', '8954', '8962', '8970', '8978', '8986', '9094', '9102', - '9020', '9018', '9026', '9034', '9042', '9060', '9058', '9076', '9074', '9082', - '9110', '9098', '9106', '9214', '9132', '9130', '9138', '9146', '9154', '9162', - '9170', '9178', '1101', '1109', '1017', '1025', '1043', '1041', '1049', '1057', - '1065', '1073', '1081', '1089', '1097', '0105', '0213', '0121', '0129', '0137', - '0145', '0153', '0161', '0169', '0187', '0185', '0193', '0201', '0209', '0217', - '0325', '0243', '0241', '0249', '0257', '0265', '0273', '0281', '0289', '0297', - '0305', '0313', '0321', '0329', '0437', '0345', '0353', '0361', '0369', '0387', - '0385', '0393', '0401', '0409', '0417', '0425', '0473', '0541', '0549', '0457', - '0465', '0473', '0481', '0489', '0497', '0505', '4097', '4105', '4213', '4121', - '4129', '4137', '4145', '4153', '4161', '4169', '4187', '4185', '4193', '4201', - '4209', '4217', '4325', '4243', '4241', '4249', '4257', '4265', '4273', '4281', - '4289', '4297', '4305', '4313', '4321', '4329', '4437', '4345', '4353', '4361', - '4369', '4387', '4385', '4393', '5401', '5409', '5417', '5425', '5443', '5541', - '5549', '5457', '5465', '5473', '5481', '5489', '5497', '4505', '4513', '4521', - '4529', '4537', '4545', '4653', '4561', '4569', '4587', '4585', '4593', '4601', - '8193', '8201', '8209', '8217', '8325', '8243', '8241', '8249', '8257', '8265', - '8273', '8281', '8289', '8297', '8305', '8313', '8321', '8329', '8437', '8345', - '8353', '8361', '8369', '8387', '8385', '8393', '8401', '8409', '8417', '8425', - '8443', '8541', '8549', '8457', '8465', '8473', '8481', '8489', '8497', '8505', - '8513', '8521', '8529', '8537', '8545', '8653', '8561', '8569', '8587', '8585', - '8593', '8601', '8609', '8617', '8625', '8643', '8641', '8649', '8657', '8765', - '8673', '8681', '8689', '8697', '3310', '3298', '2306', '2314', '2432', '2430', - '2438', '2346', '2354', '2372', '2370', '2378', '2386', '2394', '2402', '2420', - '2418', '2426', '2434', '2542', '2450', '2458', '2476', '2484', '2482', '2510', - '2498', '2506', '2514', '2532', '2530', '2538', '2546', '2654', '2562', '2570', - '2578', '2586', '2594', '2602', '2620', '2618', '2626', '2634', '2642', '2650', - '2658', '2676', '2674', '2682', '2710', '2698', '2706', '2714', '2732', '2730', - '2738', '2746', '2754', '2762', '2870', '2778', '2786', '2794', '6386', '6394', - '6402', '6420', '6418', '6426', '6434', '6542', '6450', '6458', '6476', '6484', - '6482', '6510', '6498', '6506', '6514', '6532', '6530', '6538', '6546', '6654', - '6562', '6570', '6578', '6586', '6594', '7602', '7620', '7618', '7626', '7634', - '7642', '7650', '7658', '7676', '7674', '7682', '7710', '7698', '6706', '6714', - '6732', '6730', '6738', '6746', '6754', '6762', '6870', '6878', '6786', '6794', - '6802', '6820', '6818', '6826', '6834', '6842', '6860', '6858', '6876', '6874', - '6982', '6890', '0483', '0491', '0509', '0507', '0515', '0523', '0541', '0539', - '0547', '0565', '0563', '0571', '0579', '0587', '0595', '0603', '0621', '0619', - '0627', '0635', '0643', '0651', '0659', '0767', '0675', '0683', '0691', '0709', - '0707', '0715', '0723', '0731', '0739', '0747', '0765', '0763', '0871', '0879', - '0787', '0795', '0803', '0821', '0819', '0827', '0835', '0843', '0851', '0859', - '0867', '0875', '0983', '0891', '0909', '0907', '0915', '0923', '0941', '0939', - '0947', '0965', '0963', '0981', '0979', '0987', '4579', '4587', '4595', '4603', - '4621', '4619', '4627', '4635', '4643', '4651', '4659', '4767', '4675', '4683', - '4691', '4709', '4707', '4715', '4723', '4731', '4739', '4747', '4765', '4763', - '4871', '4879', '4787', '4795', '4803', '4821', '4819', '4827', '4835', '4843', - '4851', '4859', '4867', '4875', '4983', '4891', '4909', '4907', '4915', '4923', - '4941', '4939', '4947', '4965', '4963', '4981', '4979', '4987', '5095', '5103', - '5021', '5019', '5027', '5035', '5043', '5051', '5059', '5067', '5075', '5083', - '8675', '8683', '8691', '8709', '8707', '8715', '8723', '8731', '8739', '8747', - '8765', '8763', '8871', '8879', '8787', '8795', '9803', '9821', '9819', '9827', - '9835', '9843', '9851', '9859', '9867', '9875', '9983', '9891', '9909', '8907', - '8915', '8923', '8941', '8939', '8947', '8965', '8963', '8981', '8979', '8987', - '9095', '9103', '9021', '9019', '9027', '9035', '9043', '9051', '9059', '9067', - '9075', '9083', '9091', '9109', '9107', '9215', '9123', '9141', '9139', '9147', - '9165', '9163', '9181', '9179', '1102', '1020', '1018', '1026', '1034', '1042', - '1060', '1058', '1076', '1074', '1082', '1110', '1098', '0106', '0214', '0132', - '0130', '0138', '0146', '0154', '0162', '0170', '0178', '0186', '0194', '0202', - '0320', '0218', '0326', '0234', '0242', '0260', '0258', '0276', '0274', '0282', - '0310', '0298', '0306', '0314', '0432', '0430', '0438', '0346', '0354', '0372', - '0370', '0378', '0386', '0394', '0402', '0420', '0418', '0426', '0434', '0542', - '0450', '0458', '0476', '0484', '0482', '0510', '0498', '0506', '4098', '4106', - '4214', '4132', '4130', '4138', '4146', '4154', '4162', '4170', '4178', '4186', - '4194', '4202', '4320', '4218', '4326', '4234', '4242', '4260', '4258', '4276', - '4274', '4282', '4310', '4298', '4306', '4314', '4432', '4430', '4438', '4346', - '4354', '4372', '4370', '4378', '4386', '4394', '5402', '5420', '5418', '5426', - '5434', '5542', '5450', '5458', '5476', '5484', '5482', '5510', '5498', '4506', - '4514', '4532', '4530', '4538', '4546', '4654', '4562', '4570', '4578', '4586', - '4594', '4602', '8194', '8202', '8320', '8218', '8326', '8234', '8242', '8260', - '8258', '8276', '8274', '8282', '8310', '8298', '8306', '8314', '8432', '8430', - '8438', '8346', '8354', '8372', '8370', '8378', '8386', '8394', '8402', '8420', - '8418', '8426', '8434', '8542', '8450', '8458', '8476', '8484', '8482', '8510', - '8498', '8506', '8514', '8532', '8530', '8538', '8546', '8654', '8562', '8570', - '8578', '8586', '8594', '8602', '8620', '8618', '8626', '8634', '8642', '8650', - '8658', '8676', '8674', '8682', '8710', '8698', '3291', '3309', '2307', '2315', - '2323', '2431', '2439', '2347', '2365', '2363', '2371', '2379', '2387', '2395', - '2403', '2421', '2419', '2427', '2435', '2543', '2451', '2459', '2467', '2475', - '2483', '2491', '2509', '2507', '2515', '2523', '2541', '2539', '2547', '2565', - '2563', '2571', '2579', '2587', '2595', '2603', '2621', '2619', '2627', '2635', - '2643', '2651', '2659', '2767', '2675', '2683', '2691', '2709', '2707', '2715', - '2723', '2731', '2739', '2747', '2765', '2763', '2871', '2879', '2787', '2795', - '6387', '6395', '6403', '6421', '6419', '6427', '6435', '6543', '6451', '6459', - '6467', '6475', '6483', '6491', '6509', '6507', '6515', '6523', '6541', '6539', - '6547', '6565', '6563', '6571', '6579', '6587', '6595', '7603', '7621', '7619', - '7627', '7635', '7643', '7651', '7659', '7767', '7675', '7683', '7691', '7709', - '6707', '6715', '6723', '6731', '6739', '6747', '6765', '6763', '6871', '6879', - '6787', '6795', '6803', '6821', '6819', '6827', '6835', '6843', '6851', '6859', - '6867', '6875', '6983', '6891', '0484', '0492', '0510', '0508', '0516', '0524', - '0532', '0540', '0548', '0656', '0574', '0572', '0590', '0598', '0596', '0604', - '0612', '0620', '0628', '0636', '0654', '0652', '0760', '0768', '0686', '0684', - '0692', '0710', '0708', '0716', '0724', '0732', '0740', '0748', '0756', '0764', - '0872', '0790', '0798', '0796', '0804', '0812', '0830', '0828', '0836', '0854', - '0852', '0870', '0868', '0876', '0984', '0892', '0910', '0908', '0916', '0924', - '0932', '0950', '0948', '0956', '0964', '0972', '0980', '1098', '4590', '4598', - '4596', '4604', '4612', '4620', '4628', '4636', '4654', '4652', '4760', '4768', - '4686', '4684', '4692', '4710', '4708', '4716', '4724', '4732', '4740', '4748', - '4756', '4764', '4872', '4790', '4798', '4796', '4804', '4812', '4830', '4828', - '4836', '4854', '4852', '4870', '4868', '4876', '4984', '4892', '4910', '4908', - '4916', '4924', '4932', '4950', '4948', '4956', '4964', '4972', '4980', '5098', - '5096', '5104', '5012', '5030', '5028', '5036', '5054', '5052', '5070', '5068', - '5076', '5084', '8686', '8684', '8692', '8710', '8708', '8716', '8724', '8732', - '8740', '8748', '8756', '8764', '8872', '8790', '8798', '8796', '9804', '9812', - '9830', '9828', '9836', '9854', '9852', '9870', '9868', '9876', '9984', '9892', - '8910', '8908', '8916', '8924', '8932', '8950', '8948', '8956', '8964', '8972', - '8980', '9098', '9096', '9104', '9012', '9030', '9028', '9036', '9054', '9052', - '9070', '9068', '9076', '9084', '9092', '9210', '9108', '9216', '9124', '9132', - '9150', '9148', '9156', '9164', '9172', '9190', '1103', '1021', '1019', '1027', - '1035', '1043', '1051', '1059', '1067', '1075', '1083', '1091', '1109', '0107', - '0215', '0123', '0141', '0139', '0147', '0165', '0163', '0181', '0179', '0187', - '0195', '0203', '0221', '0219', '0327', '0235', '0243', '0251', '0259', '0267', - '0275', '0283', '0291', '0309', '0307', '0315', '0323', '0431', '0439', '0347', - '0365', '0363', '0371', '0379', '0387', '0395', '0403', '0421', '0419', '0427', - '0435', '0543', '0451', '0459', '0467', '0475', '0483', '0491', '0509', '0507', - '4109', '4107', '4215', '4123', '4141', '4139', '4147', '4165', '4163', '4181', - '4179', '4187', '4195', '4203', '4221', '4219', '4327', '4235', '4243', '4251', - '4259', '4267', '4275', '4283', '4291', '4309', '4307', '4315', '4323', '4431', - '4439', '4347', '4365', '4363', '4371', '4379', '4387', '4395', '5403', '5421', - '5419', '5427', '5435', '5543', '5451', '5459', '5467', '5475', '5483', '5491', - '5509', '4507', '4515', '4523', '4541', '4539', '4547', '4565', '4563', '4571', - '4579', '4587', '4595', '4603', '8195', '8203', '8221', '8219', '8327', '8235', - '8243', '8251', '8259', '8267', '8275', '8283', '8291', '8309', '8307', '8315', - '8323', '8431', '8439', '8347', '8365', '8363', '8371', '8379', '8387', '8395', - '8403', '8421', '8419', '8427', '8435', '8543', '8451', '8459', '8467', '8475', - '8483', '8491', '8509', '8507', '8515', '8523', '8541', '8539', '8547', '8565', - '8563', '8571', '8579', '8587', '8595', '8603', '8621', '8619', '8627', '8635', - '8643', '8651', '8659', '8767', '8675', '8683', '8691', '8709', '3292', '2310', - '2308', '2316', '2324', '2432', '2350', '2348', '2356', '2364', '2372', '2390', - '2398', '2396', '2404', '2412', '2430', '2428', '2436', '2454', '2452', '2460', - '2468', '2476', '2484', '2492', '2510', '2508', '2516', '2524', '2532', '2540', - '2548', '2656', '2574', '2572', '2590', '2598', '2596', '2604', '2612', '2620', - '2628', '2636', '2654', '2652', '2760', '2768', '2686', '2684', '2692', '2710', - '2708', '2716', '2724', '2732', '2740', '2748', '2756', '2764', '2872', '2790', - '2798', '2796', '6398', '6396', '6404', '6412', '6430', '6428', '6436', '6454', - '6452', '6460', '6468', '6476', '6484', '6492', '6510', '6508', '6516', '6524', - '6532', '6540', '6548', '6656', '6574', '6572', '6590', '6598', '6596', '7604', - '7612', '7620', '7628', '7636', '7654', '7652', '7760', '7768', '7686', '7684', - '7692', '6710', '6708', '6716', '6724', '6732', '6740', '6748', '6756', '6764', - '6872', '6790', '6798', '6796', '6804', '6812', '6830', '6828', '6836', '6854', - '6852', '6870', '6868', '6876', '6984', '6892', '0485', '0493', '0501', '0509', - '0517', '0525', '0543', '0541', '0549', '0657', '0565', '0573', '0581', '0589', - '0597', '0605', '0613', '0621', '0629', '0637', '0645', '0653', '0761', '0769', - '0687', '0685', '0693', '0701', '0709', '0717', '0725', '0743', '0741', '0749', - '0757', '0765', '0873', '0781', '0789', '0797', '0805', '0813', '0821', '0829', - '0837', '0845', '0853', '0861', '0869', '0887', '0985', '0893', '0901', '0909', - '0917', '0925', '0943', '0941', '0949', '0957', '0965', '0973', '0981', '0989', - '4581', '4589', '4597', '4605', '4613', '4621', '4629', '4637', '4645', '4653', - '4761', '4769', '4687', '4685', '4693', '4701', '4709', '4717', '4725', '4743', - '4741', '4749', '4757', '4765', '4873', '4781', '4789', '4797', '4805', '4813', - '4821', '4829', '4837', '4845', '4853', '4861', '4869', '4887', '4985', '4893', - '4901', '4909', '4917', '4925', '4943', '4941', '4949', '4957', '4965', '4973', - '4981', '4989', '5097', '5105', '5013', '5021', '5029', '5037', '5045', '5053', - '5061', '5069', '5087', '5085', '8687', '8685', '8693', '8701', '8709', '8717', - '8725', '8743', '8741', '8749', '8757', '8765', '8873', '8781', '8789', '8797', - '9805', '9813', '9821', '9829', '9837', '9845', '9853', '9861', '9869', '9887', - '9985', '9893', '8901', '8909', '8917', '8925', '8943', '8941', '8949', '8957', - '8965', '8973', '8981', '8989', '9097', '9105', '9013', '9021', '9029', '9037', - '9045', '9053', '9061', '9069', '9087', '9085', '9093', '9101', '9109', '9217', - '9125', '9143', '9141', '9149', '9157', '9165', '9173', '9181', '1104', '1012', - '1030', '1028', '1036', '1054', '1052', '1070', '1068', '1076', '1084', '1092', - '0210', '0108', '0216', '0124', '0132', '0150', '0148', '0156', '0164', '0172', - '0190', '0198', '0196', '0204', '0212', '0320', '0328', '0236', '0254', '0252', - '0260', '0268', '0276', '0284', '0292', '0310', '0308', '0316', '0324', '0432', - '0350', '0348', '0356', '0364', '0372', '0390', '0398', '0396', '0404', '0412', - '0430', '0428', '0436', '0454', '0452', '0460', '0468', '0476', '0484', '0492', - '0510', '0508', '4210', '4108', '4216', '4124', '4132', '4150', '4148', '4156', - '4164', '4172', '4190', '4198', '4196', '4204', '4212', '4320', '4328', '4236', - '4254', '4252', '4260', '4268', '4276', '4284', '4292', '4310', '4308', '4316', - '4324', '4432', '4350', '4348', '4356', '4364', '4372', '4390', '4398', '4396', - '5404', '5412', '5430', '5428', '5436', '5454', '5452', '5460', '5468', '5476', - '5484', '5492', '4510', '4508', '4516', '4524', '4532', '4540', '4548', '4656', - '4574', '4572', '4590', '4598', '4596', '4604', '8196', '8204', '8212', '8320', - '8328', '8236', '8254', '8252', '8260', '8268', '8276', '8284', '8292', '8310', - '8308', '8316', '8324', '8432', '8350', '8348', '8356', '8364', '8372', '8390', - '8398', '8396', '8404', '8412', '8430', '8428', '8436', '8454', '8452', '8460', - '8468', '8476', '8484', '8492', '8510', '8508', '8516', '8524', '8532', '8540', - '8548', '8656', '8574', '8572', '8590', '8598', '8596', '8604', '8612', '8620', - '8628', '8636', '8654', '8652', '8760', '8768', '8686', '8684', '8692', '8710', - '3293', '2301', '2309', '2317', '2325', '2343', '2341', '2349', '2357', '2365', - '2383', '2381', '2389', '2397', '2405', '2413', '2421', '2429', '2437', '2545', - '2453', '2461', '2469', '2487', '2485', '2493', '2501', '2509', '2517', '2525', - '2543', '2541', '2549', '2657', '2565', '2573', '2581', '2589', '2597', '2605', - '2613', '2621', '2629', '2637', '2645', '2653', '2761', '2769', '2687', '2685', - '2693', '2701', '2709', '2717', '2725', '2743', '2741', '2749', '2757', '2765', - '2873', '2781', '2789', '2797', '6389', '6397', '6405', '6413', '6421', '6429', - '6437', '6545', '6453', '6461', '6469', '6487', '6485', '6493', '6501', '6509', - '6517', '6525', '6543', '6541', '6549', '6657', '6565', '6573', '6581', '6589', - '6597', '7605', '7613', '7621', '7629', '7637', '7645', '7653', '7761', '7769', - '7687', '7685', '7693', '6701', '6709', '6717', '6725', '6743', '6741', '6749', - '6757', '6765', '6873', '6781', '6789', '6797', '6805', '6813', '6821', '6829', - '6837', '6845', '6853', '6861', '6869', '6887', '6985', '6893', '0486', '0494', - '0502', '0510', '0518', '0526', '0534', '0542', '0650', '0658', '0576', '0574', - '0582', '0590', '0598', '0606', '0614', '0632', '0630', '0638', '0646', '0764', - '0762', '0670', '0678', '0686', '0694', '0702', '0710', '0718', '0726', '0734', - '0742', '0750', '0758', '0876', '0874', '0782', '0790', '0798', '0806', '0814', - '0832', '0840', '0838', '0846', '0854', '0862', '0980', '0878', '0986', '0894', - '0902', '0910', '0918', '0926', '0934', '0942', '0950', '0958', '0976', '0974', - '0982', '1090', '4582', '4590', '4598', '4606', '4614', '4632', '4630', '4638', - '4646', '4764', '4762', '4670', '4678', '4686', '4694', '4702', '4710', '4718', - '4726', '4734', '4742', '4750', '4758', '4876', '4874', '4782', '4790', '4798', - '4806', '4814', '4832', '4840', '4838', '4846', '4854', '4862', '4980', '4878', - '4986', '4894', '4902', '4910', '4918', '4926', '4934', '4942', '4950', '4958', - '4976', '4974', '4982', '5090', '5098', '5106', '5014', '5032', '5040', '5038', - '5046', '5054', '5062', '5080', '5078', '5086', '8678', '8686', '8694', '8702', - '8710', '8718', '8726', '8734', '8742', '8750', '8758', '8876', '8874', '8782', - '8790', '8798', '9806', '9814', '9832', '9840', '9838', '9846', '9854', '9862', - '9980', '9878', '9986', '9894', '8902', '8910', '8918', '8926', '8934', '8942', - '8950', '8958', '8976', '8974', '8982', '9090', '9098', '9106', '9014', '9032', - '9040', '9038', '9046', '9054', '9062', '9080', '9078', '9086', '9094', '9102', - '9210', '9218', '9126', '9134', '9142', '9150', '9158', '9176', '9174', '9182', - '1105', '1013', '1021', '1029', '1037', '1045', '1053', '1061', '1069', '1087', - '1085', '1093', '0101', '0109', '0217', '0125', '0143', '0141', '0149', '0157', - '0165', '0173', '0181', '0189', '0197', '0205', '0213', '0321', '0329', '0237', - '0245', '0253', '0261', '0269', '0287', '0285', '0293', '0301', '0309', '0317', - '0325', '0343', '0341', '0349', '0357', '0365', '0383', '0381', '0389', '0397', - '0405', '0413', '0421', '0429', '0437', '0545', '0453', '0461', '0469', '0487', - '0485', '0493', '0501', '0509', '4101', '4109', '4217', '4125', '4143', '4141', - '4149', '4157', '4165', '4173', '4181', '4189', '4197', '4205', '4213', '4321', - '4329', '4237', '4245', '4253', '4261', '4269', '4287', '4285', '4293', '4301', - '4309', '4317', '4325', '4343', '4341', '4349', '4357', '4365', '4383', '4381', - '4389', '4397', '5405', '5413', '5421', '5429', '5437', '5545', '5453', '5461', - '5469', '5487', '5485', '5493', '4501', '4509', '4517', '4525', '4543', '4541', - '4549', '4657', '4565', '4573', '4581', '4589', '4597', '4605', '8197', '8205', - '8213', '8321', '8329', '8237', '8245', '8253', '8261', '8269', '8287', '8285', - '8293', '8301', '8309', '8317', '8325', '8343', '8341', '8349', '8357', '8365', - '8383', '8381', '8389', '8397', '8405', '8413', '8421', '8429', '8437', '8545', - '8453', '8461', '8469', '8487', '8485', '8493', '8501', '8509', '8517', '8525', - '8543', '8541', '8549', '8657', '8565', '8573', '8581', '8589', '8597', '8605', - '8613', '8621', '8629', '8637', '8645', '8653', '8761', '8769', '8687', '8685', - '8693', '8701', '3294', '2302', '2310', '2318', '2326', '2434', '2342', '2350', - '2358', '2376', '2374', '2382', '2390', '2398', '2406', '2414', '2432', '2540', - '2438', '2546', '2464', '2462', '2470', '2478', '2486', '2494', '2502', '2510', - '2518', '2526', '2534', '2542', '2650', '2658', '2576', '2574', '2582', '2590', - '2598', '2606', '2614', '2632', '2630', '2638', '2646', '2764', '2762', '2670', - '2678', '2686', '2694', '2702', '2710', '2718', '2726', '2734', '2742', '2750', - '2758', '2876', '2874', '2782', '2790', '2798', '6390', '6398', '6406', '6414', - '6432', '6540', '6438', '6546', '6464', '6462', '6470', '6478', '6486', '6494', - '6502', '6510', '6518', '6526', '6534', '6542', '6650', '6658', '6576', '6574', - '6582', '6590', '6598', '7606', '7614', '7632', '7630', '7638', '7646', '7764', - '7762', '7670', '7678', '7686', '7694', '6702', '6710', '6718', '6726', '6734', - '6742', '6750', '6758', '6876', '6874', '6782', '6790', '6798', '6806', '6814', - '6832', '6840', '6838', '6846', '6854', '6862', '6980', '6878', '6986', '6894', - '0487', '0495', '0503', '0521', '0519', '0527', '0535', '0543', '0651', '0659', - '0567', '0585', '0583', '0601', '0609', '0607', '0615', '0623', '0631', '0639', - '0647', '0665', '0763', '0671', '0679', '0687', '0695', '0703', '0721', '0719', - '0727', '0735', '0743', '0751', '0759', '0767', '0875', '0783', '0801', '0809', - '0807', '0815', '0823', '0831', '0839', '0847', '0865', '0863', '0871', '0879', - '0987', '0895', '0903', '0921', '0919', '0927', '0935', '0943', '0961', '0959', - '0967', '0975', '0983', '1091', '4583', '4601', '4609', '4607', '4615', '4623', - '4631', '4639', '4647', '4665', '4763', '4671', '4679', '4687', '4695', '4703', - '4721', '4719', '4727', '4735', '4743', '4751', '4759', '4767', '4875', '4783', - '4801', '4809', '4807', '4815', '4823', '4831', '4839', '4847', '4865', '4863', - '4871', '4879', '4987', '4895', '4903', '4921', '4919', '4927', '4935', '4943', - '4961', '4959', '4967', '4975', '4983', '5091', '5109', '5107', '5015', '5023', - '5031', '5039', '5047', '5065', '5063', '5071', '5079', '5087', '8679', '8687', - '8695', '8703', '8721', '8719', '8727', '8735', '8743', '8751', '8759', '8767', - '8875', '8783', '8801', '8809', '9807', '9815', '9823', '9831', '9839', '9847', - '9865', '9863', '9871', '9879', '9987', '9895', '8903', '8921', '8919', '8927', - '8935', '8943', '8961', '8959', '8967', '8975', '8983', '9091', '9109', '9107', - '9015', '9023', '9031', '9039', '9047', '9065', '9063', '9071', '9079', '9087', - '9095', '9103', '9121', '9219', '9127', '9135', '9143', '9161', '9159', '9167', - '9175', '9183', '1106', '1014', '1032', '1040', '1038', '1046', '1054', '1062', - '1080', '1078', '1086', '1094', '0102', '0210', '0218', '0126', '0134', '0142', - '0150', '0158', '0176', '0174', '0182', '0190', '0198', '0206', '0214', '0232', - '0230', '0238', '0246', '0254', '0272', '0270', '0278', '0286', '0294', '0302', - '0310', '0318', '0326', '0434', '0342', '0350', '0358', '0376', '0374', '0382', - '0390', '0398', '0406', '0414', '0432', '0540', '0438', '0546', '0464', '0462', - '0470', '0478', '0486', '0494', '0502', '0510', '4102', '4210', '4218', '4126', - '4134', '4142', '4150', '4158', '4176', '4174', '4182', '4190', '4198', '4206', - '4214', '4232', '4230', '4238', '4246', '4254', '4272', '4270', '4278', '4286', - '4294', '4302', '4310', '4318', '4326', '4434', '4342', '4350', '4358', '4376', - '4374', '4382', '4390', '4398', '5406', '5414', '5432', '5540', '5438', '5546', - '5464', '5462', '5470', '5478', '5486', '5494', '4502', '4510', '4518', '4526', - '4534', '4542', '4650', '4658', '4576', '4574', '4582', '4590', '4598', '4606', - '8198', '8206', '8214', '8232', '8230', '8238', '8246', '8254', '8272', '8270', - '8278', '8286', '8294', '8302', '8310', '8318', '8326', '8434', '8342', '8350', - '8358', '8376', '8374', '8382', '8390', '8398', '8406', '8414', '8432', '8540', - '8438', '8546', '8464', '8462', '8470', '8478', '8486', '8494', '8502', '8510', - '8518', '8526', '8534', '8542', '8650', '8658', '8576', '8574', '8582', '8590', - '8598', '8606', '8614', '8632', '8630', '8638', '8646', '8764', '8762', '8670', - '8678', '8686', '8694', '8702', '3295', '2303', '2321', '2319', '2327', '2435', - '2343', '2361', '2359', '2367', '2375', '2383', '2401', '2409', '2407', '2415', - '2423', '2431', '2439', '2547', '2465', '2463', '2471', '2479', '2487', '2495', - '2503', '2521', '2519', '2527', '2535', '2543', '2651', '2659', '2567', '2585', - '2583', '2601', '2609', '2607', '2615', '2623', '2631', '2639', '2647', '2665', - '2763', '2671', '2679', '2687', '2695', '2703', '2721', '2719', '2727', '2735', - '2743', '2751', '2759', '2767', '2875', '2783', '2801', '2809', '6401', '6409', - '6407', '6415', '6423', '6431', '6439', '6547', '6465', '6463', '6471', '6479', - '6487', '6495', '6503', '6521', '6519', '6527', '6535', '6543', '6651', '6659', - '6567', '6585', '6583', '6601', '6609', '7607', '7615', '7623', '7631', '7639', - '7647', '7665', '7763', '7671', '7679', '7687', '7695', '6703', '6721', '6719', - '6727', '6735', '6743', '6751', '6759', '6767', '6875', '6783', '6801', '6809', - '6807', '6815', '6823', '6831', '6839', '6847', '6865', '6863', '6871', '6879', - '6987', '6895', '0498', '0496', '0504', '0512', '0530', '0528', '0536', '0654', - '0652', '0560', '0568', '0576', '0584', '0592', '0610', '0608', '0616', '0624', - '0642', '0640', '0648', '0656', '0764', '0672', '0690', '0698', '0696', '0704', - '0712', '0720', '0728', '0736', '0754', '0752', '0760', '0768', '0876', '0784', - '0792', '0810', '0808', '0816', '0824', '0832', '0850', '0848', '0856', '0864', - '0872', '0980', '0898', '0896', '0904', '0912', '0930', '0928', '0936', '0954', - '0952', '0970', '0968', '0976', '0984', '1092', '4584', '4592', '4610', '4608', - '4616', '4624', '4642', '4640', '4648', '4656', '4764', '4672', '4690', '4698', - '4696', '4704', '4712', '4720', '4728', '4736', '4754', '4752', '4760', '4768', - '4876', '4784', '4792', '4810', '4808', '4816', '4824', '4832', '4850', '4848', - '4856', '4864', '4872', '4980', '4898', '4896', '4904', '4912', '4930', '4928', - '4936', '4954', '4952', '4970', '4968', '4976', '4984', '5092', '5010', '5108', - '5016', '5024', '5032', '5050', '5048', '5056', '5064', '5072', '5090', '5098', - '8690', '8698', '8696', '8704', '8712', '8720', '8728', '8736', '8754', '8752', - '8760', '8768', '8876', '8784', '8792', '9810', '9808', '9816', '9824', '9832', - '9850', '9848', '9856', '9864', '9872', '9980', '9898', '9896', '8904', '8912', - '8930', '8928', '8936', '8954', '8952', '8970', '8968', '8976', '8984', '9092', - '9010', '9108', '9016', '9024', '9032', '9050', '9048', '9056', '9064', '9072', - '9090', '9098', '9096', '9104', '9212', '9130', '9128', '9136', '9154', '9152', - '9170', '9168', '9176', '9184', '1107', '1015', '1023', '1031', '1039', '1047', - '1065', '1063', '1071', '1079', '1087', '1095', '0103', '0121', '0219', '0127', - '0135', '0143', '0161', '0159', '0167', '0175', '0183', '0201', '0209', '0207', - '0215', '0323', '0231', '0239', '0247', '0265', '0263', '0271', '0279', '0287', - '0295', '0303', '0321', '0319', '0327', '0435', '0343', '0361', '0359', '0367', - '0375', '0383', '0401', '0409', '0407', '0415', '0423', '0431', '0439', '0547', - '0465', '0463', '0471', '0479', '0487', '0495', '0503', '0521', '4103', '4121', - '4219', '4127', '4135', '4143', '4161', '4159', '4167', '4175', '4183', '4201', - '4209', '4207', '4215', '4323', '4231', '4239', '4247', '4265', '4263', '4271', - '4279', '4287', '4295', '4303', '4321', '4319', '4327', '4435', '4343', '4361', - '4359', '4367', '4375', '4383', '4401', '4409', '5407', '5415', '5423', '5431', - '5439', '5547', '5465', '5463', '5471', '5479', '5487', '5495', '4503', '4521', - '4519', '4527', '4535', '4543', '4651', '4659', '4567', '4585', '4583', '4601', - '4609', '4607', '8209', '8207', '8215', '8323', '8231', '8239', '8247', '8265', - '8263', '8271', '8279', '8287', '8295', '8303', '8321', '8319', '8327', '8435', - '8343', '8361', '8359', '8367', '8375', '8383', '8401', '8409', '8407', '8415', - '8423', '8431', '8439', '8547', '8465', '8463', '8471', '8479', '8487', '8495', - '8503', '8521', '8519', '8527', '8535', '8543', '8651', '8659', '8567', '8585', - '8583', '8601', '8609', '8607', '8615', '8623', '8631', '8639', '8647', '8665', - '8763', '8671', '8679', '8687', '8695', '8703', '3296', '2304', '2312', '2320', - '2328', '2436', '2354', '2352', '2360', '2368', '2376', '2384', '2392', '2410', - '2408', '2416', '2424', '2432', '2540', '2548', '2456', '2474', '2472', '2490', - '2498', '2496', '2504', '2512', '2530', '2528', '2536', '2654', '2652', '2560', - '2568', '2576', '2584', '2592', '2610', '2608', '2616', '2624', '2642', '2640', - '2648', '2656', '2764', '2672', '2690', '2698', '2696', '2704', '2712', '2720', - '2728', '2736', '2754', '2752', '2760', '2768', '2876', '2784', '2792', '2810', - '6392', '6410', '6408', '6416', '6424', '6432', '6540', '6548', '6456', '6474', - '6472', '6490', '6498', '6496', '6504', '6512', '6530', '6528', '6536', '6654', - '6652', '6560', '6568', '6576', '6584', '6592', '7610', '7608', '7616', '7624', - '7642', '7640', '7648', '7656', '7764', '7672', '7690', '7698', '7696', '6704', - '6712', '6720', '6728', '6736', '6754', '6752', '6760', '6768', '6876', '6784', - '6792', '6810', '6808', '6816', '6824', '6832', '6850', '6848', '6856', '6864', - '6872', '6980', '6898', '6896', '0489', '0497', '0505', '0513', '0521', '0529', - '0537', '0545', '0653', '0561', '0569', '0587', '0585', '0593', '0601', '0609', - '0617', '0625', '0643', '0641', '0649', '0657', '0765', '0673', '0681', '0689', - '0697', '0705', '0713', '0721', '0729', '0737', '0745', '0753', '0761', '0769', - '0787', '0785', '0793', '0801', '0809', '0817', '0825', '0843', '0841', '0849', - '0857', '0865', '0873', '0981', '0989', '0897', '0905', '0913', '0921', '0929', - '0937', '0945', '0953', '0961', '0969', '0987', '0985', '1093', '4585', '4593', - '4601', '4609', '4617', '4625', '4643', '4641', '4649', '4657', '4765', '4673', - '4681', '4689', '4697', '4705', '4713', '4721', '4729', '4737', '4745', '4753', - '4761', '4769', '4787', '4785', '4793', '4801', '4809', '4817', '4825', '4843', - '4841', '4849', '4857', '4865', '4873', '4981', '4989', '4897', '4905', '4913', - '4921', '4929', '4937', '4945', '4953', '4961', '4969', '4987', '4985', '5093', - '5101', '5109', '5017', '5025', '5043', '5041', '5049', '5057', '5065', '5073', - '5081', '5089', '8681', '8689', '8697', '8705', '8713', '8721', '8729', '8737', - '8745', '8753', '8761', '8769', '8787', '8785', '8793', '9801', '9809', '9817', - '9825', '9843', '9841', '9849', '9857', '9865', '9873', '9981', '9889', '9897', - '8905', '8913', '8921', '8929', '8937', '8945', '8953', '8961', '8969', '8987', - '8985', '9093', '9101', '9109', '9017', '9025', '9043', '9041', '9049', '9057', - '9065', '9073', '9081', '9089', '9097', '9105', '9213', '9121', '9129', '9137', - '9145', '9153', '9161', '9169', '9187', '9185', '0512', '0530', '0528', '0536', - '0654', '0652', '0560', '0568', '0576', '0584', '0592', '0610', '0608', '0616', - '0624', '0642', '0640', '0648', '0656', '0764', '0672', '0690', '0698', '0696', - '0704', '0712', '0720', '0728', '0736', '0754', '0752', '0760', '0768', '0876', - '0784', '0792', '0810', '0808', '0816', '0824', '0832', '0850', '0848', '0856', - '0864', '0872', '0980', '0898', '0896', '0904', '0912', '0930', '0928', '0936', - '0954', '0952', '0970', '0968', '0976', '0984', '1092', '1010', '1108', '1016', - '4608', '4616', '4624', '4642', '4640', '4648', '4656', '4764', '4672', '4690', - '4698', '4696', '4704', '4712', '4720', '4728', '4736', '4754', '4752', '4760', - '4768', '4876', '4784', '4792', '4810', '4808', '4816', '4824', '4832', '4850', - '4848', '4856', '4864', '4872', '4980', '4898', '4896', '4904', '4912', '4930', - '4928', '4936', '4954', '4952', '4970', '4968', '4976', '4984', '5092', '5010', - '5108', '5016', '5024', '5032', '5050', '5048', '5056', '5064', '5072', '5090', - '5098', '5096', '5104', '5212', '8704', '8712', '8720', '8728', '8736', '8754', - '8752', '8760', '8768', '8876', '8784', '8792', '9810', '9808', '9816', '9824', - '9832', '9850', '9848', '9856', '9864', '9872', '9980', '9898', '9896', '8904', - '8912', '8930', '8928', '8936', '8954', '8952', '8970', '8968', '8976', '8984', - '9092', '9010', '9108', '9016', '9024', '9032', '9050', '9048', '9056', '9064', - '9072', '9090', '9098', '9096', '9104', '9212', '9130', '9128', '9136', '9154', - '9152', '9170', '9168', '9176', '9184', '9192', '9210', '9208', '2801', '2809', - '2817', '2825', '2843', '2841', '2849', '2857', '2865', '2873', '2981', '2989', - '2897', '2905', '2913', '2921', '2929', '2937', '2945', '2953', '2961', '2969', - '2987', '2985', '3093', '3101', '3109', '3017', '3025', '3043', '3041', '3049', - '3057', '3065', '3073', '3081', '3089', '3097', '3105', '3213', '3121', '3129', - '3137', '3145', '3153', '3161', '3169', '3187', '3185', '3193', '3201', '3209', - '3217', '3325', '3243', '3241', '3249', '3257', '3265', '3273', '3281', '3289', - '3297', '4305', '6897', '6905', '6913', '6921', '6929', '6937', '6945', '6953', - '6961', '6969', '6987', '6985', '7093', '7101', '7109', '7017', '7025', '7043', - '7041', '7049', '7057', '7065', '7073', '7081', '7089', '7097', '7105', '7213', - '7121', '7129', '7137', '7145', '7153', '7161', '7169', '7187', '7185', '7193', - '7201', '7209', '7217', '7325', '7243', '7241', '7249', '7257', '7265', '7273', - '7281', '7289', '7297', '7305', '7313', '7321', '7329', '7437', '7345', '7353', - '7361', '7369', '7387', '7385', '7393', '7401', '1094', '1102', '1020', '1018', - '1026', '1034', '1042', '1060', '1058', '1076', '1074', '1082', '1110', '1098', - '2106', '2214', '2132', '2130', '2138', '2146', '2154', '2162', '2170', '2178', - '2186', '2194', '1202', '1320', '1218', '1326', '1234', '1242', '1260', '1258', - '1276', '1274', '1282', '1310', '1298', '1306', '1314', '1432', '1430', '1438', - '1346', '1354', '1372', '1370', '1378', '1386', '1394', '1402', '1420', '1418', - '1426', '1434', '1542', '1450', '1458', '1476', '1484', '1482', '1510', '1498', - '5110', '5098', '5106', '5214', '5132', '5130', '5138', '5146', '5154', '5162', - '5170', '5178', '5186', '5194', '5202', '5320', '5218', '5326', '5234', '5242', - '5260', '5258', '5276', '5274', '5282', '5310', '5298', '5306', '5314', '5432', - '5430', '5438', '5346', '5354', '5372', '5370', '5378', '5386', '5394', '5402', - '5420', '5418', '5426', '5434', '5542', '5450', '5458', '5476', '5484', '5482', - '5510', '5498', '6506', '6514', '6532', '6530', '6538', '6546', '6654', '6562', - '6570', '6578', '6586', '6594', '9186', '9194', '9202', '9320', '9218', '9326', - '9234', '9242', '9260', '9258', '9276', '9274', '9282', '9310', '9298', '9306', - '9314', '9432', '9430', '9438', '9346', '9354', '9372', '9370', '9378', '9386', - '9394', '9402', '9420', '9418', '9426', '9434', '9542', '9450', '9458', '9476', - '9484', '9482', '9510', '9498', '9506', '9514', '9532', '9530', '9538', '9546', - '9654', '9562', '9570', '9578', '9586', '9594', '9602', '9620', '9618', '9626', - '9634', '9642', '9650', '9658', '9676', '9674', '9682', '9710', '0513', '0521', - '0529', '0537', '0545', '0653', '0561', '0569', '0587', '0585', '0593', '0601', - '0609', '0617', '0625', '0643', '0641', '0649', '0657', '0765', '0673', '0681', - '0689', '0697', '0705', '0713', '0721', '0729', '0737', '0745', '0753', '0761', - '0769', '0787', '0785', '0793', '0801', '0809', '0817', '0825', '0843', '0841', - '0849', '0857', '0865', '0873', '0981', '0989', '0897', '0905', '0913', '0921', - '0929', '0937', '0945', '0953', '0961', '0969', '0987', '0985', '1093', '1101', - '1109', '1017', '4609', '4617', '4625', '4643', '4641', '4649', '4657', '4765', - '4673', '4681', '4689', '4697', '4705', '4713', '4721', '4729', '4737', '4745', - '4753', '4761', '4769', '4787', '4785', '4793', '4801', '4809', '4817', '4825', - '4843', '4841', '4849', '4857', '4865', '4873', '4981', '4989', '4897', '4905', - '4913', '4921', '4929', '4937', '4945', '4953', '4961', '4969', '4987', '4985', - '5093', '5101', '5109', '5017', '5025', '5043', '5041', '5049', '5057', '5065', - '5073', '5081', '5089', '5097', '5105', '5213', '8705', '8713', '8721', '8729', - '8737', '8745', '8753', '8761', '8769', '8787', '8785', '8793', '9801', '9809', - '9817', '9825', '9843', '9841', '9849', '9857', '9865', '9873', '9981', '9989', - '9897', '8905', '8913', '8921', '8929', '8937', '8945', '8953', '8961', '8969', - '8987', '8985', '9093', '9101', '9109', '9017', '9025', '9043', '9041', '9049', - '9057', '9065', '9073', '9081', '9089', '9097', '9105', '9213', '9121', '9129', - '9137', '9145', '9153', '9161', '9169', '9187', '9185', '9193', '9201', '9209', - '2802', '2820', '2818', '2826', '2834', '2842', '2860', '2858', '2876', '2874', - '2982', '2890', '2908', '2906', '2914', '2932', '2930', '2938', '2946', '2954', - '2962', '2970', '2978', '2986', '3094', '3102', '3020', '3018', '3026', '3034', - '3042', '3060', '3058', '3076', '3074', '3082', '3110', '3098', '3106', '3214', - '3132', '3130', '3138', '3146', '3154', '3162', '3170', '3178', '3186', '3194', - '3202', '3320', '3218', '3326', '3234', '3242', '3260', '3258', '3276', '3274', - '3282', '3310', '3298', '4306', '6908', '6906', '6914', '6932', '6930', '6938', - '6946', '6954', '6962', '6970', '6978', '6986', '7094', '7102', '7020', '7018', - '7026', '7034', '7042', '7060', '7058', '7076', '7074', '7082', '7110', '7098', - '7106', '7214', '7132', '7130', '7138', '7146', '7154', '7162', '7170', '7178', - '7186', '7194', '7202', '7320', '7218', '7326', '7234', '7242', '7260', '7258', - '7276', '7274', '7282', '7310', '7298', '7306', '7314', '7432', '7430', '7438', - '7346', '7354', '7372', '7370', '7378', '7386', '7394', '7402', '1095', '1103', - '1021', '1019', '1027', '1035', '1043', '1051', '1059', '1067', '1075', '1083', - '1091', '1109', '2107', '2215', '2123', '2141', '2139', '2147', '2165', '2163', - '2181', '2179', '2187', '2195', '1203', '1221', '1219', '1327', '1235', '1243', - '1251', '1259', '1267', '1275', '1283', '1291', '1309', '1307', '1315', '1323', - '1431', '1439', '1347', '1365', '1363', '1371', '1379', '1387', '1395', '1403', - '1421', '1419', '1427', '1435', '1543', '1451', '1459', '1467', '1475', '1483', - '1491', '1509', '5091', '5109', '5107', '5215', '5123', '5141', '5139', '5147', - '5165', '5163', '5181', '5179', '5187', '5195', '5203', '5221', '5219', '5327', - '5235', '5243', '5251', '5259', '5267', '5275', '5283', '5291', '5309', '5307', - '5315', '5323', '5431', '5439', '5347', '5365', '5363', '5371', '5379', '5387', - '5395', '5403', '5421', '5419', '5427', '5435', '5543', '5451', '5459', '5467', - '5475', '5483', '5491', '5509', '6507', '6515', '6523', '6541', '6539', '6547', - '6565', '6563', '6571', '6579', '6587', '6595', '9187', '9195', '9203', '9221', - '9219', '9327', '9235', '9243', '9251', '9259', '9267', '9275', '9283', '9291', - '9309', '9307', '9315', '9323', '9431', '9439', '9347', '9365', '9363', '9371', - '9379', '9387', '9395', '9403', '9421', '9419', '9427', '9435', '9543', '9451', - '9459', '9467', '9475', '9483', '9491', '9509', '9507', '9515', '9523', '9541', - '9539', '9547', '9565', '9563', '9571', '9579', '9587', '9595', '9603', '9621', - '9619', '9627', '9635', '9643', '9651', '9659', '9767', '9675', '9683', '9691', - '0514', '0532', '0530', '0538', '0546', '0654', '0562', '0570', '0578', '0586', - '0594', '0602', '0620', '0618', '0626', '0634', '0642', '0650', '0658', '0676', - '0674', '0682', '0710', '0698', '0706', '0714', '0732', '0730', '0738', '0746', - '0754', '0762', '0870', '0878', '0786', '0794', '0802', '0820', '0818', '0826', - '0834', '0842', '0860', '0858', '0876', '0874', '0982', '0890', '0908', '0906', - '0914', '0932', '0930', '0938', '0946', '0954', '0962', '0970', '0978', '0986', - '1094', '1102', '1020', '1018', '4620', '4618', '4626', '4634', '4642', '4650', - '4658', '4676', '4674', '4682', '4710', '4698', '4706', '4714', '4732', '4730', - '4738', '4746', '4754', '4762', '4870', '4878', '4786', '4794', '4802', '4820', - '4818', '4826', '4834', '4842', '4860', '4858', '4876', '4874', '4982', '4890', - '4908', '4906', '4914', '4932', '4930', '4938', '4946', '4954', '4962', '4970', - '4978', '4986', '5094', '5102', '5020', '5018', '5026', '5034', '5042', '5060', - '5058', '5076', '5074', '5082', '5110', '5098', '5106', '5214', '8706', '8714', - '8732', '8730', '8738', '8746', '8754', '8762', '8870', '8878', '8786', '8794', - '9802', '9820', '9818', '9826', '9834', '9842', '9860', '9858', '9876', '9874', - '9982', '9890', '9908', '8906', '8914', '8932', '8930', '8938', '8946', '8954', - '8962', '8970', '8978', '8986', '9094', '9102', '9020', '9018', '9026', '9034', - '9042', '9060', '9058', '9076', '9074', '9082', '9110', '9098', '9106', '9214', - '9132', '9130', '9138', '9146', '9154', '9162', '9170', '9178', '9186', '9194', - '9202', '9320', '2803', '2821', '2819', '2827', '2835', '2843', '2851', '2859', - '2867', '2875', '2983', '2891', '2909', '2907', '2915', '2923', '2941', '2939', - '2947', '2965', '2963', '2981', '2979', '2987', '3095', '3103', '3021', '3019', - '3027', '3035', '3043', '3051', '3059', '3067', '3075', '3083', '3091', '3109', - '3107', '3215', '3123', '3141', '3139', '3147', '3165', '3163', '3181', '3179', - '3187', '3195', '3203', '3221', '3219', '3327', '3235', '3243', '3251', '3259', - '3267', '3275', '3283', '3291', '3309', '4307', '6909', '6907', '6915', '6923', - '6941', '6939', '6947', '6965', '6963', '6981', '6979', '6987', '7095', '7103', - '7021', '7019', '7027', '7035', '7043', '7051', '7059', '7067', '7075', '7083', - '7091', '7109', '7107', '7215', '7123', '7141', '7139', '7147', '7165', '7163', - '7181', '7179', '7187', '7195', '7203', '7221', '7219', '7327', '7235', '7243', - '7251', '7259', '7267', '7275', '7283', '7291', '7309', '7307', '7315', '7323', - '7431', '7439', '7347', '7365', '7363', '7371', '7379', '7387', '7395', '7403', - '1096', '1104', '1012', '1030', '1028', '1036', '1054', '1052', '1070', '1068', - '1076', '1084', '1092', '2210', '2108', '2216', '2124', '2132', '2150', '2148', - '2156', '2164', '2172', '2190', '2198', '2196', '1204', '1212', '1320', '1328', - '1236', '1254', '1252', '1260', '1268', '1276', '1284', '1292', '1310', '1308', - '1316', '1324', '1432', '1350', '1348', '1356', '1364', '1372', '1390', '1398', - '1396', '1404', '1412', '1430', '1428', '1436', '1454', '1452', '1460', '1468', - '1476', '1484', '1492', '1510', '5092', '5210', '5108', '5216', '5124', '5132', - '5150', '5148', '5156', '5164', '5172', '5190', '5198', '5196', '5204', '5212', - '5320', '5328', '5236', '5254', '5252', '5260', '5268', '5276', '5284', '5292', - '5310', '5308', '5316', '5324', '5432', '5350', '5348', '5356', '5364', '5372', - '5390', '5398', '5396', '5404', '5412', '5430', '5428', '5436', '5454', '5452', - '5460', '5468', '5476', '5484', '5492', '5510', '6508', '6516', '6524', '6532', - '6540', '6548', '6656', '6574', '6572', '6590', '6598', '6596', '9198', '9196', - '9204', '9212', '9320', '9328', '9236', '9254', '9252', '9260', '9268', '9276', - '9284', '9292', '9310', '9308', '9316', '9324', '9432', '9350', '9348', '9356', - '9364', '9372', '9390', '9398', '9396', '9404', '9412', '9430', '9428', '9436', - '9454', '9452', '9460', '9468', '9476', '9484', '9492', '9510', '9508', '9516', - '9524', '9532', '9540', '9548', '9656', '9574', '9572', '9590', '9598', '9596', - '9604', '9612', '9620', '9628', '9636', '9654', '9652', '9760', '9768', '9686', - '9684', '9692', '0515', '0523', '0541', '0539', '0547', '0565', '0563', '0571', - '0579', '0587', '0595', '0603', '0621', '0619', '0627', '0635', '0643', '0651', - '0659', '0767', '0675', '0683', '0691', '0709', '0707', '0715', '0723', '0731', - '0739', '0747', '0765', '0763', '0871', '0879', '0787', '0795', '0803', '0821', - '0819', '0827', '0835', '0843', '0851', '0859', '0867', '0875', '0983', '0891', - '0909', '0907', '0915', '0923', '0941', '0939', '0947', '0965', '0963', '0981', - '0979', '0987', '1095', '1103', '1021', '1019', '4621', '4619', '4627', '4635', - '4643', '4651', '4659', '4767', '4675', '4683', '4691', '4709', '4707', '4715', - '4723', '4731', '4739', '4747', '4765', '4763', '4871', '4879', '4787', '4795', - '4803', '4821', '4819', '4827', '4835', '4843', '4851', '4859', '4867', '4875', - '4983', '4891', '4909', '4907', '4915', '4923', '4941', '4939', '4947', '4965', - '4963', '4981', '4979', '4987', '5095', '5103', '5021', '5019', '5027', '5035', - '5043', '5051', '5059', '5067', '5075', '5083', '5091', '5109', '5107', '5215', - '8707', '8715', '8723', '8731', '8739', '8747', '8765', '8763', '8871', '8879', - '8787', '8795', '9803', '9821', '9819', '9827', '9835', '9843', '9851', '9859', - '9867', '9875', '9983', '9891', '9909', '8907', '8915', '8923', '8941', '8939', - '8947', '8965', '8963', '8981', '8979', '8987', '9095', '9103', '9021', '9019', - '9027', '9035', '9043', '9051', '9059', '9067', '9075', '9083', '9091', '9109', - '9107', '9215', '9123', '9141', '9139', '9147', '9165', '9163', '9181', '9179', - '9187', '9195', '9203', '9221', '2804', '2812', '2830', '2828', '2836', '2854', - '2852', '2870', '2868', '2876', '2984', '2892', '2910', '2908', '2916', '2924', - '2932', '2950', '2948', '2956', '2964', '2972', '2980', '3098', '3096', '3104', - '3012', '3030', '3028', '3036', '3054', '3052', '3070', '3068', '3076', '3084', - '3092', '3210', '3108', '3216', '3124', '3132', '3150', '3148', '3156', '3164', - '3172', '3190', '3198', '3196', '3204', '3212', '3320', '3328', '3236', '3254', - '3252', '3260', '3268', '3276', '3284', '3292', '4310', '4308', '6910', '6908', - '6916', '6924', '6932', '6950', '6948', '6956', '6964', '6972', '6980', '7098', - '7096', '7104', '7012', '7030', '7028', '7036', '7054', '7052', '7070', '7068', - '7076', '7084', '7092', '7210', '7108', '7216', '7124', '7132', '7150', '7148', - '7156', '7164', '7172', '7190', '7198', '7196', '7204', '7212', '7320', '7328', - '7236', '7254', '7252', '7260', '7268', '7276', '7284', '7292', '7310', '7308', - '7316', '7324', '7432', '7350', '7348', '7356', '7364', '7372', '7390', '7398', - '7396', '7404', '1097', '1105', '1013', '1021', '1029', '1037', '1045', '1053', - '1061', '1069', '1087', '1085', '1093', '2101', '2109', '2217', '2125', '2143', - '2141', '2149', '2157', '2165', '2173', '2181', '2189', '2197', '1205', '1213', - '1321', '1329', '1237', '1245', '1253', '1261', '1269', '1287', '1285', '1293', - '1301', '1309', '1317', '1325', '1343', '1341', '1349', '1357', '1365', '1383', - '1381', '1389', '1397', '1405', '1413', '1421', '1429', '1437', '1545', '1453', - '1461', '1469', '1487', '1485', '1493', '1501', '5093', '5101', '5109', '5217', - '5125', '5143', '5141', '5149', '5157', '5165', '5173', '5181', '5189', '5197', - '5205', '5213', '5321', '5329', '5237', '5245', '5253', '5261', '5269', '5287', - '5285', '5293', '5301', '5309', '5317', '5325', '5343', '5341', '5349', '5357', - '5365', '5383', '5381', '5389', '5397', '5405', '5413', '5421', '5429', '5437', - '5545', '5453', '5461', '5469', '5487', '5485', '5493', '6501', '6509', '6517', - '6525', '6543', '6541', '6549', '6657', '6565', '6573', '6581', '6589', '6597', - '9189', '9197', '9205', '9213', '9321', '9329', '9237', '9245', '9253', '9261', - '9269', '9287', '9285', '9293', '9301', '9309', '9317', '9325', '9343', '9341', - '9349', '9357', '9365', '9383', '9381', '9389', '9397', '9405', '9413', '9421', - '9429', '9437', '9545', '9453', '9461', '9469', '9487', '9485', '9493', '9501', - '9509', '9517', '9525', '9543', '9541', '9549', '9657', '9565', '9573', '9581', - '9589', '9597', '9605', '9613', '9621', '9629', '9637', '9645', '9653', '9761', - '9769', '9687', '9685', '9693', '0516', '0524', '0532', '0540', '0548', '0656', - '0574', '0572', '0590', '0598', '0596', '0604', '0612', '0620', '0628', '0636', - '0654', '0652', '0760', '0768', '0686', '0684', '0692', '0710', '0708', '0716', - '0724', '0732', '0740', '0748', '0756', '0764', '0872', '0790', '0798', '0796', - '0804', '0812', '0830', '0828', '0836', '0854', '0852', '0870', '0868', '0876', - '0984', '0892', '0910', '0908', '0916', '0924', '0932', '0950', '0948', '0956', - '0964', '0972', '0980', '1098', '1096', '1104', '1012', '1030', '4612', '4620', - '4628', '4636', '4654', '4652', '4760', '4768', '4686', '4684', '4692', '4710', - '4708', '4716', '4724', '4732', '4740', '4748', '4756', '4764', '4872', '4790', - '4798', '4796', '4804', '4812', '4830', '4828', '4836', '4854', '4852', '4870', - '4868', '4876', '4984', '4892', '4910', '4908', '4916', '4924', '4932', '4950', - '4948', '4956', '4964', '4972', '4980', '5098', '5096', '5104', '5012', '5030', - '5028', '5036', '5054', '5052', '5070', '5068', '5076', '5084', '5092', '5210', - '5108', '5216', '8708', '8716', '8724', '8732', '8740', '8748', '8756', '8764', - '8872', '8790', '8798', '8796', '9804', '9812', '9830', '9828', '9836', '9854', - '9852', '9870', '9868', '9876', '9984', '9892', '8910', '8908', '8916', '8924', - '8932', '8950', '8948', '8956', '8964', '8972', '8980', '9098', '9096', '9104', - '9012', '9030', '9028', '9036', '9054', '9052', '9070', '9068', '9076', '9084', - '9092', '9210', '9108', '9216', '9124', '9132', '9150', '9148', '9156', '9164', - '9172', '9190', '9198', '9196', '9204', '9212', '2805', '2813', '2821', '2829', - '2837', '2845', '2853', '2861', '2869', '2887', '2985', '2893', '2901', '2909', - '2917', '2925', '2943', '2941', '2949', '2957', '2965', '2973', '2981', '2989', - '3097', '3105', '3013', '3021', '3029', '3037', '3045', '3053', '3061', '3069', - '3087', '3085', '3093', '3101', '3109', '3217', '3125', '3143', '3141', '3149', - '3157', '3165', '3173', '3181', '3189', '3197', '3205', '3213', '3321', '3329', - '3237', '3245', '3253', '3261', '3269', '3287', '3285', '3293', '4301', '4309', - '6901', '6909', '6917', '6925', '6943', '6941', '6949', '6957', '6965', '6973', - '6981', '6989', '7097', '7105', '7013', '7021', '7029', '7037', '7045', '7053', - '7061', '7069', '7087', '7085', '7093', '7101', '7109', '7217', '7125', '7143', - '7141', '7149', '7157', '7165', '7173', '7181', '7189', '7197', '7205', '7213', - '7321', '7329', '7237', '7245', '7253', '7261', '7269', '7287', '7285', '7293', - '7301', '7309', '7317', '7325', '7343', '7341', '7349', '7357', '7365', '7383', - '7381', '7389', '7397', '7405', '1098', '1106', '1014', '1032', '1040', '1038', - '1046', '1054', '1062', '1080', '1078', '1086', '1094', '2102', '2210', '2218', - '2126', '2134', '2142', '2150', '2158', '2176', '2174', '2182', '2190', '2198', - '1206', '1214', '1232', '1230', '1238', '1246', '1254', '1272', '1270', '1278', - '1286', '1294', '1302', '1310', '1318', '1326', '1434', '1342', '1350', '1358', - '1376', '1374', '1382', '1390', '1398', '1406', '1414', '1432', '1540', '1438', - '1546', '1464', '1462', '1470', '1478', '1486', '1494', '1502', '5094', '5102', - '5210', '5218', '5126', '5134', '5142', '5150', '5158', '5176', '5174', '5182', - '5190', '5198', '5206', '5214', '5232', '5230', '5238', '5246', '5254', '5272', - '5270', '5278', '5286', '5294', '5302', '5310', '5318', '5326', '5434', '5342', - '5350', '5358', '5376', '5374', '5382', '5390', '5398', '5406', '5414', '5432', - '5540', '5438', '5546', '5464', '5462', '5470', '5478', '5486', '5494', '6502', - '6510', '6518', '6526', '6534', '6542', '6650', '6658', '6576', '6574', '6582', - '6590', '6598', '9190', '9198', '9206', '9214', '9232', '9230', '9238', '9246', - '9254', '9272', '9270', '9278', '9286', '9294', '9302', '9310', '9318', '9326', - '9434', '9342', '9350', '9358', '9376', '9374', '9382', '9390', '9398', '9406', - '9414', '9432', '9540', '9438', '9546', '9464', '9462', '9470', '9478', '9486', - '9494', '9502', '9510', '9518', '9526', '9534', '9542', '9650', '9658', '9576', - '9574', '9582', '9590', '9598', '9606', '9614', '9632', '9630', '9638', '9646', - '9764', '9762', '9670', '9678', '9686', '9694', '0517', '0525', '0543', '0541', - '0549', '0657', '0565', '0573', '0581', '0589', '0597', '0605', '0613', '0621', - '0629', '0637', '0645', '0653', '0761', '0769', '0687', '0685', '0693', '0701', - '0709', '0717', '0725', '0743', '0741', '0749', '0757', '0765', '0873', '0781', - '0789', '0797', '0805', '0813', '0821', '0829', '0837', '0845', '0853', '0861', - '0869', '0887', '0985', '0893', '0901', '0909', '0917', '0925', '0943', '0941', - '0949', '0957', '0965', '0973', '0981', '0989', '1097', '1105', '1013', '1021', - '4613', '4621', '4629', '4637', '4645', '4653', '4761', '4769', '4687', '4685', - '4693', '4701', '4709', '4717', '4725', '4743', '4741', '4749', '4757', '4765', - '4873', '4781', '4789', '4797', '4805', '4813', '4821', '4829', '4837', '4845', - '4853', '4861', '4869', '4887', '4985', '4893', '4901', '4909', '4917', '4925', - '4943', '4941', '4949', '4957', '4965', '4973', '4981', '4989', '5097', '5105', - '5013', '5021', '5029', '5037', '5045', '5053', '5061', '5069', '5087', '5085', - '5093', '5101', '5109', '5217', '8709', '8717', '8725', '8743', '8741', '8749', - '8757', '8765', '8873', '8781', '8789', '8797', '9805', '9813', '9821', '9829', - '9837', '9845', '9853', '9861', '9869', '9887', '9985', '9893', '8901', '8909', - '8917', '8925', '8943', '8941', '8949', '8957', '8965', '8973', '8981', '8989', - '9097', '9105', '9013', '9021', '9029', '9037', '9045', '9053', '9061', '9069', - '9087', '9085', '9093', '9101', '9109', '9217', '9125', '9143', '9141', '9149', - '9157', '9165', '9173', '9181', '9189', '9197', '9205', '9213', '2806', '2814', - '2832', '2840', '2838', '2846', '2854', '2862', '2980', '2878', '2986', '2894', - '2902', '2910', '2918', '2926', '2934', '2942', '2950', '2958', '2976', '2974', - '2982', '3090', '3098', '3106', '3014', '3032', '3040', '3038', '3046', '3054', - '3062', '3080', '3078', '3086', '3094', '3102', '3210', '3218', '3126', '3134', - '3142', '3150', '3158', '3176', '3174', '3182', '3190', '3198', '3206', '3214', - '3232', '3230', '3238', '3246', '3254', '3272', '3270', '3278', '3286', '3294', - '4302', '4310', '6902', '6910', '6918', '6926', '6934', '6942', '6950', '6958', - '6976', '6974', '6982', '7090', '7098', '7106', '7014', '7032', '7040', '7038', - '7046', '7054', '7062', '7080', '7078', '7086', '7094', '7102', '7210', '7218', - '7126', '7134', '7142', '7150', '7158', '7176', '7174', '7182', '7190', '7198', - '7206', '7214', '7232', '7230', '7238', '7246', '7254', '7272', '7270', '7278', - '7286', '7294', '7302', '7310', '7318', '7326', '7434', '7342', '7350', '7358', - '7376', '7374', '7382', '7390', '7398', '7406', '1109', '1107', '1015', '1023', - '1031', '1039', '1047', '1065', '1063', '1071', '1079', '1087', '1095', '2103', - '2121', '2219', '2127', '2135', '2143', '2161', '2159', '2167', '2175', '2183', - '2201', '2209', '1207', '1215', '1323', '1231', '1239', '1247', '1265', '1263', - '1271', '1279', '1287', '1295', '1303', '1321', '1319', '1327', '1435', '1343', - '1361', '1359', '1367', '1375', '1383', '1401', '1409', '1407', '1415', '1423', - '1431', '1439', '1547', '1465', '1463', '1471', '1479', '1487', '1495', '1503', - '5095', '5103', '5121', '5219', '5127', '5135', '5143', '5161', '5159', '5167', - '5175', '5183', '5201', '5209', '5207', '5215', '5323', '5231', '5239', '5247', - '5265', '5263', '5271', '5279', '5287', '5295', '5303', '5321', '5319', '5327', - '5435', '5343', '5361', '5359', '5367', '5375', '5383', '5401', '5409', '5407', - '5415', '5423', '5431', '5439', '5547', '5465', '5463', '5471', '5479', '5487', - '5495', '6503', '6521', '6519', '6527', '6535', '6543', '6651', '6659', '6567', - '6585', '6583', '6601', '6609', '9201', '9209', '9207', '9215', '9323', '9231', - '9239', '9247', '9265', '9263', '9271', '9279', '9287', '9295', '9303', '9321', - '9319', '9327', '9435', '9343', '9361', '9359', '9367', '9375', '9383', '9401', - '9409', '9407', '9415', '9423', '9431', '9439', '9547', '9465', '9463', '9471', - '9479', '9487', '9495', '9503', '9521', '9519', '9527', '9535', '9543', '9651', - '9659', '9567', '9585', '9583', '9601', '9609', '9607', '9615', '9623', '9631', - '9639', '9647', '9665', '9763', '9671', '9679', '9687', '9695', '0518', '0526', - '0534', '0542', '0650', '0658', '0576', '0574', '0582', '0590', '0598', '0606', - '0614', '0632', '0630', '0638', '0646', '0764', '0762', '0670', '0678', '0686', - '0694', '0702', '0710', '0718', '0726', '0734', '0742', '0750', '0758', '0876', - '0874', '0782', '0790', '0798', '0806', '0814', '0832', '0840', '0838', '0846', - '0854', '0862', '0980', '0878', '0986', '0894', '0902', '0910', '0918', '0926', - '0934', '0942', '0950', '0958', '0976', '0974', '0982', '1090', '1098', '1106', - '1014', '1032', '4614', '4632', '4630', '4638', '4646', '4764', '4762', '4670', - '4678', '4686', '4694', '4702', '4710', '4718', '4726', '4734', '4742', '4750', - '4758', '4876', '4874', '4782', '4790', '4798', '4806', '4814', '4832', '4840', - '4838', '4846', '4854', '4862', '4980', '4878', '4986', '4894', '4902', '4910', - '4918', '4926', '4934', '4942', '4950', '4958', '4976', '4974', '4982', '5090', - '5098', '5106', '5014', '5032', '5040', '5038', '5046', '5054', '5062', '5080', - '5078', '5086', '5094', '5102', '5210', '5218', '8710', '8718', '8726', '8734', - '8742', '8750', '8758', '8876', '8874', '8782', '8790', '8798', '9806', '9814', - '9832', '9840', '9838', '9846', '9854', '9862', '9980', '9878', '9986', '9894', - '8902', '8910', '8918', '8926', '8934', '8942', '8950', '8958', '8976', '8974', - '8982', '9090', '9098', '9106', '9014', '9032', '9040', '9038', '9046', '9054', - '9062', '9080', '9078', '9086', '9094', '9102', '9210', '9218', '9126', '9134', - '9142', '9150', '9158', '9176', '9174', '9182', '9190', '9198', '9206', '9214', - '2807', '2815', '2823', '2831', '2839', '2847', '2865', '2863', '2871', '2879', - '2987', '2895', '2903', '2921', '2919', '2927', '2935', '2943', '2961', '2959', - '2967', '2975', '2983', '3091', '3109', '3107', '3015', '3023', '3031', '3039', - '3047', '3065', '3063', '3071', '3079', '3087', '3095', '3103', '3121', '3219', - '3127', '3135', '3143', '3161', '3159', '3167', '3175', '3183', '3201', '3209', - '3207', '3215', '3323', '3231', '3239', '3247', '3265', '3263', '3271', '3279', - '3287', '3295', '4303', '4321', '6903', '6921', '6919', '6927', '6935', '6943', - '6961', '6959', '6967', '6975', '6983', '7091', '7109', '7107', '7015', '7023', - '7031', '7039', '7047', '7065', '7063', '7071', '7079', '7087', '7095', '7103', - '7121', '7219', '7127', '7135', '7143', '7161', '7159', '7167', '7175', '7183', - '7201', '7209', '7207', '7215', '7323', '7231', '7239', '7247', '7265', '7263', - '7271', '7279', '7287', '7295', '7303', '7321', '7319', '7327', '7435', '7343', - '7361', '7359', '7367', '7375', '7383', '7401', '7409', '7407', '1010', '1108', - '1016', '1024', '1032', '1050', '1048', '1056', '1064', '1072', '1090', '1098', - '1096', '2104', '2212', '2130', '2128', '2136', '2154', '2152', '2170', '2168', - '2176', '2184', '2192', '1210', '1208', '1216', '1324', '1242', '1250', '1248', - '1256', '1264', '1282', '1290', '1298', '1296', '1304', '1312', '1320', '1328', - '1436', '1354', '1352', '1360', '1368', '1376', '1384', '1392', '1410', '1408', - '1416', '1424', '1432', '1540', '1548', '1456', '1474', '1472', '1490', '1498', - '1496', '1504', '5096', '5104', '5212', '5130', '5128', '5136', '5154', '5152', - '5170', '5168', '5176', '5184', '5192', '5210', '5208', '5216', '5324', '5242', - '5250', '5248', '5256', '5264', '5282', '5290', '5298', '5296', '5304', '5312', - '5320', '5328', '5436', '5354', '5352', '5360', '5368', '5376', '5384', '5392', - '5410', '5408', '5416', '5424', '5432', '5540', '5548', '5456', '5474', '5472', - '5490', '5498', '5496', '6504', '6512', '6530', '6528', '6536', '6654', '6652', - '6560', '6568', '6576', '6584', '6592', '5610', '9192', '9210', '9208', '9216', - '9324', '9242', '9250', '9248', '9256', '9264', '9282', '9290', '9298', '9296', - '9304', '9312', '9320', '9328', '9436', '9354', '9352', '9360', '9368', '9376', - '9384', '9392', '9410', '9408', '9416', '9424', '9432', '9540', '9548', '9456', - '9474', '9472', '9490', '9498', '9496', '9504', '9512', '9530', '9528', '9536', - '9654', '9652', '9560', '9568', '9576', '9584', '9592', '9610', '9608', '9616', - '9624', '9642', '9640', '9648', '9656', '9764', '9672', '9690', '9698', '9696', - '0519', '0527', '0535', '0543', '0651', '0659', '0567', '0585', '0583', '0601', - '0609', '0607', '0615', '0623', '0631', '0639', '0647', '0665', '0763', '0671', - '0679', '0687', '0695', '0703', '0721', '0719', '0727', '0735', '0743', '0751', - '0759', '0767', '0875', '0783', '0801', '0809', '0807', '0815', '0823', '0831', - '0839', '0847', '0865', '0863', '0871', '0879', '0987', '0895', '0903', '0921', - '0919', '0927', '0935', '0943', '0961', '0959', '0967', '0975', '0983', '1091', - '1109', '1107', '1015', '1023', '4615', '4623', '4631', '4639', '4647', '4665', - '4763', '4671', '4679', '4687', '4695', '4703', '4721', '4719', '4727', '4735', - '4743', '4751', '4759', '4767', '4875', '4783', '4801', '4809', '4807', '4815', - '4823', '4831', '4839', '4847', '4865', '4863', '4871', '4879', '4987', '4895', - '4903', '4921', '4919', '4927', '4935', '4943', '4961', '4959', '4967', '4975', - '4983', '5091', '5109', '5107', '5015', '5023', '5031', '5039', '5047', '5065', - '5063', '5071', '5079', '5087', '5095', '5103', '5121', '5219', '8721', '8719', - '8727', '8735', '8743', '8751', '8759', '8767', '8875', '8783', '8801', '8809', - '9807', '9815', '9823', '9831', '9839', '9847', '9865', '9863', '9871', '9879', - '9987', '9895', '8903', '8921', '8919', '8927', '8935', '8943', '8961', '8959', - '8967', '8975', '8983', '9091', '9109', '9107', '9015', '9023', '9031', '9039', - '9047', '9065', '9063', '9071', '9079', '9087', '9095', '9103', '9121', '9219', - '9127', '9135', '9143', '9161', '9159', '9167', '9175', '9183', '9201', '9209', - '9207', '9215', '2808', '2816', '2824', '2832', '2850', '2848', '2856', '2864', - '2872', '2980', '2898', '2896', '2904', '2912', '2930', '2928', '2936', '2954', - '2952', '2970', '2968', '2976', '2984', '3092', '3010', '3108', '3016', '3024', - '3032', '3050', '3048', '3056', '3064', '3072', '3090', '3098', '3096', '3104', - '3212', '3130', '3128', '3136', '3154', '3152', '3170', '3168', '3176', '3184', - '3192', '3210', '3208', '3216', '3324', '3242', '3250', '3248', '3256', '3264', - '3282', '3290', '3298', '3296', '4304', '4312', '6904', '6912', '6930', '6928', - '6936', '6954', '6952', '6970', '6968', '6976', '6984', '7092', '7010', '7108', - '7016', '7024', '7032', '7050', '7048', '7056', '7064', '7072', '7090', '7098', - '7096', '7104', '7212', '7130', '7128', '7136', '7154', '7152', '7170', '7168', - '7176', '7184', '7192', '7210', '7208', '7216', '7324', '7242', '7250', '7248', - '7256', '7264', '7282', '7290', '7298', '7296', '7304', '7312', '7320', '7328', - '7436', '7354', '7352', '7360', '7368', '7376', '7384', '7392', '7410', '7408', - '1101', '1109', '1017', '1025', '1043', '1041', '1049', '1057', '1065', '1073', - '1081', '1089', '1097', '2105', '2213', '2121', '2129', '2137', '2145', '2153', - '2161', '2169', '2187', '2185', '2193', '1201', '1209', '1217', '1325', '1243', - '1241', '1249', '1257', '1265', '1273', '1281', '1289', '1297', '1305', '1313', - '1321', '1329', '1437', '1345', '1353', '1361', '1369', '1387', '1385', '1393', - '1401', '1409', '1417', '1425', '1443', '1541', '1549', '1457', '1465', '1473', - '1481', '1489', '1497', '1505', '5097', '5105', '5213', '5121', '5129', '5137', - '5145', '5153', '5161', '5169', '5187', '5185', '5193', '5201', '5209', '5217', - '5325', '5243', '5241', '5249', '5257', '5265', '5273', '5281', '5289', '5297', - '5305', '5313', '5321', '5329', '5437', '5345', '5353', '5361', '5369', '5387', - '5385', '5393', '5401', '5409', '5417', '5425', '5443', '5541', '5549', '5457', - '5465', '5473', '5481', '5489', '5497', '6505', '6513', '6521', '6529', '6537', - '6545', '6653', '6561', '6569', '6587', '6585', '6593', '5601', '9193', '9201', - '9209', '9217', '9325', '9243', '9241', '9249', '9257', '9265', '9273', '9281', - '9289', '9297', '9305', '9313', '9321', '9329', '9437', '9345', '9353', '9361', - '9369', '9387', '9385', '9393', '9401', '9409', '9417', '9425', '9443', '9541', - '9549', '9457', '9465', '9473', '9481', '9489', '9497', '9505', '9513', '9521', - '9529', '9537', '9545', '9653', '9561', '9569', '9587', '9585', '9593', '9601', - '9609', '9617', '9625', '9643', '9641', '9649', '9657', '9765', '9673', '9681', - '9689', '9697', '1024', '1032', '1050', '1048', '1056', '1064', '1072', '1090', - '1098', '1096', '2104', '2212', '2130', '2128', '2136', '2154', '2152', '2170', - '2168', '2176', '2184', '2192', '1210', '1208', '1216', '1324', '1242', '1250', - '1248', '1256', '1264', '1282', '1290', '1298', '1296', '1304', '1312', '1320', - '1328', '1436', '1354', '1352', '1360', '1368', '1376', '1384', '1392', '1410', - '1408', '1416', '1424', '1432', '1540', '1548', '1456', '1474', '1472', '1490', - '1498', '1496', '1504', '1512', '1530', '1528', '5130', '5128', '5136', '5154', - '5152', '5170', '5168', '5176', '5184', '5192', '5210', '5208', '5216', '5324', - '5242', '5250', '5248', '5256', '5264', '5282', '5290', '5298', '5296', '5304', - '5312', '5320', '5328', '5436', '5354', '5352', '5360', '5368', '5376', '5384', - '5392', '5410', '5408', '5416', '5424', '5432', '5540', '5548', '5456', '5474', - '5472', '5490', '5498', '5496', '6504', '6512', '6530', '6528', '6536', '6654', - '6652', '6560', '6568', '6576', '6584', '6592', '5610', '5608', '5616', '5624', - '9216', '9324', '9242', '9250', '9258', '9256', '9264', '9282', '9290', '9298', - '9296', '9304', '9312', '9320', '9328', '9436', '9354', '9352', '9360', '9368', - '9376', '9384', '9392', '9410', '9408', '9416', '9424', '9432', '9540', '9548', - '9456', '9474', '9472', '9490', '9498', '9496', '9504', '9512', '9530', '9528', - '9536', '9654', '9652', '9560', '9568', '9576', '9584', '9592', '9610', '9608', - '9616', '9624', '9642', '9640', '9648', '9656', '9764', '9672', '9690', '9698', - '9696', '9704', '9712', '9720', '4313', '4321', '4329', '4437', '4345', '4353', - '4361', '4369', '4387', '4385', '4393', '3401', '3409', '3417', '3425', '3443', - '3541', '3549', '3457', '3465', '3473', '3481', '3489', '3497', '3505', '3513', - '3521', '3529', '3537', '3545', '3653', '3561', '3569', '3587', '3585', '3593', - '3601', '3609', '3617', '3625', '3643', '3641', '3649', '3657', '3765', '3673', - '3681', '3689', '3697', '3705', '3713', '3721', '3729', '3737', '3745', '3753', - '3761', '3769', '3787', '3785', '3793', '3801', '3809', '3817', '7409', '7417', - '7425', '7443', '7541', '7549', '7457', '7465', '7473', '7481', '7489', '7497', - '7505', '7513', '7521', '7529', '7537', '7545', '7653', '7561', '7569', '7587', - '7585', '7593', '7601', '7609', '7617', '7625', '7643', '7641', '7649', '7657', - '7765', '7673', '7681', '7689', '7697', '8705', '8713', '8721', '8729', '8737', - '8745', '8753', '8761', '8769', '8787', '8785', '8793', '7801', '7809', '7817', - '7825', '7843', '7841', '7849', '7857', '7865', '7873', '7981', '7989', '7897', - '7905', '7913', '1506', '1514', '1532', '1530', '1538', '1546', '1654', '1562', - '1570', '1578', '1586', '1594', '1602', '1620', '1618', '1626', '1634', '1642', - '1650', '1658', '1676', '1674', '1682', '1710', '1698', '1706', '1714', '1732', - '1730', '1738', '1746', '1754', '1762', '1870', '1878', '1786', '1794', '1802', - '1820', '1818', '1826', '1834', '1842', '1860', '1858', '1876', '1874', '1982', - '1890', '1908', '1906', '1914', '1932', '1930', '1938', '1946', '1954', '1962', - '1970', '1978', '1986', '2094', '2102', '2020', '5602', '5620', '5618', '5626', - '5634', '5642', '5650', '5658', '5676', '5674', '5682', '5710', '5698', '5706', - '5714', '5732', '5730', '5738', '5746', '5754', '5762', '5870', '5878', '5786', - '5794', '5802', '5820', '5818', '5826', '5834', '5842', '5860', '5858', '5876', - '5874', '5982', '5890', '5908', '5906', '5914', '5932', '5930', '5938', '5946', - '5954', '5962', '5970', '5978', '5986', '6094', '6102', '6020', '6018', '6026', - '6034', '6042', '6060', '6058', '6076', '6074', '6082', '6110', '6098', '6106', - '9698', '9706', '9714', '9732', '9730', '9738', '9746', '9754', '9762', '9870', - '9878', '9786', '9794', '9802', '9820', '9818', '9826', '9834', '9842', '9860', - '9858', '9876', '9874', '9982', '9890', '9908', '9906', '9914', '9922', '9930', - '9938', '9946', '9954', '9962', '9970', '9978', '9986', '9994', '1103', '1021', - '1019', '1027', '1035', '1043', '1051', '1059', '1067', '1075', '1083', '1091', - '1109', '0107', '0215', '0123', '0141', '0139', '0147', '0165', '0163', '0181', - '0179', '0187', '0195', '0203', '1025', '1043', '1041', '1049', '1057', '1065', - '1073', '1081', '1089', '1097', '2105', '2213', '2121', '2129', '2137', '2145', - '2153', '2161', '2169', '2187', '2185', '2193', '1201', '1209', '1217', '1325', - '1243', '1241', '1249', '1257', '1265', '1273', '1281', '1289', '1297', '1305', - '1313', '1321', '1329', '1437', '1345', '1353', '1361', '1369', '1387', '1385', - '1393', '1401', '1409', '1417', '1425', '1443', '1541', '1549', '1457', '1465', - '1473', '1481', '1489', '1497', '1505', '1513', '1521', '1529', '5121', '5129', - '5137', '5145', '5153', '5161', '5169', '5187', '5185', '5193', '5201', '5209', - '5217', '5325', '5243', '5241', '5249', '5257', '5265', '5273', '5281', '5289', - '5297', '5305', '5313', '5321', '5329', '5437', '5345', '5353', '5361', '5369', - '5387', '5385', '5393', '5401', '5409', '5417', '5425', '5443', '5541', '5949', - '5457', '5465', '5473', '5481', '5489', '5497', '6505', '6513', '6521', '6529', - '6537', '6545', '6653', '6561', '6569', '6587', '6585', '6593', '5601', '5609', - '5617', '5625', '9217', '9325', '9243', '9241', '9249', '9257', '9265', '9273', - '9281', '9289', '9297', '9305', '9313', '9321', '9329', '9437', '9345', '9353', - '9361', '9369', '9387', '9385', '9393', '9401', '9409', '9417', '9425', '9433', - '9541', '9549', '9457', '9465', '9473', '9481', '9489', '9497', '9505', '9513', - '9521', '9529', '9537', '9545', '9653', '9561', '9569', '9587', '9585', '9593', - '9601', '9609', '9617', '9625', '9643', '9641', '9649', '9657', '9765', '9673', - '9681', '9689', '9697', '9705', '9713', '9721', '4314', '4432', '4430', '4438', - '4346', '4354', '4372', '4370', '4378', '4386', '4394', '3402', '3420', '3418', - '3426', '3434', '3542', '3450', '3458', '3476', '3484', '3482', '3510', '3498', - '3506', '3514', '3532', '3530', '3538', '3546', '3654', '3562', '3570', '3578', - '3586', '3594', '3602', '3620', '3618', '3626', '3634', '3642', '3650', '3658', - '3676', '3674', '3682', '3710', '3698', '3706', '3714', '3732', '3730', '3738', - '3746', '3754', '3762', '3870', '3878', '3786', '3794', '3802', '3820', '3818', - '7420', '7418', '7426', '7434', '7542', '7450', '7458', '7476', '7484', '7482', - '7510', '7498', '7506', '7514', '7532', '7530', '7538', '7546', '7654', '7562', - '7570', '7578', '7586', '7594', '7602', '7620', '7618', '7626', '7634', '7642', - '7650', '7658', '7676', '7674', '7682', '7710', '7698', '8706', '8714', '8732', - '8730', '8738', '8746', '8754', '8762', '8870', '8878', '8786', '8794', '7802', - '7820', '7818', '7826', '7834', '7842', '7860', '7858', '7876', '7874', '7982', - '7890', '7908', '7906', '7914', '1507', '1515', '1523', '1541', '1539', '1547', - '1565', '1563', '1571', '1579', '1587', '1595', '1603', '1621', '1619', '1627', - '1635', '1643', '1651', '1659', '1767', '1675', '1683', '1691', '1709', '1707', - '1715', '1723', '1731', '1739', '1747', '1765', '1763', '1871', '1879', '1787', - '1795', '1803', '1821', '1819', '1827', '1835', '1843', '1851', '1859', '1867', - '1875', '1983', '1891', '1909', '1907', '1915', '1923', '1941', '1939', '1947', - '1965', '1963', '1981', '1979', '1987', '2095', '2103', '2021', '5603', '5621', - '5619', '5627', '5635', '5643', '5651', '5659', '5767', '5675', '5683', '5691', - '5709', '5707', '5715', '5723', '5731', '5739', '5747', '5765', '5763', '5871', - '5879', '5787', '5795', '5803', '5821', '5819', '5827', '5835', '5843', '5851', - '5859', '5867', '5875', '5983', '5891', '5909', '5907', '5915', '5923', '5941', - '5939', '5947', '5965', '5963', '5981', '5979', '5987', '6095', '6103', '6021', - '6019', '6027', '6035', '6043', '6051', '6059', '6067', '6075', '6083', '6091', - '6109', '6107', '9709', '9707', '9715', '9723', '9731', '9739', '9747', '9765', - '9763', '9871', '9879', '9787', '9795', '9803', '9821', '9819', '9827', '9835', - '9843', '9851', '9859', '9867', '9875', '9983', '9891', '9909', '9907', '9915', - '9923', '9941', '9939', '9947', '9965', '9963', '9971', '9979', '9987', '9995', - '1104', '1012', '1030', '1028', '1036', '1054', '1052', '1070', '1068', '1076', - '1084', '1092', '0210', '0108', '0216', '0124', '0132', '0150', '0148', '0156', - '0164', '0172', '0190', '0198', '0196', '0204', '1026', '1034', '1042', '1060', - '1058', '1076', '1074', '1082', '1110', '1098', '2106', '2214', '2132', '2130', - '2138', '2146', '2154', '2162', '2170', '2178', '2186', '2194', '1202', '1320', - '1218', '1326', '1234', '1242', '1260', '1258', '1276', '1274', '1282', '1310', - '1298', '1306', '1314', '1432', '1430', '1438', '1346', '1354', '1372', '1370', - '1378', '1386', '1394', '1402', '1420', '1418', '1426', '1434', '1542', '1450', - '1458', '1476', '1484', '1482', '1510', '1498', '1506', '1514', '1532', '1530', - '5132', '5130', '5138', '5146', '5154', '5162', '5170', '5178', '5186', '5194', - '5202', '5320', '5218', '5326', '5234', '5242', '5260', '5258', '5276', '5274', - '5282', '5310', '5298', '5306', '5314', '5432', '5430', '5438', '5346', '5354', - '5372', '5370', '5378', '5386', '5394', '5402', '5420', '5418', '5426', '5434', - '5542', '5450', '5458', '5476', '5484', '5482', '5510', '5498', '6506', '6514', - '6532', '6530', '6538', '6546', '6654', '6562', '6570', '6578', '6586', '6594', - '5602', '5620', '5618', '5626', '9218', '9326', '9234', '9242', '9260', '9258', - '9276', '9274', '9282', '9310', '9298', '9306', '9314', '9432', '9430', '9438', - '9346', '9354', '9372', '9370', '9378', '9386', '9394', '9402', '9420', '9418', - '9426', '9434', '9542', '9450', '9458', '9476', '9484', '9482', '9510', '9498', - '9506', '9514', '9532', '9530', '9538', '9546', '9654', '9562', '9570', '9578', - '9586', '9594', '9602', '9620', '9618', '9626', '9634', '9642', '9650', '9658', - '9676', '9674', '9682', '9710', '9698', '9706', '9714', '9732', '4315', '4323', - '4431', '4439', '4347', '4365', '4363', '4371', '4379', '4387', '4395', '3403', - '3421', '3419', '3427', '3435', '3543', '3451', '3459', '3467', '3475', '3483', - '3491', '3509', '3507', '3515', '3523', '3541', '3539', '3547', '3565', '3563', - '3571', '3579', '3587', '3595', '3603', '3621', '3619', '3627', '3635', '3643', - '3651', '3659', '3767', '3675', '3683', '3691', '3709', '3707', '3715', '3723', - '3731', '3739', '3747', '3765', '3763', '3871', '3879', '3787', '3795', '3803', - '3821', '3819', '7421', '7419', '7427', '7435', '7543', '7451', '7459', '7467', - '7475', '7483', '7491', '7509', '7507', '7515', '7523', '7541', '7539', '7547', - '7565', '7563', '7571', '7579', '7587', '7595', '7603', '7621', '7619', '7627', - '7635', '7643', '7651', '7659', '7767', '7675', '7683', '7691', '7709', '8707', - '8715', '8723', '8731', '8739', '8747', '8765', '8763', '8871', '8879', '8787', - '8795', '7803', '7821', '7819', '7827', '7835', '7843', '7851', '7859', '7867', - '7875', '7983', '7891', '7909', '7907', '7915', '1508', '1516', '1524', '1532', - '1540', '1548', '1656', '1574', '1572', '1590', '1598', '1596', '1604', '1612', - '1620', '1628', '1636', '1654', '1652', '1760', '1768', '1686', '1684', '1692', - '1710', '1708', '1716', '1724', '1732', '1740', '1748', '1756', '1764', '1872', - '1790', '1798', '1796', '1804', '1812', '1830', '1828', '1836', '1854', '1852', - '1870', '1868', '1876', '1984', '1892', '1910', '1908', '1916', '1924', '1932', - '1950', '1948', '1956', '1964', '1972', '1980', '2098', '2096', '2104', '2012', - '5604', '5612', '5620', '5628', '5636', '5654', '5652', '5760', '5768', '5686', - '5684', '5692', '5710', '5708', '5716', '5724', '5732', '5740', '5748', '5756', - '5764', '5872', '5790', '5798', '5796', '5804', '5812', '5830', '5828', '5836', - '5854', '5852', '5870', '5868', '5876', '5984', '5892', '5910', '5908', '5916', - '5924', '5932', '5950', '5948', '5956', '5964', '5972', '5980', '6098', '6096', - '6104', '6012', '6030', '6028', '6036', '6054', '6052', '6070', '6068', '6076', - '6084', '6092', '6210', '6108', '9710', '9708', '9716', '9724', '9732', '9740', - '9748', '9756', '9764', '9872', '9790', '9798', '9796', '9804', '9812', '9830', - '9828', '9836', '9854', '9852', '9870', '9868', '9876', '9984', '9892', '9910', - '9908', '9916', '9924', '9932', '9950', '9948', '9956', '9964', '9972', '9980', - '9998', '9996', '1105', '1013', '1021', '1029', '1037', '1045', '1053', '1061', - '1069', '1087', '1085', '1093', '0101', '0109', '0217', '0125', '0143', '0141', - '0149', '0157', '0165', '0173', '0181', '0189', '0197', '0205', '1027', '1035', - '1043', '1051', '1059', '1067', '1075', '1083', '1091', '1109', '2107', '2115', - '2123', '2141', '2139', '2147', '2165', '2163', '2181', '2179', '2187', '2195', - '1203', '1221', '1219', '1327', '1235', '1243', '1251', '1259', '1267', '1275', - '1283', '1291', '1309', '1307', '1315', '1323', '1431', '1439', '1347', '1365', - '1363', '1371', '1379', '1387', '1395', '1403', '1421', '1419', '1427', '1435', - '1543', '1451', '1459', '1467', '1475', '1483', '1491', '1509', '1507', '1515', - '1523', '1541', '5123', '5141', '5139', '5147', '5165', '5163', '5181', '5179', - '5187', '5195', '5203', '5221', '5219', '5327', '5235', '5243', '5251', '5259', - '5267', '5275', '5283', '5291', '5309', '5307', '5315', '5323', '5431', '5439', - '5347', '5365', '5363', '5371', '5379', '5387', '5395', '5403', '5421', '5419', - '5427', '5435', '5543', '5451', '5459', '5467', '5475', '5483', '5491', '5509', - '6507', '6515', '6523', '6541', '6539', '6547', '6565', '6563', '6571', '6579', - '6587', '6595', '5603', '5621', '5619', '5627', '9219', '9327', '9235', '9243', - '9251', '9259', '9267', '9275', '9283', '9291', '9309', '9307', '9315', '9323', - '9431', '9439', '9347', '9365', '9363', '9371', '9379', '9387', '9395', '9403', - '9421', '9419', '9427', '9435', '9543', '9451', '9459', '9467', '9475', '9483', - '9491', '9509', '9507', '9515', '9523', '9541', '9539', '9547', '9565', '9563', - '9571', '9579', '9587', '9595', '9603', '9621', '9619', '9627', '9635', '9643', - '9651', '9659', '9767', '9675', '9683', '9691', '9709', '9707', '9715', '9723', - '4316', '4324', '4432', '4350', '4348', '4356', '4364', '4372', '4390', '4398', - '4396', '3404', '3412', '3430', '3428', '3436', '3454', '3452', '3460', '3468', - '3476', '3484', '3492', '3510', '3508', '3516', '3524', '3532', '3540', '3548', - '3656', '3574', '3572', '3590', '3598', '3596', '3604', '3612', '3620', '3628', - '3636', '3654', '3652', '3760', '3768', '3686', '3684', '3692', '3710', '3708', - '3716', '3724', '3732', '3740', '3748', '3756', '3764', '3872', '3790', '3798', - '3796', '3804', '3812', '3830', '7412', '7430', '7428', '7436', '7454', '7452', - '7460', '7468', '7476', '7484', '7492', '7510', '7508', '7516', '7524', '7532' - ); public /// - /// Get description for this calculator + /// Get description. /// function GetDescription: string; override; /// - /// Radio Code input validator + /// Validate. /// - /// - /// The serial number or any other needed input to calculate the - /// radio code. - /// - /// /// - /// A error message (Optional) that descibes why the input is invalid. - /// function Validate(const Input: string; var ErrorMessage: string): Boolean; override; /// - /// Radio Code Calculator + /// Calculate. /// - /// - /// The serial number or any other needed input to calculate the - /// radio code. - /// - /// - /// The calculated radio code. - /// - /// - /// A error message (Optional) that descibes why the input is invalid. - /// function Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; override; end; implementation -uses System.StrUtils; +uses + System.Classes, System.JSON, + OBD.Catalog.Path; + +const + CatalogFileName = 'radiocode-becker4.json'; + TableSize = 10000; + +var + GDatabase: array[0..TableSize - 1] of string; + GLoaded: Boolean = False; + +//------------------------------------------------------------------------------ +// LOAD CATALOG +//------------------------------------------------------------------------------ +procedure LoadCatalog; +var + Path, Raw: string; + Stream: TStringStream; + Doc: TJSONValue; + Arr: TJSONArray; + I: Integer; +begin + Path := ResolveCatalogPath(CatalogFileName); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('codes'); + if (Arr = nil) or (Arr.Count <> TableSize) then Exit; + for I := 0 to TableSize - 1 do + GDatabase[I] := Arr.Items[I].Value; + GLoaded := True; + finally + // Free the document + Doc.Free; + end; +end; //------------------------------------------------------------------------------ // GET DESCRIPTION @@ -1087,17 +119,10 @@ function TOBDRadioCodeBecker4.Validate(const Input: string; var ErrorMessage: st Result := True; // Clear the error message ErrorMessage := ''; - - // Sanitize input (remove whitespace, convert to uppercase) + // Sanitize the input Sanitized := SanitizeInput(Input); - - // Validate length using helper method - if not ValidateLength(Sanitized, 4, ErrorMessage) then - Exit(False); - - // Validate that all characters are digits using helper method - if not ValidateDigits(Sanitized, ErrorMessage) then - Exit(False); + if not ValidateLength(Sanitized, 4, ErrorMessage) then Exit(False); + if not ValidateDigits(Sanitized, ErrorMessage) then Exit(False); end; //------------------------------------------------------------------------------ @@ -1114,18 +139,20 @@ function TOBDRadioCodeBecker4.Calculate(const Input: string; var Output: string; Output := ''; // Clear the error message ErrorMessage := ''; - - // Sanitize input + // Sanitize the input Sanitized := SanitizeInput(Input); - - // Check if the input is valid if not Self.Validate(Sanitized, ErrorMessage) then Exit(False); - - // Convert the serial to a index + if not GLoaded then + begin + ErrorMessage := 'Becker4 code catalog not loaded; expected ' + + 'catalogs/' + CatalogFileName; + Exit(False); + end; I := StrToInt(Sanitized); - - // Format the code for the output - Output := Database[I]; + Output := GDatabase[I]; end; +initialization + LoadCatalog; + end. diff --git a/src/RadioCode/OBD.RadioCode.Becker5.pas b/src/RadioCode/OBD.RadioCode.Becker5.pas index b506f85e..3086ff44 100644 --- a/src/RadioCode/OBD.RadioCode.Becker5.pas +++ b/src/RadioCode/OBD.RadioCode.Becker5.pas @@ -1,19 +1,20 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // UNIT : OBD.RadioCode.Becker5.pas // CONTENTS : Becker Radio Code Calculator (5 Digits) // VERSION : 1.0 // TARGET : Embarcadero Delphi 11 or higher // AUTHOR : Ernst Reidinga (ERDesigns) // STATUS : Open source under Apache 2.0 library -// COMPATIBILITY : Windows 7, 8/8.1, 10, 11 -// RELEASE DATE : 14/04/2024 +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 13/04/2024 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) //------------------------------------------------------------------------------ unit OBD.RadioCode.Becker5; interface uses - WinApi.Windows, System.SysUtils, + System.SysUtils, OBD.RadioCode; @@ -22,1051 +23,82 @@ interface //------------------------------------------------------------------------------ type /// - /// OBD Becker RadioCode Calculator (5 Digits) + /// OBD Becker RadioCode Calculator (5 Digits). The serial-to-code + /// table (10,000 entries) is loaded from catalogs/radiocode-becker5.json + /// at unit init so a corrected entry can be shipped without recompiling. /// TOBDRadioCodeBecker5 = class(TOBDRadioCode) - private const - /// - /// Database containing all Becker (5 Digits) codes from serial code - /// 0001 until 9999 - /// - Database: array[0..9999] of AnsiString = ( - '12111', '21118', '21116', '31124', '41132', '51141', '61148', '71156', '81164', '91172', - '11181', '21188', '31196', '41114', '51112', '61121', '71128', '81136', '91144', '11152', - '21161', '31168', '41176', '51184', '61192', '71211', '81218', '91216', '11224', '21232', - '31241', '41248', '51256', '61264', '71272', '81281', '91288', '11296', '21314', '31312', - '41321', '51328', '61336', '71344', '81352', '91361', '11368', '21376', '31384', '41392', - '51411', '61418', '71416', '81424', '91432', '11441', '21448', '31456', '41464', '51472', - '61481', '71488', '81496', '94611', '14196', '24114', '34112', '44121', '54128', '64136', - '74144', '84152', '94161', '14168', '24176', '34184', '44192', '54211', '64218', '74216', - '84224', '94232', '14241', '24248', '34256', '44264', '54272', '64281', '74288', '84296', - '94314', '14312', '24321', '34328', '44336', '54344', '64352', '74361', '84368', '94376', - '14384', '24362', '34411', '44418', '54416', '64424', '74432', '84441', '94448', '14456', - '24464', '34472', '44481', '54488', '64496', '74514', '84512', '94521', '14528', '24536', - '34544', '44552', '54561', '64568', '74576', '84584', '94592', '18696', '28192', '38211', - '48218', '58216', '68224', '78232', '88241', '98248', '18256', '28264', '38272', '48281', - '58288', '68296', '78314', '88312', '98321', '18328', '28336', '38344', '48352', '58361', - '68368', '78376', '88384', '98392', '18411', '28418', '38416', '48424', '58432', '68441', - '78448', '88456', '98464', '18472', '28481', '38488', '48496', '58514', '68512', '78521', - '88528', '98536', '18544', '28552', '38561', '48568', '58576', '68584', '78592', '88611', - '98618', '18616', '28624', '38632', '48641', '58648', '68656', '78664', '88672', '98681', - '18688', '22792', '32288', '42296', '52314', '62312', '72321', '82328', '92336', '12344', - '22352', '32361', '42368', '52376', '62384', '72392', '82411', '92418', '12416', '22424', - '32432', '42441', '52448', '62456', '72464', '82472', '92481', '12488', '22496', '32514', - '42512', '52521', '62528', '72536', '82544', '92552', '12561', '22568', '32576', '42584', - '52592', '62611', '72618', '82616', '92624', '12632', '22641', '32648', '42656', '52664', - '62672', '72681', '82688', '92696', '12714', '22712', '32721', '42728', '52736', '62744', - '72752', '82761', '92768', '12776', '22784', '36888', '46384', '56392', '66411', '76418', - '86416', '96424', '16432', '26441', '36448', '46456', '56464', '66472', '76481', '86488', - '96496', '16514', '26512', '36521', '46528', '56536', '66544', '76552', '86561', '96568', - '16576', '26584', '36592', '46611', '56618', '66616', '76624', '86632', '96641', '16648', - '26656', '36664', '46672', '56681', '66688', '76696', '86714', '96712', '16721', '26728', - '36736', '46744', '56752', '66761', '76768', '86776', '96784', '16792', '26811', '36818', - '46816', '56824', '66832', '76841', '86848', '96856', '16864', '26872', '36881', '41984', - '51481', '61488', '71496', '81514', '91512', '11521', '21528', '31536', '41544', '51552', - '61561', '71568', '81576', '91584', '11592', '21611', '31618', '41616', '51624', '61632', - '71641', '81648', '91656', '11664', '21672', '31681', '41688', '51696', '61714', '71712', - '81721', '91728', '11736', '21744', '31752', '41761', '51768', '61776', '71784', '81792', - '91811', '11818', '21816', '31824', '41832', '51841', '61848', '71856', '81864', '91872', - '11881', '21888', '31896', '41914', '51912', '61921', '71928', '81936', '91944', '11952', - '21961', '31968', '41976', '55181', '64576', '74584', '84592', '94611', '14618', '24616', - '34624', '44632', '54641', '64648', '74656', '84664', '94672', '14681', '24688', '34696', - '44714', '54712', '64721', '74728', '84736', '94744', '14752', '24761', '34768', '44776', - '54784', '64792', '74811', '84818', '94816', '14824', '24832', '34841', '44848', '54856', - '64864', '74872', '84881', '94888', '14896', '24914', '34912', '44921', '54928', '64936', - '74944', '84952', '94961', '14968', '24976', '34984', '44992', '55111', '65118', '75116', - '85124', '95132', '15141', '25148', '35156', '45164', '55172', '69176', '78672', '88681', - '98688', '18696', '28714', '38712', '48721', '58728', '68736', '78744', '88752', '98761', - '18768', '28776', '38784', '48792', '58811', '68818', '78816', '88824', '98832', '18841', - '28848', '38856', '48864', '58872', '68881', '79888', '88896', '98914', '18912', '28921', - '38928', '48936', '58944', '68952', '78961', '88968', '98976', '18984', '28992', '39111', - '49118', '59116', '69124', '79132', '89141', '99148', '19156', '29164', '39172', '49181', - '59188', '69196', '79114', '89112', '99121', '19128', '29136', '39144', '49152', '59161', - '69168', '71514', '82111', '92119', '21117', '21125', '31133', '41141', '51149', '61157', - '71165', '81173', '91181', '11189', '21197', '31115', '41113', '51121', '61129', '71137', - '81145', '91153', '11161', '21169', '31177', '41185', '51193', '61211', '71219', '81217', - '91225', '11233', '21241', '31249', '41257', '51265', '61273', '71281', '81289', '91217', - '11315', '21313', '31321', '41329', '51337', '61345', '71353', '81361', '91369', '11377', - '21385', '31393', '41411', '51419', '61417', '71425', '81433', '91441', '11449', '21457', - '31465', '41473', '51481', '61489', '71497', '84611', '94197', '14115', '24113', '34121', - '44129', '54137', '64145', '74153', '84161', '94169', '14177', '24185', '34193', '44211', - '54219', '64217', '74225', '84233', '94241', '14249', '24257', '34265', '44273', '54281', - '64289', '74297', '84315', '94313', '14321', '24329', '34337', '44345', '54353', '64361', - '74369', '84377', '94385', '14393', '24411', '34419', '44417', '54425', '64433', '74441', - '84449', '94457', '14465', '24473', '34481', '44489', '54497', '64515', '74513', '84521', - '94529', '14537', '24545', '34553', '44561', '54569', '64577', '74585', '84593', '98697', - '18193', '28211', '38219', '48217', '58225', '68233', '78241', '88249', '98257', '18265', - '28273', '38281', '48289', '58297', '68315', '78313', '88321', '98329', '18337', '28345', - '38353', '48361', '58369', '68377', '78385', '88393', '98411', '18419', '28417', '38425', - '48433', '58441', '68449', '78457', '88465', '98473', '18481', '28489', '38497', '48515', - '58513', '68521', '78529', '88537', '98545', '18553', '28561', '38569', '48577', '58585', - '68593', '78611', '88619', '98617', '18625', '28633', '38641', '48649', '58657', '68665', - '78673', '88681', '98689', '12793', '22289', '32297', '42315', '52313', '62329', '72321', - '82337', '92345', '12353', '22369', '32361', '42377', '52385', '62393', '72411', '82419', - '92417', '12425', '22433', '32441', '42449', '52457', '62465', '72473', '82481', '92489', - '12497', '22515', '32513', '42521', '52529', '62537', '72545', '82553', '92561', '12569', - '22577', '32585', '42593', '52611', '62619', '72617', '82625', '92633', '12641', '22649', - '32657', '42665', '52673', '62681', '72689', '82697', '92715', '12713', '22721', '32729', - '42737', '52745', '62753', '72761', '82769', '92777', '12785', '26889', '36385', '46393', - '56411', '66419', '76417', '86425', '96433', '16441', '26449', '36457', '46465', '57473', - '66481', '76489', '86497', '96515', '16513', '26521', '36529', '46537', '56545', '66553', - '76561', '86569', '96577', '16585', '26593', '36611', '46619', '56617', '66625', '76633', - '86641', '96649', '16657', '26665', '36673', '46681', '56689', '66697', '76715', '86713', - '96721', '16721', '26737', '36745', '46753', '56761', '66769', '76777', '86785', '96793', - '16811', '26819', '36817', '46825', '56833', '66841', '76849', '86857', '96865', '16873', - '26881', '31985', '41481', '51489', '61497', '71515', '81513', '91521', '11529', '21537', - '31545', '41553', '51561', '61569', '71577', '81585', '91513', '11611', '21619', '31617', - '41625', '51633', '61641', '71649', '81657', '91665', '11673', '21681', '31689', '41697', - '51715', '61713', '71721', '81729', '91737', '11745', '21753', '31761', '41769', '51777', - '61785', '71793', '81811', '91819', '11817', '21825', '31833', '41841', '51849', '61857', - '71865', '81873', '91881', '11889', '21897', '31915', '41913', '51921', '61929', '71937', - '81945', '91953', '11961', '21969', '31977', '45181', '54577', '64585', '74593', '84611', - '94619', '14617', '24625', '34633', '44641', '54649', '64657', '74665', '84673', '94681', - '14689', '24697', '34715', '44713', '54721', '64729', '74737', '84745', '94753', '14761', - '24769', '34777', '44785', '54793', '64811', '74819', '84817', '94825', '14833', '24841', - '34849', '44857', '54865', '64873', '74881', '84889', '94897', '14915', '24913', '34921', - '44929', '54937', '64945', '74953', '84961', '94969', '14977', '24985', '34993', '45111', - '55119', '65117', '75125', '85133', '95141', '15149', '25157', '35165', '45173', '59177', - '68673', '78681', '88689', '98697', '18715', '28713', '38721', '48729', '58737', '68745', - '78753', '88761', '98769', '18777', '28785', '38793', '48811', '58819', '68817', '78825', - '88833', '98841', '18849', '28857', '38865', '48873', '58881', '68889', '78897', '88915', - '98913', '18921', '28929', '38937', '48945', '58953', '68961', '78969', '88977', '98985', - '18993', '29111', '39119', '49117', '59125', '69133', '79141', '89149', '99157', '19165', - '29173', '39181', '49189', '59197', '69115', '79113', '89121', '99129', '19137', '29145', - '39153', '49161', '59169', '61515', '71112', '82111', '91118', '11126', '21134', '31142', - '41151', '51158', '61166', '71174', '81182', '91191', '11198', '21116', '31114', '41122', - '51131', '61138', '71146', '81154', '91162', '11171', '21178', '31186', '41194', '51212', - '61211', '71218', '81226', '91234', '11242', '21251', '31258', '41266', '51274', '61282', - '71291', '81298', '91316', '11314', '21322', '31331', '41338', '51346', '61354', '71362', - '81371', '91378', '11386', '21394', '31412', '41411', '51418', '61426', '71434', '81442', - '91451', '11458', '21466', '31474', '41482', '51491', '61498', '74612', '84198', '94116', - '14114', '24122', '34131', '44138', '54146', '64154', '74162', '84171', '94178', '14186', - '24194', '34212', '44211', '54218', '64226', '74234', '84242', '94251', '14258', '24266', - '34274', '44282', '54291', '64298', '74316', '84314', '94322', '14331', '24338', '34346', - '44354', '54362', '64371', '74378', '84386', '94394', '14412', '24411', '34418', '44426', - '54434', '64442', '74451', '84458', '94466', '14474', '24482', '34491', '44498', '54516', - '64514', '74522', '84531', '94538', '14546', '24554', '34562', '44571', '54578', '64586', - '74594', '88698', '98194', '18212', '28211', '38218', '48226', '58234', '68242', '78251', - '88258', '98266', '18274', '28282', '38291', '48298', '58316', '68314', '78322', '88331', - '98338', '18346', '28354', '38362', '48371', '58378', '68386', '78394', '88412', '98411', - '18418', '28426', '38434', '48442', '58451', '68458', '78466', '88474', '98482', '18491', - '28498', '38516', '48514', '58522', '68531', '78538', '88546', '98554', '18562', '28571', - '38578', '48586', '58594', '68612', '78611', '88618', '98626', '18634', '28642', '38651', - '48658', '58666', '68674', '78682', '88691', '92794', '12291', '22298', '32316', '42314', - '52322', '62331', '72338', '82346', '92354', '12362', '22371', '32378', '42386', '52394', - '62412', '72411', '82418', '92426', '12434', '22442', '32451', '42458', '52466', '62474', - '72482', '82491', '92498', '12516', '22514', '32522', '42531', '52538', '62546', '72554', - '82562', '92571', '12578', '22586', '32594', '42612', '52611', '62618', '72626', '82634', - '92642', '12651', '22658', '32666', '42674', '52682', '62691', '72698', '82716', '92714', - '12722', '22731', '32738', '42746', '52754', '62762', '72771', '82778', '92786', '16891', - '26386', '36394', '46412', '56411', '66418', '76426', '86434', '96442', '16451', '26458', - '36466', '46474', '56482', '66491', '76418', '86516', '96514', '16522', '26531', '36538', - '46546', '56554', '66562', '76571', '86578', '96586', '16594', '26612', '36611', '46618', - '56626', '66634', '76642', '86651', '96658', '17666', '26674', '36682', '46691', '56698', - '66716', '76714', '86722', '96731', '16738', '26746', '36754', '46762', '56771', '66778', - '76786', '86794', '96812', '16811', '26818', '36826', '46834', '56842', '66851', '76858', - '86866', '96874', '16882', '21986', '31482', '41491', '51498', '61516', '71514', '81522', - '91531', '11538', '21546', '31554', '41562', '51571', '61578', '71586', '81594', '91612', - '11611', '21618', '31626', '41634', '51642', '61651', '71658', '81666', '91674', '11682', - '21691', '31698', '41716', '51714', '61722', '71731', '81738', '91746', '11754', '21762', - '31771', '41778', '51786', '61794', '71812', '81811', '91818', '11826', '21834', '31842', - '41851', '51858', '61866', '71874', '81882', '91891', '11898', '21916', '31914', '41922', - '51931', '61938', '71946', '81954', '91962', '11971', '21978', '35182', '44578', '54586', - '64594', '74612', '84611', '94618', '14626', '24634', '34642', '44651', '54658', '64666', - '74674', '84682', '94691', '14698', '24716', '34714', '44722', '54731', '64738', '74746', - '84754', '94762', '14771', '24778', '34786', '44794', '54812', '64811', '74818', '84826', - '94834', '14842', '24851', '34858', '44866', '54874', '64882', '74891', '84898', '94916', - '14914', '24922', '34931', '44938', '54946', '64954', '74962', '84971', '94978', '14986', - '24994', '35112', '45111', '55118', '65126', '75134', '85142', '95151', '15158', '25166', - '35174', '49178', '58674', '68682', '78691', '88698', '98716', '18714', '28722', '38731', - '48738', '58746', '68754', '78762', '88771', '98778', '18786', '28794', '38812', '48811', - '58818', '68826', '78834', '88842', '98851', '18858', '28866', '38874', '48882', '58891', - '68898', '78916', '88914', '98922', '18931', '28938', '38946', '48954', '58962', '68971', - '78978', '88986', '98994', '19112', '29111', '39118', '49126', '59134', '69142', '79151', - '89158', '99166', '19174', '29182', '39191', '49198', '59116', '69114', '79122', '89131', - '99138', '19146', '29154', '39162', '49171', '51178', '61113', '72111', '81119', '91127', - '11135', '21143', '31151', '41159', '51167', '61175', '71183', '81191', '91199', '21117', - '21115', '31123', '41131', '51139', '61147', '71155', '81163', '91171', '11179', '21187', - '31195', '41213', '51211', '61219', '71227', '81235', '91243', '11251', '21259', '31267', - '41275', '51283', '61291', '71299', '81317', '91315', '11323', '21331', '31339', '41347', - '51355', '61363', '71371', '81379', '91387', '11395', '21413', '31411', '41419', '51427', - '61435', '71443', '81451', '91459', '11467', '21475', '31483', '41491', '51499', '64613', - '74199', '84117', '94115', '14123', '24131', '34139', '44147', '54155', '64163', '74171', - '84179', '94187', '14195', '24213', '34211', '44219', '54227', '64235', '74243', '84251', - '94259', '14267', '24275', '34283', '44291', '54299', '64317', '74315', '84323', '94331', - '14339', '24347', '34355', '44363', '54371', '64379', '74387', '84395', '94413', '14411', - '24419', '34427', '44435', '54443', '64451', '74459', '84467', '94475', '14483', '24491', - '34499', '44517', '54515', '64523', '74531', '84539', '94547', '14555', '24563', '34571', - '44579', '54587', '64595', '78699', '88195', '98213', '18211', '28219', '38227', '48235', - '58243', '68251', '78259', '88267', '98275', '18283', '28291', '38299', '48317', '58315', - '68323', '78331', '88339', '98347', '18355', '28363', '38371', '48379', '58387', '68395', - '78413', '88411', '98419', '18427', '28435', '38443', '48451', '58459', '68467', '78475', - '88483', '98491', '18499', '28517', '38515', '48523', '58531', '68539', '78547', '88555', - '98563', '18571', '28579', '38587', '48595', '58613', '68611', '78619', '88627', '98635', - '18643', '28651', '38659', '48667', '58675', '68683', '78691', '82795', '92291', '12299', - '22317', '32315', '42323', '52331', '62339', '72347', '82355', '92363', '12371', '22379', - '32387', '42395', '52413', '62411', '72419', '82427', '92435', '12443', '22451', '32459', - '42467', '52475', '62483', '72491', '82499', '92517', '12515', '22523', '32531', '42539', - '52547', '62555', '72563', '82571', '92579', '12587', '22595', '32613', '42611', '52619', - '62627', '72635', '82643', '92651', '12659', '22667', '32675', '42683', '52691', '62699', - '72717', '82715', '92723', '12731', '22739', '32747', '42755', '52763', '62771', '72779', - '82787', '96891', '16387', '26395', '36413', '46411', '56419', '66427', '76435', '86443', - '96451', '16459', '26467', '36475', '46483', '56491', '66499', '76517', '86515', '96523', - '16531', '26539', '36547', '46555', '56563', '66571', '76579', '86587', '96595', '16613', - '26611', '36619', '46627', '56635', '66643', '76651', '86659', '96667', '16675', '26683', - '36691', '46699', '56717', '66715', '76723', '86731', '96739', '16747', '26755', '36763', - '46771', '56779', '66787', '76795', '86813', '96811', '16819', '26827', '36835', '46843', - '56851', '66859', '76867', '86875', '96883', '11987', '21483', '31491', '41499', '51517', - '61515', '71523', '81531', '91539', '11547', '21555', '31563', '41571', '51579', '61587', - '71595', '81613', '91611', '11619', '21627', '31635', '41643', '51651', '61659', '71667', - '81675', '91683', '11691', '21699', '31717', '41715', '51723', '61731', '71739', '81747', - '91755', '11763', '21771', '31779', '41787', '51795', '61813', '71811', '81819', '91827', - '11835', '21843', '31851', '41859', '51867', '61875', '71883', '81891', '91899', '11917', - '21915', '31923', '41931', '51939', '61947', '71955', '81963', '91971', '11979', '25183', - '34579', '44587', '54595', '64613', '74611', '84619', '94627', '14635', '24643', '34651', - '44659', '54667', '64675', '74683', '84691', '94699', '14717', '24715', '34723', '44731', - '54739', '64747', '74755', '84763', '94771', '14779', '24787', '34795', '44813', '54811', - '64819', '74827', '84835', '94843', '14851', '24859', '34867', '44875', '54883', '64891', - '74899', '84917', '94915', '14923', '24931', '34939', '44947', '54955', '64963', '74971', - '84979', '94987', '14995', '25113', '35111', '45119', '55127', '65135', '75143', '85151', - '95159', '15167', '25175', '39179', '48675', '58683', '68691', '78699', '88717', '98715', - '18723', '28731', '38739', '48747', '58755', '68763', '78771', '88779', '98787', '18795', - '28813', '38811', '48819', '58827', '68835', '78843', '88851', '98859', '18867', '28875', - '38883', '48891', '58899', '68917', '78915', '88923', '98931', '18939', '28947', '38955', - '48963', '58971', '68979', '78987', '88995', '99113', '19111', '29119', '39127', '49135', - '59143', '69151', '79159', '89167', '99175', '19183', '29191', '39199', '49117', '59115', - '69123', '79131', '89139', '99147', '19155', '29163', '39171', '41517', '51114', '61112', - '71121', '81128', '91136', '11144', '21152', '31161', '41168', '51176', '61184', '71192', - '82111', '91118', '21116', '21124', '31132', '41141', '51148', '61156', '71164', '81172', - '91181', '11188', '21196', '31214', '41212', '51221', '61228', '71236', '81244', '91252', - '11261', '21268', '31276', '41284', '51292', '61311', '71318', '81316', '91324', '11332', - '21341', '31348', '41356', '51364', '61372', '71381', '81388', '91396', '11414', '21412', - '31421', '41428', '51436', '61444', '71452', '81461', '91468', '11476', '21484', '31492', - '41511', '54614', '64111', '74118', '84116', '94124', '14132', '24141', '34148', '44156', - '54164', '64172', '74181', '84188', '94196', '14214', '24212', '34221', '44228', '54236', - '64244', '74252', '84261', '94268', '14276', '24284', '34292', '44311', '54318', '64316', - '74324', '84332', '94341', '14348', '24356', '34364', '44372', '54381', '64388', '74396', - '84414', '94412', '14421', '24428', '34436', '45444', '54452', '64461', '74468', '84476', - '94484', '14492', '24511', '34518', '44516', '54524', '64532', '74541', '84548', '94556', - '14564', '24572', '34581', '44588', '54596', '68711', '78196', '88214', '98212', '18221', - '28228', '38236', '48244', '58252', '68261', '78268', '88276', '98284', '18292', '28311', - '38318', '48316', '58324', '68332', '78341', '88348', '98356', '18364', '28372', '38381', - '48388', '58396', '68414', '78412', '88421', '98428', '18436', '28444', '38452', '48461', - '58468', '68476', '78484', '88492', '98511', '18518', '28516', '38524', '48532', '58541', - '68548', '78556', '88564', '98572', '18581', '28588', '38596', '48614', '58612', '68621', - '78628', '88636', '98644', '18652', '28661', '38668', '48676', '58684', '68692', '72796', - '82292', '92311', '12318', '22316', '32324', '42332', '52341', '62348', '72356', '82364', - '92372', '12381', '22388', '32396', '42414', '52412', '62421', '72428', '82436', '92444', - '12452', '22461', '32468', '42476', '52484', '62492', '72511', '82518', '92516', '12524', - '22532', '32541', '42548', '52556', '62564', '72572', '82581', '92588', '12596', '22614', - '32612', '42621', '52628', '62636', '72644', '82652', '92661', '12668', '22676', '32684', - '42612', '52711', '62718', '72716', '82724', '92732', '12741', '22748', '32756', '42764', - '52772', '62781', '72788', '86892', '96388', '16396', '26414', '36412', '46421', '56428', - '66436', '76444', '86452', '96461', '16468', '26476', '36484', '46492', '56511', '66518', - '76516', '86524', '96532', '16541', '26548', '36556', '46564', '56572', '66581', '76588', - '86596', '96614', '16612', '26621', '36628', '46636', '56644', '66652', '76661', '86668', - '96676', '16684', '26692', '36711', '46718', '56716', '66724', '76732', '86741', '96748', - '16756', '26764', '36772', '46781', '56788', '66796', '76814', '86812', '96821', '16828', - '26836', '36844', '46852', '56861', '66868', '76876', '86884', '91988', '11484', '21492', - '31511', '41518', '51516', '61524', '71532', '81541', '91548', '11556', '21564', '31572', - '41581', '51588', '61596', '71614', '81612', '91621', '11628', '21636', '31644', '41652', - '51661', '61668', '71676', '81684', '91692', '11711', '21718', '31716', '41724', '51732', - '61741', '71748', '81756', '91764', '11772', '21781', '31788', '41796', '51814', '61812', - '71821', '81828', '91836', '11844', '21852', '31861', '41868', '51876', '61884', '71892', - '81911', '91918', '11916', '21924', '31932', '41941', '51948', '61956', '71964', '81972', - '91981', '15184', '24581', '34588', '44596', '54614', '64612', '74621', '84628', '94636', - '14644', '24652', '34661', '44668', '54676', '64684', '74692', '84711', '94718', '14716', - '24724', '34732', '44741', '54748', '64756', '74764', '84772', '94781', '14788', '24796', - '34814', '44812', '54821', '64828', '74836', '84844', '94852', '14861', '24868', '34876', - '44884', '54892', '64911', '74918', '84916', '94924', '14932', '24941', '34948', '44956', - '54964', '64972', '74981', '84988', '94996', '15114', '25112', '35121', '45128', '55136', - '65144', '75152', '85161', '95168', '15176', '29181', '38676', '48684', '58692', '68711', - '78718', '88716', '98724', '18732', '28741', '38748', '48756', '58764', '68772', '78781', - '88788', '98716', '18814', '28812', '38821', '48828', '58836', '68844', '78852', '88861', - '98868', '18876', '28884', '38892', '48911', '58918', '68916', '78924', '88932', '98941', - '18948', '28956', '38964', '48972', '58981', '68988', '78996', '89114', '99112', '19121', - '29128', '39136', '49144', '59152', '69161', '79168', '89176', '99184', '19192', '29111', - '39118', '49116', '59124', '69132', '79141', '89148', '99156', '19164', '29172', '31181', - '41115', '51113', '61121', '71129', '81137', '91145', '11153', '21161', '31169', '41177', - '51185', '61193', '72111', '82119', '91117', '11125', '21133', '31141', '41149', '51157', - '61165', '71173', '81181', '91189', '11197', '21215', '31213', '41221', '51229', '61237', - '71245', '81253', '91261', '11269', '21277', '31285', '41293', '51311', '61319', '71317', - '81325', '91333', '11341', '21349', '31357', '41365', '51373', '61381', '71389', '81397', - '91415', '11413', '21421', '31429', '41437', '51445', '61453', '71461', '81469', '91477', - '11485', '21493', '31511', '44615', '54111', '64119', '74117', '84125', '94133', '14141', - '24149', '34157', '44165', '54173', '64181', '74189', '84197', '94215', '14213', '24221', - '34229', '44237', '54245', '64253', '74261', '84269', '94277', '14285', '24293', '34311', - '44319', '54317', '64325', '74333', '84341', '94349', '14357', '24365', '34373', '44381', - '54389', '64397', '74415', '84413', '94421', '14429', '24437', '34445', '44453', '54461', - '64469', '74477', '84485', '94493', '14511', '24519', '34517', '44525', '54533', '64541', - '74549', '84557', '94565', '14573', '24581', '34589', '44597', '58711', '68197', '78215', - '88213', '98221', '18229', '28237', '38245', '48253', '58261', '68269', '78277', '88285', - '98293', '18311', '28319', '38317', '48325', '58333', '68341', '78349', '88357', '98365', - '18373', '28381', '38389', '48397', '58415', '68413', '78421', '88429', '98437', '18445', - '28453', '38461', '48469', '58477', '68485', '78493', '88511', '98519', '18517', '28525', - '38533', '48541', '58549', '68557', '78565', '88573', '98581', '18589', '28597', '38615', - '48613', '58621', '68629', '78637', '88645', '98653', '18661', '28669', '38677', '48685', - '58693', '62797', '72293', '82311', '92319', '12317', '22325', '32333', '42341', '52349', - '62357', '72365', '82373', '92381', '12389', '22397', '32415', '42413', '52421', '62429', - '72437', '82445', '92453', '12461', '22469', '32477', '42485', '52493', '62511', '72519', - '82517', '92525', '12533', '22541', '32549', '42557', '52565', '62573', '72581', '82589', - '92597', '12615', '22613', '32621', '42629', '52637', '62645', '72653', '82661', '92669', - '12677', '22685', '32693', '42711', '52719', '62717', '72725', '82733', '92741', '12749', - '22757', '32765', '42773', '52781', '62789', '76893', '86389', '96397', '16415', '26413', - '36421', '46429', '56437', '66445', '76453', '86461', '96469', '16477', '26485', '36493', - '46511', '56519', '66517', '76525', '86533', '96541', '16549', '26557', '36565', '46573', - '56581', '66589', '76597', '86615', '96613', '16621', '26629', '36637', '46645', '56653', - '76661', '76669', '86677', '96685', '16693', '26711', '36719', '46717', '56725', '66733', - '76741', '86749', '96757', '16765', '26773', '36781', '46789', '56797', '66815', '76813', - '86821', '96829', '16837', '26845', '36853', '46861', '56869', '66877', '76885', '81989', - '91485', '11493', '21511', '31519', '41517', '51525', '61533', '71541', '81549', '91557', - '11565', '21573', '31581', '41589', '51597', '61615', '71613', '81621', '91629', '11637', - '21645', '31653', '41661', '51669', '61677', '71685', '81693', '91711', '11719', '21717', - '31725', '41733', '51741', '61749', '71757', '81765', '91773', '11781', '21789', '31797', - '41815', '51813', '61821', '71829', '81837', '91845', '11853', '21861', '31869', '41877', - '51885', '61893', '71911', '81919', '91917', '11925', '21933', '31941', '41949', '51957', - '61965', '71973', '81981', '95185', '14581', '24589', '34597', '44615', '54613', '64621', - '74629', '84637', '94645', '14653', '24661', '34669', '44677', '54685', '64693', '74711', - '84719', '94717', '14725', '24733', '34741', '44749', '54757', '64765', '74773', '84781', - '94789', '14797', '24815', '34813', '44821', '54829', '64837', '74845', '84853', '94861', - '14869', '24877', '34885', '44893', '54911', '64919', '74917', '84925', '94933', '14941', - '24949', '34957', '44965', '54973', '64981', '74989', '84997', '95115', '15113', '25121', - '35129', '45137', '55145', '65153', '75161', '85169', '95177', '15185', '28677', '38685', - '48693', '58711', '68719', '78717', '88725', '98733', '18741', '28749', '38757', '48765', - '58773', '68781', '78789', '88797', '98815', '18813', '28821', '38829', '48837', '58845', - '68853', '78861', '88869', '98877', '18885', '28893', '38911', '48919', '58917', '68925', - '78933', '88941', '98949', '18957', '28965', '38973', '48981', '58989', '68997', '79115', - '89113', '99121', '19129', '29137', '39145', '49153', '59161', '69169', '79177', '89185', - '99193', '19111', '29119', '39117', '49125', '59133', '69141', '79149', '89157', '99165', - '19173', '21519', '31116', '41114', '51122', '61131', '71138', '81146', '91154', '11162', - '21171', '31178', '41186', '51194', '61112', '72111', '81118', '91126', '11134', '21142', - '31151', '41158', '51166', '61174', '71182', '81191', '91198', '11216', '21214', '31222', - '41231', '51238', '61246', '71254', '81262', '91271', '11278', '21286', '31294', '41312', - '51311', '61318', '71326', '81334', '91342', '11351', '21358', '31366', '41374', '51382', - '61391', '71398', '81416', '91414', '11422', '21431', '31438', '41446', '51454', '61462', - '71471', '81478', '91486', '11494', '21512', '34616', '44112', '54111', '64118', '74126', - '84134', '94142', '14151', '24158', '34166', '44174', '54182', '64191', '74198', '84216', - '94214', '14222', '24231', '34238', '44246', '54254', '64262', '74271', '84278', '94286', - '14294', '24312', '34311', '44318', '54326', '64334', '74342', '84351', '94358', '14366', - '24374', '34382', '44391', '54398', '64416', '74414', '84422', '94431', '14438', '24446', - '34454', '44462', '54471', '64478', '74486', '84494', '94512', '14511', '24518', '34526', - '44534', '54542', '64551', '74558', '84566', '94574', '14582', '24591', '34598', '48712', - '58198', '68216', '78214', '88222', '98231', '18238', '28246', '38254', '48262', '58271', - '68278', '78286', '88294', '98312', '18311', '28318', '38326', '48334', '58342', '68351', - '78358', '88366', '98374', '18382', '28391', '38398', '48416', '58414', '68422', '78431', - '88438', '98446', '18454', '28462', '38471', '48478', '58486', '68494', '78512', '88511', - '98518', '18526', '28534', '38542', '48551', '58558', '68566', '78574', '88582', '98591', - '18598', '28616', '38614', '48622', '58631', '68638', '78646', '88654', '98662', '14671', - '28678', '38686', '48694', '52798', '62294', '72312', '82311', '92318', '12326', '22334', - '32342', '42351', '52358', '62366', '72374', '82382', '92391', '12398', '22416', '32414', - '42422', '52431', '62438', '72446', '82454', '92462', '12471', '22478', '32486', '42494', - '52512', '62511', '72518', '82526', '92534', '12542', '22551', '32558', '42566', '52574', - '62582', '72591', '82598', '92616', '12614', '22622', '32631', '42638', '52646', '62654', - '72662', '82671', '92678', '12686', '22694', '32712', '42711', '52718', '62726', '72734', - '82742', '92751', '12758', '22766', '32774', '42782', '52791', '66894', '76391', '86398', - '96416', '16414', '26422', '36431', '46438', '56446', '66454', '76462', '86471', '96478', - '16486', '26494', '36512', '46511', '56518', '66526', '76534', '86542', '96551', '16558', - '26566', '36574', '46582', '56591', '66598', '76616', '86614', '96622', '16631', '26638', - '36646', '46654', '56662', '66671', '76678', '86686', '96694', '16712', '26711', '36718', - '46726', '56734', '66742', '76751', '86758', '96766', '16774', '26782', '36791', '46798', - '56816', '66814', '76822', '86831', '96838', '16846', '26854', '36862', '46871', '56878', - '66886', '71991', '81486', '91494', '11512', '21511', '31518', '41526', '51534', '61542', - '71551', '81558', '91566', '11574', '21582', '31591', '41598', '51616', '61614', '71622', - '81631', '91638', '11646', '21654', '31662', '41671', '51678', '61686', '71694', '81712', - '91711', '11718', '21726', '31734', '41742', '51751', '61758', '71766', '81774', '91782', - '11791', '21798', '31816', '41814', '51822', '61831', '71838', '81846', '91854', '11862', - '21871', '31878', '41886', '51894', '61912', '72911', '81918', '91926', '11934', '21942', - '31951', '41958', '51966', '61974', '71982', '85186', '94582', '14591', '24598', '34616', - '44614', '54622', '64631', '74638', '84646', '94654', '14662', '24671', '34678', '44686', - '54694', '64712', '74711', '84718', '94726', '14734', '24742', '34751', '44758', '54766', - '64774', '74782', '84791', '94798', '14816', '24814', '34822', '44831', '54838', '64846', - '74854', '84862', '94871', '14878', '24886', '34894', '44912', '54911', '64918', '74926', - '84934', '94942', '14951', '24958', '34966', '44974', '54982', '64991', '74998', '85116', - '95114', '15122', '25131', '35138', '45146', '55154', '65162', '75171', '85178', '99182', - '18678', '28686', '38694', '48712', '58711', '68718', '78726', '88734', '98742', '18751', - '28758', '38766', '48774', '58782', '68791', '78798', '88816', '98814', '18822', '28831', - '38838', '48846', '58854', '68862', '78871', '88878', '98886', '18894', '28912', '38911', - '48918', '58926', '68934', '78942', '88951', '98958', '18966', '28974', '38982', '48991', - '58998', '69116', '79114', '89122', '99131', '19138', '29146', '39154', '49162', '59171', - '69178', '79186', '89194', '99112', '19111', '29118', '39126', '49134', '59142', '69151', - '79158', '89166', '99174', '11511', '21117', '31115', '41123', '51131', '61139', '71147', - '81155', '91163', '11171', '21179', '31187', '41195', '51113', '62111', '71119', '81127', - '91135', '11143', '21151', '31159', '41167', '51175', '61183', '71191', '81199', '91217', - '11215', '21223', '31231', '41239', '51247', '61255', '71263', '81271', '91279', '11287', - '21295', '31313', '41311', '51319', '61327', '71335', '81343', '91351', '11359', '21367', - '31375', '41383', '51391', '61399', '71417', '81415', '91423', '11431', '21439', '31447', - '41455', '51463', '61471', '71479', '81487', '91495', '11513', '24617', '34113', '44111', - '54119', '64127', '74135', '84143', '94151', '14159', '24167', '34175', '44183', '54191', - '64199', '74217', '84215', '94223', '14231', '24239', '34247', '44255', '54263', '64271', - '74279', '84287', '94295', '14313', '24311', '34319', '44327', '54335', '64343', '74351', - '84359', '94367', '14375', '24383', '34391', '44399', '54417', '64415', '74423', '84431', - '94439', '14447', '24455', '34463', '44471', '54479', '64487', '74495', '84513', '94511', - '14519', '24527', '34535', '44543', '54551', '64559', '74567', '84575', '94583', '14591', - '24599', '38713', '48199', '58217', '68215', '78223', '88231', '98239', '18247', '28255', - '38263', '48271', '58279', '68287', '78295', '88313', '98311', '18319', '28327', '38335', - '48343', '58351', '68359', '78367', '88375', '98383', '18391', '28399', '38417', '48415', - '58423', '68431', '78439', '88447', '98455', '18463', '28471', '38479', '48487', '58495', - '68513', '78511', '88519', '98527', '18535', '28543', '38551', '48559', '58567', '68575', - '78583', '88591', '98599', '18617', '28615', '38623', '48631', '58639', '68647', '78655', - '88663', '98671', '18679', '28687', '38695', '42799', '52295', '62313', '72311', '82319', - '92327', '12335', '22343', '32351', '42359', '52367', '62375', '72383', '82391', '92399', - '12417', '22415', '32423', '42431', '52439', '62447', '72455', '82463', '92471', '12179', - '22487', '32495', '42513', '52511', '62519', '72527', '82535', '92543', '12551', '22559', - '32567', '42575', '52583', '62591', '72599', '82617', '92615', '12623', '22631', '32639', - '42647', '52655', '62663', '72671', '82679', '92687', '12695', '22713', '32711', '42719', - '52727', '62735', '72743', '82751', '92759', '12767', '22775', '32783', '42791', '56895', - '66391', '76399', '86417', '96415', '16423', '26431', '36439', '46447', '56455', '66463', - '76471', '86479', '96487', '16495', '26513', '36511', '46519', '56527', '66535', '76543', - '86551', '96559', '16567', '26575', '36583', '46591', '56599', '66617', '76615', '86623', - '96631', '16639', '26647', '36655', '46663', '56671', '66679', '76687', '86695', '96713', - '16711', '26719', '36727', '46735', '56743', '66751', '76759', '86767', '96775', '16783', - '26791', '36799', '46817', '56815', '66823', '76831', '86839', '96847', '16855', '26863', - '36871', '46879', '56887', '61991', '71487', '81495', '91513', '11511', '21519', '31527', - '41535', '51543', '61551', '71559', '81567', '91575', '11583', '21591', '31599', '41617', - '51615', '61623', '71631', '81639', '91647', '11655', '21663', '31671', '41679', '51687', - '61695', '71713', '81711', '91719', '11727', '21735', '31743', '41751', '51759', '61767', - '71775', '81783', '91791', '11799', '21817', '31815', '41823', '51831', '61839', '71847', - '81855', '91863', '11871', '21879', '31887', '41895', '51913', '61911', '71919', '81927', - '91935', '11943', '21951', '31959', '41967', '51975', '61983', '75187', '84583', '94591', - '14599', '24617', '34615', '44623', '54631', '64639', '74647', '84655', '94663', '14671', - '24679', '34687', '44695', '54713', '64711', '74719', '84727', '94735', '14743', '24751', - '34759', '44767', '54775', '64783', '74791', '84799', '94817', '14815', '24823', '34831', - '44839', '54847', '64855', '74863', '84871', '94879', '14887', '24895', '34913', '44911', - '54919', '64927', '74935', '84943', '94951', '14959', '24967', '34975', '44983', '54991', - '64999', '75117', '85115', '95123', '15131', '25139', '35147', '45155', '55163', '65171', - '75179', '89183', '98679', '18687', '28695', '38713', '48711', '58719', '68727', '78735', - '88743', '98751', '18759', '28767', '38775', '48783', '58791', '68799', '78817', '88815', - '98823', '18831', '28831', '38847', '48855', '58863', '68871', '78879', '88887', '98895', - '18913', '28911', '38919', '48927', '58935', '68943', '78951', '88959', '98967', '18175', - '28183', '38991', '48999', '59117', '69115', '79123', '89131', '99139', '19147', '29155', - '39163', '49171', '59179', '69187', '79195', '89113', '99111', '19119', '29127', '39135', - '49143', '59151', '69159', '79167', '89175', '91511', '11512', '21521', '31528', '41536', - '51544', '61552', '71561', '81568', '91576', '11584', '21592', '31611', '41618', '51616', - '61624', '71632', '81641', '91648', '11656', '21664', '31672', '41681', '51688', '61696', - '71714', '81712', '91721', '11728', '21736', '31744', '41752', '51761', '61768', '71776', - '81784', '91792', '11811', '21818', '31816', '41824', '51832', '61841', '71848', '81856', - '91864', '11872', '21881', '31888', '41896', '51914', '61912', '71921', '81928', '91936', - '11944', '21952', '31961', '41968', '51976', '61984', '71992', '82111', '91118', '15112', - '24618', '34616', '44624', '54632', '64641', '74648', '84656', '94664', '14672', '24681', - '34688', '44696', '54714', '64712', '74721', '84728', '94332', '14744', '24752', '34761', - '44768', '54776', '64784', '74792', '84811', '94818', '14816', '24824', '34832', '44841', - '54848', '64856', '74864', '84872', '94881', '14888', '24896', '34914', '44912', '54921', - '64928', '74936', '84944', '94952', '14961', '24968', '34976', '44984', '54992', '65111', - '75118', '85116', '95124', '15132', '25141', '35148', '45156', '55164', '65172', '75181', - '85188', '95196', '15114', '29218', '38714', '48712', '58721', '68728', '78736', '88744', - '98752', '18761', '28768', '38776', '48784', '58792', '68811', '78818', '88816', '98824', - '18832', '28841', '38848', '48856', '58864', '68872', '78881', '89888', '98896', '18914', - '28912', '38921', '48928', '58936', '68944', '78952', '88961', '98968', '18976', '28984', - '38992', '49111', '59118', '69116', '79124', '89132', '99141', '19148', '29156', '39164', - '49172', '59181', '69188', '79196', '89114', '99112', '19121', '29128', '39136', '49144', - '59152', '69161', '79168', '89176', '99184', '19192', '29211', '33314', '42811', '52818', - '62816', '72824', '82832', '92841', '12848', '22856', '32864', '42872', '52881', '62888', - '72896', '82914', '92912', '12921', '22928', '32936', '42944', '52952', '62961', '72968', - '82976', '92984', '12992', '23111', '33118', '43116', '53124', '63132', '73141', '83148', - '93156', '13164', '23172', '33181', '43188', '53196', '63114', '73112', '83121', '93128', - '13136', '23144', '33152', '43161', '53168', '63176', '73184', '83192', '93211', '13218', - '23216', '33224', '43232', '53241', '63248', '73256', '83264', '93272', '13281', '23288', - '33296', '47411', '56896', '66914', '76912', '86921', '96928', '16936', '26944', '36952', - '46961', '56968', '66976', '76984', '86992', '97111', '17118', '27116', '37124', '47132', - '57141', '67148', '77156', '87164', '97172', '17181', '27188', '37196', '47114', '57112', - '67121', '77128', '87136', '97144', '17152', '27161', '37168', '47176', '57184', '67192', - '77211', '87218', '97216', '17224', '27232', '37241', '47248', '57256', '67264', '77272', - '87281', '97288', '17296', '27314', '37312', '47321', '57328', '67336', '77344', '87352', - '97361', '17368', '27376', '37384', '47392', '51496', '61992', '72111', '81118', '91116', - '11124', '21132', '31141', '41148', '51156', '61164', '71172', '81181', '91188', '11196', - '21114', '31112', '41121', '51128', '61136', '71144', '81152', '91161', '11168', '21176', - '31184', '41192', '51211', '61218', '71216', '81224', '91232', '11241', '21248', '31256', - '41264', '51272', '61281', '71288', '81296', '91314', '11312', '21321', '31328', '41336', - '51344', '61352', '71361', '81368', '91376', '11384', '21392', '31411', '41418', '51416', - '61424', '71432', '81441', '91448', '11456', '21464', '31472', '41481', '51488', '65592', - '75188', '85196', '95114', '15112', '25121', '35128', '45136', '55144', '65152', '75161', - '85168', '95176', '15184', '25192', '35211', '45218', '55216', '65224', '75232', '85241', - '95248', '15256', '25264', '35272', '45281', '55288', '65296', '75314', '85312', '95321', - '15328', '25336', '35344', '45352', '55361', '65368', '75376', '85384', '95392', '15411', - '25418', '35416', '45424', '55432', '65441', '75448', '85456', '95464', '15472', '25481', - '35488', '45496', '55514', '65512', '75521', '85528', '95536', '15544', '25552', '35561', - '45568', '55576', '65584', '79688', '89184', '99192', '19211', '29218', '39216', '49224', - '59232', '69241', '79248', '89256', '99264', '19272', '29281', '39288', '49296', '59314', - '69312', '79321', '89328', '99336', '19344', '29352', '39361', '49368', '59376', '69384', - '79392', '89411', '99418', '19416', '29424', '39432', '49441', '59448', '69456', '79464', - '89472', '99481', '19488', '29496', '39514', '49512', '59521', '69528', '79536', '89544', - '99552', '19561', '29568', '39576', '49584', '59592', '69611', '79618', '89616', '99624', - '19632', '29641', '39648', '49656', '59664', '69672', '79681', '81116', '91513', '11521', - '21529', '31537', '41545', '51553', '61561', '71569', '81577', '91585', '11593', '21611', - '31619', '41617', '51625', '61633', '71641', '81649', '91657', '11665', '21673', '31681', - '41689', '51697', '61715', '71713', '81721', '91729', '11737', '21745', '31753', '41761', - '51769', '61777', '71785', '81793', '91811', '11819', '21817', '31825', '41833', '51841', - '61849', '71857', '81865', '91873', '11881', '21889', '31897', '41915', '51913', '61921', - '71921', '81937', '91945', '11953', '21961', '31969', '41977', '51985', '61993', '72111', - '81119', '95113', '14619', '24617', '34625', '44633', '54641', '64649', '74657', '84665', - '94673', '14681', '24689', '34697', '44715', '54713', '64721', '74729', '84737', '94745', - '14753', '24761', '34769', '44777', '54785', '64793', '74811', '84819', '94817', '14825', - '24833', '34841', '44849', '54857', '64865', '74873', '84881', '94889', '14897', '24915', - '34913', '44921', '54929', '64937', '74945', '84953', '94961', '14969', '24977', '34985', - '44993', '55111', '65119', '75117', '85125', '95133', '15141', '25149', '35157', '45165', - '55173', '65181', '75189', '85197', '95115', '19219', '28715', '38713', '48721', '58729', - '68737', '78745', '88753', '98761', '18769', '28777', '38785', '48793', '58811', '68819', - '78817', '88825', '98833', '18841', '28849', '38857', '48865', '58873', '68881', '78889', - '88897', '98915', '18913', '28921', '38929', '48937', '58945', '68953', '78961', '88969', - '98977', '18985', '28993', '39111', '49119', '59117', '69125', '79133', '89141', '99149', - '19157', '29165', '39173', '49181', '59189', '69197', '79115', '89113', '99121', '19129', - '29137', '39145', '49153', '59161', '69169', '79177', '89185', '99193', '19211', '23315', - '32811', '42819', '52817', '62825', '72833', '82841', '92849', '12857', '22865', '32873', - '42881', '52889', '62897', '72915', '82913', '92921', '12929', '22937', '32945', '42953', - '52961', '62969', '72977', '82985', '92993', '13111', '23119', '33117', '43125', '53133', - '63141', '73149', '83157', '93165', '13173', '23181', '33189', '43197', '53115', '63113', - '73121', '83129', '93137', '13145', '23153', '33161', '43169', '53177', '63185', '73193', - '83211', '93219', '13217', '23225', '33233', '43241', '53249', '63257', '73265', '83273', - '93281', '13289', '23297', '37411', '46897', '56915', '66913', '76921', '86929', '96937', - '16945', '26953', '36961', '46969', '56977', '66985', '76993', '87111', '97119', '17117', - '27125', '37133', '47141', '57149', '67157', '77165', '87173', '97181', '17189', '27197', - '37115', '47113', '57121', '67129', '77137', '87145', '97153', '17161', '27169', '37177', - '47185', '57193', '67211', '77219', '87217', '97225', '17233', '27241', '37249', '47257', - '57265', '67273', '77281', '87289', '97297', '17315', '27313', '37321', '47329', '57337', - '67345', '77353', '87361', '97369', '17377', '27385', '37393', '41497', '51993', '62111', - '72119', '81117', '91125', '11133', '21141', '31149', '41157', '51165', '61173', '71181', - '81189', '91197', '21115', '21113', '31121', '41129', '51137', '61145', '71153', '81161', - '91169', '11177', '21185', '31193', '41211', '51219', '61217', '71225', '81233', '91241', - '11249', '21257', '31265', '41273', '51281', '61289', '71297', '81315', '91313', '11321', - '21329', '31337', '41345', '51353', '61361', '71369', '81377', '91385', '11393', '21411', - '31419', '41417', '51425', '61433', '71441', '81449', '91457', '11465', '21473', '31481', - '41489', '55593', '65189', '75197', '85115', '95113', '15121', '25129', '35137', '45145', - '55153', '65161', '75169', '85177', '95185', '15193', '25211', '35219', '45217', '55225', - '65233', '75241', '85249', '95257', '15265', '25273', '35281', '45289', '55297', '65315', - '75313', '85321', '95329', '15337', '25345', '35353', '45361', '55369', '65377', '75385', - '85393', '95411', '15419', '25417', '35425', '45433', '55441', '65449', '75457', '85465', - '95473', '15481', '25489', '35497', '45515', '55513', '65521', '75529', '85537', '95545', - '15553', '25561', '35569', '45577', '55585', '69689', '79185', '89193', '99211', '19219', - '29217', '39225', '49233', '59241', '69249', '79257', '89265', '99273', '19281', '29289', - '39297', '49315', '59313', '69321', '79329', '89337', '99345', '19353', '29361', '39369', - '49377', '59385', '69313', '79411', '89419', '99417', '19425', '29433', '39441', '49449', - '59457', '69465', '79473', '89481', '99489', '19497', '29515', '39513', '49521', '59529', - '69537', '79545', '89553', '99561', '19569', '29577', '39585', '49593', '59611', '69619', - '79617', '89625', '99633', '19641', '29649', '39657', '49665', '59673', '69681', '71117', - '81514', '91522', '11531', '21538', '31546', '41554', '51562', '61571', '71578', '81586', - '91514', '11612', '21611', '31618', '41626', '51634', '61642', '71651', '81658', '91666', - '11674', '21682', '31691', '41698', '51716', '61714', '71722', '81731', '91738', '11746', - '21754', '31762', '41771', '51778', '61786', '71794', '81812', '91811', '11818', '21826', - '31834', '41842', '51851', '61858', '71866', '81874', '91882', '11891', '21898', '31916', - '41914', '51922', '61931', '71938', '81946', '91954', '11962', '21971', '31978', '41986', - '51994', '61112', '72111', '85114', '94611', '14618', '24626', '34634', '44642', '54651', - '64658', '74666', '84674', '94682', '14691', '24698', '34716', '44714', '54722', '64731', - '74738', '84746', '94754', '14762', '24771', '34778', '44786', '54794', '64812', '74811', - '84818', '94826', '14834', '24842', '34851', '44858', '54866', '64874', '74882', '84891', - '94898', '14916', '24914', '34922', '44931', '54938', '64946', '74954', '84962', '94971', - '14978', '24986', '34994', '45112', '55111', '65118', '75126', '85134', '95142', '15151', - '25158', '35166', '45174', '55182', '65191', '75198', '85116', '99211', '18716', '28714', - '38722', '48731', '58738', '68746', '78754', '88762', '98771', '18778', '28786', '38794', - '48812', '58811', '68818', '78826', '88834', '98842', '18851', '28858', '38866', '48874', - '58882', '68891', '78898', '88916', '98114', '18922', '28931', '38938', '48946', '58954', - '68962', '78971', '88978', '98986', '18994', '29112', '39111', '49118', '59126', '69134', - '79142', '89151', '99158', '19166', '29174', '39182', '49191', '59198', '69116', '79114', - '89122', '99131', '19138', '29146', '39154', '49162', '59171', '69178', '79186', '89194', - '99212', '13316', '22812', '32811', '42818', '52826', '62834', '72842', '82851', '92858', - '12866', '22874', '32882', '42891', '52898', '62916', '72914', '82922', '92931', '12938', - '22946', '32954', '42962', '52971', '62978', '72986', '82994', '93112', '13111', '23118', - '33126', '43134', '53142', '63151', '73158', '83166', '93174', '13182', '23191', '33198', - '43116', '53114', '63122', '73131', '83138', '93146', '13154', '23162', '33171', '43178', - '53186', '63194', '73212', '83211', '93218', '13226', '23234', '33242', '43251', '53258', - '63266', '73274', '83282', '93291', '13298', '27412', '36898', '46916', '56914', '66922', - '76931', '86938', '96946', '16954', '26962', '36971', '46978', '56986', '66994', '77112', - '87111', '97118', '17126', '27134', '37142', '47151', '57158', '67166', '77174', '87182', - '97191', '17198', '27116', '37114', '47122', '57131', '67138', '77146', '87154', '97162', - '17171', '27178', '37186', '47194', '57212', '67211', '77218', '87226', '97234', '17242', - '27251', '37258', '47266', '57274', '67282', '77291', '87298', '97316', '17314', '27322', - '37331', '47338', '57346', '67354', '77362', '87371', '97378', '17386', '27394', '31498', - '41994', '51112', '62111', '71118', '81126', '91134', '11142', '21151', '31158', '41166', - '51174', '61182', '72191', '81198', '91116', '11114', '21122', '31131', '41138', '51146', - '61154', '71162', '81171', '91178', '11186', '21194', '31212', '41211', '51218', '61226', - '71234', '81242', '91251', '11258', '21266', '31274', '41282', '51291', '61298', '71316', - '81314', '91322', '11331', '21338', '31346', '41354', '51362', '61371', '71378', '81386', - '91394', '11412', '21411', '31418', '41426', '51434', '61442', '71451', '81458', '91466', - '11474', '21482', '31491', '45594', '55191', '65198', '75116', '85114', '95122', '15131', - '25138', '35146', '45154', '55162', '65171', '75178', '85186', '95194', '15212', '25211', - '35218', '45226', '55234', '65242', '75251', '85258', '95266', '15274', '25282', '35291', - '45298', '55316', '65314', '75322', '85331', '95338', '15346', '25354', '35362', '45371', - '55378', '65386', '75394', '85412', '95411', '15418', '25426', '35434', '45442', '55451', - '65458', '75466', '85474', '95482', '15491', '25498', '35516', '45514', '55522', '65531', - '75538', '85546', '95554', '15562', '25571', '35578', '45586', '59691', '69186', '79194', - '89212', '99211', '19218', '29226', '39234', '49242', '59251', '69258', '79266', '89274', - '99282', '19291', '29298', '39316', '49314', '59322', '69331', '79338', '89346', '99354', - '19362', '29371', '39378', '49386', '59394', '69412', '79411', '89418', '99426', '19434', - '29442', '39451', '49458', '59466', '69474', '79482', '89491', '99498', '19516', '29514', - '39522', '49531', '59538', '69546', '79554', '89562', '99571', '19578', '29586', '39594', - '49612', '59611', '69618', '79626', '89634', '99642', '19651', '29658', '39666', '49674', - '59682', '61118', '71515', '81523', '91531', '11539', '21547', '31555', '41563', '51571', - '61579', '71587', '81595', '91613', '11611', '21619', '31627', '41635', '51643', '61651', - '71659', '81667', '91675', '11683', '21691', '31699', '41717', '51715', '61723', '71731', - '81739', '91747', '11755', '21763', '31771', '41779', '51787', '61795', '71813', '81811', - '91819', '11827', '21835', '31843', '41851', '51859', '61867', '71875', '81883', '91891', - '11899', '21917', '31915', '41923', '51931', '61939', '71947', '81955', '91963', '11971', - '21979', '31987', '41995', '51113', '62111', '75115', '84611', '94619', '14627', '24635', - '34643', '44651', '54651', '64667', '74675', '84683', '94691', '14699', '24717', '34715', - '44723', '54731', '64739', '74747', '84755', '94763', '14771', '24779', '34787', '44795', - '54813', '64811', '74819', '84827', '94835', '14843', '24851', '34859', '44867', '58475', - '64883', '74891', '84899', '94917', '14915', '24923', '34931', '44939', '54947', '64955', - '74963', '84971', '94979', '14987', '24995', '35113', '45111', '55119', '65127', '75135', - '85143', '95151', '15159', '25167', '35175', '45183', '55191', '65199', '75117', '89211', - '98717', '18715', '28723', '38731', '48739', '58747', '68755', '78763', '88771', '98779', - '18787', '28795', '38813', '48811', '58819', '68827', '78835', '88843', '98851', '18859', - '28867', '38875', '48883', '58891', '68899', '78917', '88915', '98923', '18931', '28939', - '38947', '48955', '58963', '68971', '78979', '88987', '98995', '19113', '29111', '39119', - '49127', '59135', '69143', '79151', '89159', '99167', '19175', '29183', '39191', '49199', - '59117', '69115', '79123', '89131', '99139', '19147', '29155', '39163', '49171', '59179', - '69187', '79195', '89213', '93317', '12813', '22811', '32819', '42827', '52835', '62843', - '72851', '82859', '92867', '12875', '22883', '32891', '42899', '52917', '62915', '72923', - '82931', '92939', '12947', '22955', '32963', '42971', '52979', '62987', '72995', '83113', - '93111', '13119', '23127', '33135', '43143', '53151', '63159', '73167', '83175', '93183', - '13191', '23199', '33117', '43115', '53123', '63131', '73139', '83147', '93155', '13163', - '23171', '33179', '43187', '53195', '63213', '73211', '83219', '93227', '13235', '23243', - '33251', '43259', '53267', '63275', '73283', '83291', '93299', '17413', '26899', '36917', - '46915', '56923', '66931', '76939', '86947', '96955', '16963', '26971', '36979', '46987', - '56995', '67113', '77111', '87119', '97127', '17135', '27143', '37151', '47159', '57167', - '67175', '77183', '87191', '97199', '17117', '27115', '37123', '47131', '57139', '67147', - '77155', '87163', '97171', '17179', '27187', '37195', '47213', '57211', '67219', '77227', - '87235', '97243', '17251', '27259', '37267', '47275', '57283', '67291', '77299', '87317', - '97315', '17323', '27331', '37339', '47347', '57355', '67363', '77371', '87379', '97387', - '17395', '21499', '31995', '41113', '52111', '62119', '71127', '81135', '91143', '11151', - '21159', '31167', '41175', '51183', '62191', '72199', '81117', '91115', '11123', '21131', - '31139', '41147', '51155', '61163', '71171', '81179', '91187', '11195', '21213', '31211', - '41219', '51227', '61235', '71243', '81251', '91259', '11267', '21275', '31283', '41291', - '51299', '61317', '71315', '81323', '91331', '11339', '21347', '31355', '41363', '51371', - '61379', '71387', '81395', '91413', '11411', '21419', '31427', '41435', '51443', '61451', - '71459', '81467', '91475', '11483', '21491', '35595', '45191', '55199', '65117', '75115', - '85123', '95131', '15139', '25147', '35155', '45163', '55171', '65179', '75187', '85195', - '95213', '15211', '25219', '35227', '45235', '55243', '65251', '75259', '85267', '95275', - '15283', '25291', '35299', '45317', '55315', '65323', '75331', '85339', '95347', '15355', - '25363', '35371', '45379', '55387', '65395', '75413', '85411', '95419', '15427', '25435', - '35443', '45451', '55459', '65467', '75475', '85483', '95491', '15499', '25517', '35517', - '45523', '55531', '65539', '75547', '86555', '95563', '15571', '25579', '35587', '49691', - '59187', '69195', '79213', '89211', '99219', '19227', '29235', '39243', '49251', '59259', - '69267', '79275', '89283', '99291', '19299', '29317', '39315', '49323', '59331', '69339', - '79347', '89355', '99363', '19371', '29379', '39387', '49395', '59413', '69411', '79419', - '89427', '99435', '19443', '29451', '39459', '49467', '59475', '69483', '79491', '89499', - '99517', '19515', '29523', '39531', '49539', '59547', '69555', '79563', '89571', '99579', - '19587', '29595', '39613', '49611', '59619', '69627', '79635', '89643', '99651', '19659', - '29667', '39675', '49683', '51119', '61516', '71524', '81532', '91541', '11548', '21556', - '31564', '41572', '51581', '61588', '71596', '81614', '91612', '11621', '21628', '31636', - '41644', '51652', '61661', '71668', '81676', '91684', '11692', '21711', '31718', '41716', - '51724', '61732', '71741', '81748', '91756', '11764', '21772', '31781', '41788', '51796', - '61814', '71812', '81821', '91828', '11836', '21844', '31852', '41861', '51868', '61876', - '71884', '81892', '91911', '11918', '21916', '31924', '41932', '51941', '61948', '71956', - '81964', '91972', '11981', '21988', '31996', '41114', '51112', '61121', '74612', '84621', - '94628', '14636', '24644', '34652', '44661', '54668', '64676', '74684', '84692', '94711', - '14718', '24716', '34724', '44732', '54741', '64748', '74756', '84764', '94772', '14781', - '24788', '34796', '44814', '54812', '64821', '74828', '84836', '94844', '14852', '24861', - '34868', '44876', '54884', '64892', '74911', '84918', '94916', '14924', '24932', '34941', - '44948', '54956', '64964', '74972', '84981', '94988', '14996', '25114', '35112', '45121', - '55128', '65136', '75144', '85152', '95161', '15168', '25176', '35184', '45192', '55111', - '65118', '75116', '88718', '98716', '18724', '28732', '38741', '48748', '58756', '68764', - '78772', '88781', '98788', '18796', '28814', '38812', '48821', '58828', '68836', '78844', - '88852', '98861', '18868', '28876', '38884', '48892', '58911', '68918', '78916', '88924', - '98932', '18941', '28948', '38956', '48964', '58972', '68981', '78988', '88996', '99114', - '19112', '29121', '39128', '49136', '59144', '69152', '79161', '89168', '99176', '19184', - '29192', '39111', '49118', '59116', '69124', '79132', '89141', '99148', '19156', '29164', - '39172', '49181', '59188', '69196', '79214', '83318', '92814', '12812', '22821', '32828', - '42836', '52844', '62852', '72861', '82868', '92876', '12884', '22892', '32911', '42918', - '52916', '62924', '72932', '82941', '92948', '12956', '22964', '32972', '42981', '52988', - '62996', '73114', '83112', '93121', '13128', '23136', '33144', '43152', '53161', '63168', - '73176', '83184', '93192', '13111', '23118', '33116', '43124', '53132', '63141', '73148', - '83156', '93164', '13172', '23181', '33188', '43196', '53214', '63212', '73221', '83228', - '93236', '13244', '23252', '33261', '43268', '53276', '63284', '73292', '83311', '97414', - '16911', '26918', '36916', '46924', '56932', '66941', '76948', '86956', '96964', '16972', - '26981', '36988', '46996', '57114', '67112', '77121', '87128', '97136', '17144', '27152', - '37161', '47168', '57176', '67184', '77192', '87111', '97118', '17116', '27124', '37132', - '47114', '57148', '67156', '77164', '87172', '97181', '17188', '27196', '37214', '47212', - '57221', '67228', '77236', '87244', '97252', '17261', '27268', '37276', '47284', '57292', - '67311', '77318', '87316', '97324', '17332', '27341', '37348', '47356', '57364', '67372', - '77381', '87388', '97396', '11511', '21996', '31114', '41112', '51121', '61128', '71136', - '81144', '91152', '11161', '21168', '31176', '41184', '51112', '62111', '71118', '81116', - '91124', '11132', '21141', '31148', '41156', '51164', '61172', '71181', '81188', '91196', - '11214', '21212', '31221', '41228', '51236', '61244', '71252', '81261', '91268', '11276', - '21284', '31292', '41311', '51318', '61316', '71324', '81332', '91341', '11348', '21356', - '31364', '41372', '51381', '61388', '71396', '81414', '91412', '11421', '21428', '31436', - '41444', '51452', '61461', '71468', '81476', '91484', '11492', '25596', '35192', '45111', - '55118', '65116', '75124', '85132', '95141', '15148', '25156', '35164', '45172', '55181', - '65188', '75196', '85214', '95212', '15221', '25228', '35236', '45244', '55252', '65261', - '75268', '85276', '95284', '15292', '25311', '35318', '45316', '55324', '65332', '75341', - '85348', '95356', '15364', '25372', '35381', '45388', '55396', '65414', '75412', '85421', - '95428', '15436', '25444', '35452', '45461', '55468', '65476', '75484', '85492', '95511', - '15518', '25516', '35524', '45532', '55541', '65548', '75556', '85564', '95572', '15581', - '25588', '39692', '49188', '59196', '69214', '79212', '89221', '99228', '19236', '29244', - '39252', '49261', '59268', '69276', '79284', '89292', '99311', '19318', '29316', '39324', - '49332', '59341', '69348', '79356', '89364', '99372', '19381', '29388', '39396', '49414', - '59412', '69421', '79428', '89436', '99444', '19452', '29461', '39468', '49476', '59484', - '69492', '79511', '89518', '99516', '19524', '29532', '39541', '49548', '59556', '69564', - '79572', '89581', '99588', '19596', '29614', '39612', '49621', '59628', '69636', '79644', - '89652', '99661', '19668', '29676', '39684', '41121', '51517', '61525', '71533', '81541', - '91549', '11557', '21565', '31573', '41581', '51589', '61597', '71615', '81613', '91621', - '11629', '21637', '31645', '41653', '51661', '61669', '71677', '81685', '91693', '11711', - '21719', '31717', '41725', '51733', '61741', '71749', '81757', '91765', '11773', '21781', - '31789', '41797', '51815', '61813', '71821', '81829', '91837', '11845', '21853', '31861', - '41869', '51877', '61885', '71893', '81911', '91919', '11917', '21925', '31933', '41941', - '51949', '61957', '71965', '81973', '91981', '11989', '21997', '31115', '41113', '51121', - '64613', '74621', '84629', '94637', '14645', '24653', '34661', '44669', '54677', '64685', - '74693', '84711', '94719', '14717', '24725', '34733', '44741', '54749', '64757', '74765', - '84773', '94781', '14789', '24797', '34815', '44813', '54821', '64829', '74837', '84845', - '94853', '14861', '24869', '34877', '44885', '54893', '64911', '74919', '84917', '94925', - '14933', '24941', '34949', '44957', '54965', '64973', '74981', '84989', '94997', '15115', - '25113', '35121', '45129', '55137', '65145', '75153', '85161', '95169', '15177', '25185', - '35193', '45111', '55119', '69213', '78719', '88717', '98725', '18733', '28741', '38749', - '48757', '58765', '68773', '78781', '88789', '98797', '18815', '28813', '38821', '48829', - '58837', '68845', '78853', '88861', '98869', '18877', '28885', '38893', '48911', '58919', - '68917', '78925', '88933', '98941', '18949', '28957', '38965', '48973', '58981', '68989', - '78997', '89115', '99113', '19121', '29129', '39137', '49145', '59153', '69161', '79169', - '89177', '99185', '19193', '29111', '39119', '49117', '59125', '69133', '79141', '89149', - '99157', '19165', '29173', '39181', '49189', '59197', '69215', '73319', '82815', '92813', - '12821', '22829', '32837', '42845', '52853', '62861', '72869', '82877', '92885', '12893', - '22911', '32919', '42917', '52925', '62933', '72941', '82949', '92957', '12965', '22973', - '32981', '42989', '52997', '63115', '73113', '83121', '93129', '13137', '23145', '33153', - '43161', '53169', '63177', '73185', '83193', '93111', '13119', '23117', '33125', '43133', - '53141', '63149', '73157', '83165', '93173', '13181', '23189', '33197', '43215', '53213', - '63221', '73229', '83237', '93245', '13253', '23261', '33269', '43277', '53285', '63293', - '73311', '87415', '96911', '16919', '26917', '36925', '46933', '56941', '66949', '76957', - '86965', '96973', '16981', '26989', '36997', '47115', '57113', '67121', '77129', '87137', - '97145', '17153', '27161', '37169', '47177', '57185', '67193', '77111', '87119', '97117', - '17125', '27133', '37141', '47149', '57157', '67165', '77173', '87181', '97189', '17197', - '27215', '37213', '47221', '57229', '67237', '77245', '87253', '97261', '17269', '27277', - '37285', '47293', '57311', '67319', '77317', '87325', '97333', '17341', '27349', '37357', - '47365', '57373', '67381', '77389', '87397', '91511', '11997', '21115', '31113', '41121', - '51129', '61137', '71145', '81153', '91161', '11169', '21177', '31185', '41193', '52111', - '61119', '71117', '81125', '91133', '11141', '21149', '31157', '41165', '51173', '61181', - '71189', '81197', '91215', '11213', '21221', '31229', '41237', '51245', '61253', '71261', - '81269', '91277', '11285', '21293', '31311', '41319', '51317', '61325', '71333', '81341', - '91349', '11357', '21365', '31373', '41381', '51389', '61397', '71415', '81413', '91421', - '11429', '21437', '31445', '41453', '51461', '61469', '71477', '81485', '91493', '11597', - '25193', '35111', '45119', '55117', '65125', '75133', '85141', '95149', '15157', '25165', - '35173', '45181', '55189', '65197', '75215', '85213', '95221', '15229', '25237', '35245', - '45253', '55261', '65269', '75277', '85285', '95293', '15311', '25319', '35317', '45325', - '55333', '65341', '75349', '85357', '95365', '15373', '25381', '35389', '45397', '55415', - '65413', '75421', '85429', '95437', '15445', '25453', '35461', '45469', '55477', '65485', - '75493', '85511', '95519', '15517', '25525', '35533', '45541', '55549', '65557', '75565', - '85573', '95581', '15589', '29693', '39189', '49197', '59215', '69213', '79221', '89229', - '99237', '19245', '29253', '39261', '49269', '59277', '69285', '79293', '89311', '99319', - '19317', '29325', '39333', '49341', '59349', '69357', '79365', '89373', '99381', '19389', - '29397', '39415', '49413', '59421', '69429', '79437', '89445', '99453', '19461', '29469', - '39477', '49485', '59493', '69511', '79591', '89517', '99525', '19533', '29541', '39549', - '49557', '59565', '69573', '79581', '89589', '99597', '19615', '29613', '39621', '49629', - '59637', '69645', '79653', '89661', '99669', '19677', '29685', '31121', '41518', '51526', - '61534', '71542', '81551', '91558', '11566', '21574', '31582', '41591', '51598', '61616', - '71614', '81622', '91631', '11638', '21646', '31654', '41662', '51671', '61678', '71686', - '81694', '91712', '11711', '21718', '31726', '41734', '51742', '61751', '71758', '81766', - '91774', '11782', '21791', '31798', '41816', '51814', '61822', '71831', '81838', '91846', - '11854', '21862', '31871', '41878', '51886', '61894', '71912', '81911', '91918', '11926', - '21934', '31942', '41951', '51958', '61966', '71974', '81982', '91991', '11998', '21116', - '31114', '45118', '54614', '64622', '74631', '84638', '94646', '14654', '24662', '34671', - '44678', '54686', '64694', '74712', '84711', '94718', '14726', '24734', '34742', '44751', - '54758', '64766', '74774', '84782', '94791', '14798', '24816', '34814', '44822', '54831', - '64838', '74846', '84854', '94862', '14871', '24878', '34886', '44894', '54912', '64911', - '74918', '84926', '94934', '14942', '24951', '34958', '44966', '54974', '64982', '74991', - '84998', '95116', '15114', '25122', '35131', '45138', '55146', '65154', '75162', '85171', - '95178', '15186', '25194', '35112', '45111', '59214', '68711', '78718', '88726', '98734', - '18742', '28751', '38758', '48766', '58774', '68782', '78791', '88798', '98816', '18814', - '28822', '38831', '48838', '58846', '68854', '78862', '88871', '98878', '18886', '28894', - '38912', '48911', '58918', '68926', '78934', '88942', '98951', '18958', '28966', '38974', - '48982', '58991', '68998', '79116', '89114', '99122', '19131', '29138', '39146', '49154', - '59162', '69171', '79178', '89186', '99194', '19112', '29111', '39118', '49126', '59134', - '69142', '79151', '89158', '99166', '19174', '29182', '39191', '49198', '59216', '63311', - '72816', '82814', '92822', '12831', '22838', '32846', '42854', '52862', '62871', '72878', - '82886', '92894', '12912', '22911', '32918', '42926', '52934', '62942', '72951', '82958', - '92966', '12974', '22982', '32991', '42998', '53116', '63114', '73122', '83131', '93138', - '13146', '23154', '33162', '43171', '53178', '63186', '73194', '83112', '93111', '13118', - '23126', '33134', '43142', '53151', '63158', '73166', '83174', '93182', '13191', '23198', - '33216', '43214', '53222', '63231', '73238', '83246', '93254', '13262', '23271', '33278', - '43286', '53294', '63312', '77416', '86912', '96911', '16918', '26926', '36934', '46942', - '56951', '66958', '76966', '86974', '96982', '16991', '26998', '37116', '47114', '57122', - '67131', '77138', '87146', '97154', '17162', '23131', '37178', '47186', '57194', '67112', - '77111', '87118', '97126', '17134', '27142', '37151', '47158', '57166', '67174', '77182', - '87191', '97198', '17216', '27214', '37222', '47231', '57238', '67246', '77254', '87262', - '97271', '17278', '27286', '37294', '47312', '57311', '67318', '77326', '87334', '97342', - '17351', '27358', '37366', '47374', '57382', '67391', '77398', '81512', '91998', '21116', - '21114', '31122', '41131', '51138', '61146', '71154', '81162', '91171', '11178', '21186', - '31194', '41112', '52111', '61118', '71126', '81134', '91142', '11151', '21158', '31166', - '41174', '51182', '61191', '71198', '81216', '91214', '11222', '21231', '31238', '41246', - '51254', '61262', '71271', '81278', '91286', '11294', '21312', '31311', '41318', '51326', - '61334', '71342', '81351', '91358', '11366', '21374', '31382', '41391', '51398', '61416', - '71414', '81422', '91431', '11438', '21446', '31454', '41462', '51471', '61478', '71486', - '81494', '95598', '15194', '25112', '35111', '45118', '55126', '65134', '75142', '85151', - '95158', '15166', '25174', '35182', '45191', '55198', '65216', '75214', '85222', '95231', - '15238', '25246', '35254', '45262', '55271', '65278', '75286', '85294', '95312', '15311', - '25318', '35326', '45334', '55342', '65351', '75358', '85366', '95374', '15382', '25391', - '35398', '45416', '55414', '65422', '75431', '85438', '95446', '15454', '25462', '35471', - '45478', '55486', '65494', '75512', '85511', '95518', '15526', '25534', '35542', '45551', - '65558', '65566', '75574', '85582', '95591', '19694', '29191', '39198', '49216', '59214', - '69222', '79231', '89238', '99246', '19254', '29262', '39271', '49278', '59286', '69294', - '79312', '89311', '99318', '19326', '29334', '39342', '49351', '59358', '69366', '79374', - '89382', '99391', '19318', '29416', '39414', '49422', '59431', '69438', '79446', '89454', - '99462', '19471', '29478', '39486', '49494', '59512', '69511', '79518', '89526', '99534', - '19542', '29551', '39558', '49566', '59574', '69582', '79591', '89598', '99616', '19614', - '29622', '39631', '49234', '59646', '69654', '79662', '89671', '99678', '19686', '21122', - '31519', '41527', '51535', '61543', '71551', '81559', '91567', '11575', '21583', '31591', - '41599', '51617', '61615', '71623', '81631', '91639', '11647', '21655', '31663', '41671', - '51679', '61687', '71695', '81713', '91711', '11719', '21727', '31735', '41743', '51751', - '61759', '71767', '81775', '91783', '11791', '21799', '31817', '41815', '51823', '61831', - '71839', '81847', '91855', '11863', '21871', '31879', '41887', '51895', '61913', '71911', - '81919', '91927', '11935', '21943', '31951', '41959', '51967', '61975', '71983', '81991', - '91999', '21117', '21115', '35119', '44615', '54623', '64631', '74639', '84647', '94655', - '14663', '24671', '34679', '44687', '54695', '64713', '74711', '84719', '94727', '14735', - '24743', '34751', '44759', '54767', '64775', '74783', '84791', '94799', '14817', '24815', - '34823', '44831', '54839', '64847', '74855', '84863', '94871', '14879', '24887', '34895', - '44913', '54911', '64919', '74927', '84935', '94943', '14951', '24959', '34967', '44975', - '54983', '64991', '74999', '85117', '95115', '15123', '25131', '35139', '45147', '55155', - '65163', '75171', '85179', '95187', '15195', '25113', '35111', '49215', '58711', '68719', - '78727', '88735', '98743', '18751', '28759', '38767', '48775', '58783', '68791', '78799', - '88817', '98815', '18823', '28831', '38839', '48847', '58855', '68863', '78871', '88879', - '98887', '18895', '28913', '38911', '48919', '58927', '68935', '78943', '88951', '98959', - '18967', '28975', '38983', '48991', '58999', '69117', '79115', '89123', '99131', '19139', - '29147', '39155', '49163', '59171', '69179', '79187', '89195', '99113', '19111', '29119', - '39127', '49135', '59143', '69151', '79159', '89167', '99175', '19183', '29191', '39199', - '49217', '53311', '62817', '72815', '82823', '92831', '12839', '22847', '32855', '42863', - '52871', '62879', '72887', '82895', '92113', '12911', '22919', '32927', '42935', '52943', - '62951', '72959', '82967', '92975', '12983', '22991', '32999', '43117', '53115', '63123', - '73131', '83139', '93147', '13155', '23163', '33171', '43179', '53187', '63195', '73113', - '83111', '93119', '13127', '23135', '33143', '43151', '53159', '63167', '73175', '83183', - '93191', '13199', '23217', '33215', '43223', '53231', '63239', '73247', '83255', '93263', - '13271', '23279', '33287', '43295', '53313', '67417', '76193', '86911', '96919', '16927', - '26935', '36943', '46951', '56959', '66967', '76975', '86983', '96991', '16999', '27117', - '37115', '47123', '57131', '67139', '77147', '87155', '97163', '17171', '27179', '37187', - '47195', '57113', '67111', '77119', '87127', '97135', '17143', '27151', '37159', '47167', - '57175', '67183', '77191', '87199', '97217', '17215', '27223', '37231', '47239', '57247', - '67255', '77263', '87271', '97279', '17287', '27215', '37313', '47311', '57319', '67327', - '77335', '87343', '97351', '17359', '27367', '37375', '47383', '57391', '67399', '71513', - '81999', '91117', '11115', '21123', '31131', '41139', '51147', '61155', '71163', '81171', - '91179', '11187', '21195', '31113', '42111', '51119', '61127', '71135', '81143', '91151', - '11159', '21167', '31175', '41183', '51191', '61199', '71217', '81215', '91223', '11231', - '21239', '31247', '41255', '51263', '61271', '71279', '81287', '91295', '11313', '21311', - '31319', '41327', '51335', '61343', '71351', '81359', '91367', '11375', '21383', '31391', - '41399', '51417', '61415', '71423', '81431', '91439', '11447', '21455', '31463', '41471', - '51479', '61487', '71495', '85599', '95195', '15113', '25111', '35119', '45127', '55135', - '65143', '75151', '85159', '95167', '15175', '25183', '35191', '45199', '55217', '65215', - '75223', '85231', '95239', '15247', '25255', '35263', '45271', '55279', '65287', '75295', - '85313', '95311', '15319', '25327', '35335', '45343', '55351', '65359', '75367', '85375', - '95383', '15391', '25399', '35417', '45415', '55423', '65431', '75439', '85447', '95455', - '15463', '25471', '35479', '45487', '55495', '65513', '75511', '85519', '95527', '15535', - '25543', '35551', '45559', '55567', '65575', '75583', '85591', '99695', '19191', '29199', - '39217', '49215', '59223', '69231', '79239', '89247', '99255', '19263', '29271', '39279', - '49287', '59295', '69313', '79311', '89319', '99327', '19335', '29343', '39351', '49359', - '59367', '69375', '79383', '89391', '99399', '19417', '29415', '39423', '49431', '59439', - '69447', '79455', '89463', '99471', '19479', '29487', '39495', '49513', '59511', '69519', - '79527', '89535', '99543', '19551', '29559', '39567', '49575', '59583', '69591', '79599', - '89617', '99615', '19623', '29631', '39639', '49647', '59655', '69663', '79671', '89679', - '99687', '11123', '21124', '31132', '41141', '51148', '61156', '71164', '81172', '91181', - '11188', '21196', '31114', '41112', '51121', '61128', '71136', '81144', '91152', '11161', - '21168', '31176', '41184', '51192', '61211', '71218', '81216', '91224', '11232', '21241', - '31248', '41256', '51264', '61272', '71281', '81288', '91296', '11314', '21312', '31321', - '41328', '51336', '61344', '71352', '81361', '91368', '11376', '21384', '31392', '41411', - '51418', '61416', '71424', '81432', '91441', '11448', '21456', '31464', '41472', '51481', - '61488', '71496', '81514', '91512', '11521', '21528', '35121', '45128', '55136', '65144', - '75152', '85161', '95168', '15176', '25184', '35192', '45211', '55218', '65216', '75224', - '85232', '95241', '15248', '25256', '35264', '45272', '55281', '65288', '75296', '85314', - '95312', '15321', '25328', '35336', '45344', '55352', '65361', '75368', '85376', '95384', - '15392', '25411', '35418', '45416', '55424', '65432', '75441', '85448', '95456', '15464', - '25472', '35481', '45488', '55496', '65514', '75512', '85521', '95528', '15536', '25544', - '35552', '45561', '55568', '65576', '75584', '85592', '95611', '15618', '25616', '39721', - '49216', '59224', '69232', '79241', '89248', '99256', '19264', '29272', '39281', '49288', - '59296', '69314', '79312', '89321', '99328', '19336', '29344', '39352', '49361', '59368', - '69376', '79384', '89392', '99411', '19418', '29416', '39424', '49432', '59441', '69448', - '79456', '89464', '99472', '19481', '29488', '39496', '49514', '59512', '69521', '79528', - '89536', '99544', '19552', '29561', '39568', '49576', '59584', '69592', '79611', '89618', - '99616', '19624', '29632', '39641', '49648', '59656', '69664', '79672', '89681', '99688', - '19696', '29714', '39712', '43816', '53312', '63321', '73328', '83336', '93344', '13352', - '23361', '33368', '43376', '53384', '63392', '73411', '83418', '93416', '13424', '23432', - '33441', '43448', '53456', '63464', '73472', '83481', '93488', '13496', '23514', '33512', - '43521', '53528', '63536', '73544', '83552', '93561', '13568', '23576', '33584', '43592', - '53611', '63618', '73616', '83624', '93632', '13641', '23648', '33656', '43664', '53672', - '63681', '73688', '83696', '93714', '13712', '23721', '33728', '43736', '53744', '63752', - '73761', '83768', '93776', '13784', '23792', '33811', '43818', '57912', '67418', '77416', - '87424', '97432', '17441', '27448', '37456', '47464', '57472', '67481', '77488', '87496', - '97514', '17512', '27521', '37528', '47536', '57544', '67552', '77561', '87568', '97576', - '17584', '27592', '37611', '47618', '57616', '67624', '77632', '87641', '97648', '17656', - '27664', '37672', '47681', '57688', '67696', '77714', '87712', '97721', '17728', '27736', - '37744', '47752', '57761', '67768', '77776', '87784', '97792', '17811', '27818', '37816', - '47824', '57832', '67841', '77848', '87856', '97864', '17872', '27881', '37888', '47896', - '57914', '62118', '71514', '81512', '91521', '11528', '21536', '31544', '41552', '51561', - '61568', '71576', '81584', '91592', '11611', '21618', '31616', '41624', '51632', '61641', - '71648', '81656', '91664', '11672', '21681', '31688', '41696', '51714', '61712', '71721', - '81728', '91736', '11744', '21752', '31761', '41768', '51776', '61784', '71792', '81811', - '91818', '11816', '21824', '31832', '41841', '51848', '61856', '71864', '81872', '91881', - '11888', '21896', '31914', '41912', '51921', '61928', '71936', '81944', '91952', '11961', - '21968', '31976', '41984', '51992', '62111', '76114', '85611', '95618', '15616', '25624', - '35632', '45641', '55648', '65656', '75664', '85672', '95681', '15688', '25696', '35714', - '45712', '55721', '65728', '75736', '85744', '95752', '15761', '25768', '35776', '45784', - '55712', '65811', '75818', '85816', '95824', '15832', '25841', '35848', '45856', '55864', - '65872', '75881', '85888', '95896', '15914', '25912', '35921', '45928', '55936', '65944', - '75952', '85961', '95968', '15976', '25984', '35992', '46111', '56118', '66116', '76124', - '86132', '96141', '16148', '26156', '36164', '46172', '56181', '66188', '76196', '81211', - '99696', '19714', '29712', '39721', '49728', '59736', '69744', '79752', '89761', '99768', - '19776', '29784', '39792', '49811', '59818', '69816', '79824', '89832', '99841', '19848', - '29856', '39864', '49872', '59881', '69888', '79896', '89914', '99912', '19921', '29928', - '39936', '49944', '59952', '69961', '79968', '89976', '99984', '19992', '22111', '31118', - '41116', '51124', '61132', '71141', '81148', '91156', '11164', '21172', '31181', '41188', - '51196', '61114', '71112', '81121', '91128', '11136', '21144', '31152', '41161', '51168', - '61176', '71184', '81192', '91528', '11125', '21133', '31141', '41149', '51157', '61165', - '71173', '81181', '91189', '11197', '21115', '31113', '41121', '51129', '61137', '71145', - '81153', '91161', '11169', '21177', '31185', '41193', '51211', '61211', '71217', '81225', - '91233', '11241', '21249', '31257', '41265', '51273', '61281', '71289', '81297', '91315', - '11313', '21321', '31329', '41337', '51345', '61353', '71361', '81369', '91377', '11385', - '21393', '31411', '41419', '51417', '61425', '71433', '81441', '91449', '11457', '21465', - '31473', '41481', '51489', '61497', '71515', '81513', '91521', '15625', '25121', '35129', - '45137', '55145', '65153', '75161', '85169', '95177', '15185', '25193', '35211', '45219', - '55217', '65225', '75233', '85241', '95249', '15257', '25265', '35273', '45281', '55289', - '65297', '75315', '85313', '95321', '15329', '25337', '35345', '45353', '55361', '65369', - '75377', '85385', '95393', '15411', '25419', '35417', '45425', '55433', '65441', '75449', - '85457', '95465', '15473', '25481', '35489', '45497', '55515', '65513', '75521', '85529', - '95537', '15545', '25553', '35561', '45569', '55577', '65585', '75593', '85611', '95619', - '15617', '29721', '39217', '49225', '59233', '69241', '79249', '89257', '99265', '19273', - '29281', '39289', '49297', '59315', '69313', '79321', '89329', '99337', '19345', '29353', - '39361', '49369', '59377', '69385', '79393', '89411', '99419', '19417', '29425', '39433', - '49441', '59449', '69457', '79465', '89473', '99481', '19489', '29497', '39515', '49513', - '59521', '69529', '79537', '89545', '99553', '19561', '29569', '39577', '49585', '59593', - '69611', '79619', '89617', '99625', '19633', '29641', '39649', '49657', '59665', '69673', - '79681', '89689', '99697', '19715', '29713', '33817', '43313', '53321', '63329', '73337', - '83345', '93353', '13361', '23369', '33377', '43385', '53393', '63411', '73419', '83417', - '93425', '13433', '23441', '33449', '43457', '53465', '63473', '73481', '83489', '93497', - '13515', '23513', '33521', '43529', '53537', '63545', '73553', '83561', '93569', '13577', - '23585', '33593', '43611', '53619', '63617', '73625', '83633', '93641', '13649', '23657', - '33665', '43673', '53681', '63689', '73697', '83715', '93713', '13721', '23729', '33737', - '43745', '53753', '63761', '73769', '83777', '93785', '13793', '23811', '33819', '47913', - '57419', '67417', '77425', '87433', '97441', '17449', '27457', '37465', '47473', '57481', - '67489', '77497', '87515', '97513', '17521', '27529', '37537', '47545', '57553', '67561', - '77569', '87577', '97585', '17593', '27611', '37619', '47617', '57625', '67633', '77641', - '87649', '97657', '17665', '27673', '37681', '47689', '57697', '67715', '77713', '87721', - '97729', '17737', '27745', '37753', '47761', '57769', '68777', '77785', '87793', '97811', - '17819', '27817', '37825', '47833', '57841', '67849', '77857', '87865', '97873', '17881', - '27889', '37897', '47915', '52119', '61515', '71513', '81521', '91529', '11537', '21545', - '31553', '41561', '51569', '61577', '71585', '81593', '91611', '11619', '21617', '31625', - '41633', '51641', '61649', '71657', '81665', '91673', '11681', '21689', '31697', '41715', - '51713', '61721', '71729', '81737', '91745', '11753', '21761', '31769', '41777', '51785', - '61793', '71811', '81819', '91817', '11825', '21833', '31841', '41849', '51857', '61865', - '71873', '81881', '91889', '11897', '21915', '31913', '41921', '51929', '61937', '71945', - '81953', '91961', '11969', '21977', '31985', '41993', '52111', '66115', '75611', '85619', - '95617', '15625', '25633', '35641', '45649', '55657', '65665', '75673', '85681', '95689', - '15697', '25715', '35713', '45721', '55729', '65737', '75745', '85753', '95761', '15769', - '25777', '35785', '45793', '55811', '65819', '75817', '85825', '95833', '15841', '25849', - '35857', '45865', '55873', '65881', '75889', '85897', '95915', '15913', '25921', '35929', - '45937', '55945', '65953', '75961', '85969', '95977', '15985', '25993', '36111', '46119', - '56117', '66125', '76133', '86141', '96149', '16157', '26165', '36173', '46181', '56189', - '66197', '71211', '89697', '99715', '19713', '29721', '39729', '49737', '59745', '69753', - '79761', '89769', '99777', '19785', '29793', '39811', '49819', '59817', '69825', '79833', - '89841', '99849', '19857', '29865', '39873', '49881', '59889', '69897', '79915', '89913', - '99921', '19929', '29937', '39945', '49953', '59961', '69969', '79977', '89985', '19993', - '12111', '22119', '31117', '41125', '51133', '61141', '71149', '81157', '91165', '11173', - '21181', '31189', '41197', '51115', '61113', '71121', '81129', '91137', '11145', '21153', - '31161', '41169', '51177', '61185', '71193', '81529', '91126', '11134', '21142', '31151', - '41158', '51166', '61174', '71182', '82191', '91198', '21116', '21114', '31122', '41131', - '51138', '61146', '71154', '81162', '91171', '11178', '21186', '31194', '41212', '51211', - '61218', '71226', '81234', '91242', '11251', '21258', '31266', '41274', '51282', '61291', - '71298', '81316', '91314', '11322', '21331', '31338', '41346', '51354', '61362', '71371', - '81378', '91386', '11394', '21412', '31411', '41418', '51426', '61434', '71442', '81451', - '91458', '11466', '21474', '31482', '41491', '51498', '61516', '71514', '81522', '95626', - '15122', '25131', '35138', '45146', '55154', '65162', '75171', '85178', '95186', '15194', - '25212', '35211', '45218', '55226', '65234', '75242', '85251', '95258', '15266', '25274', - '35282', '45291', '55298', '65316', '75314', '85322', '95331', '15338', '25346', '35354', - '45362', '55371', '65378', '75386', '85394', '95412', '15411', '25418', '35426', '45434', - '55442', '65451', '75458', '85466', '95474', '15482', '25491', '35498', '45516', '55514', - '65522', '75531', '85538', '95546', '15554', '25562', '35571', '45578', '55586', '65594', - '75612', '85611', '95618', '19722', '29218', '39226', '49234', '59242', '69251', '79258', - '89266', '99274', '19282', '29291', '39298', '49316', '59314', '69322', '79331', '89338', - '99346', '19354', '29362', '39371', '49378', '59386', '69394', '79412', '89411', '99418', - '19426', '29434', '39442', '49451', '59458', '69466', '79474', '89482', '99491', '19498', - '29516', '39514', '49522', '59531', '69538', '79546', '89554', '99562', '19571', '29578', - '39586', '49594', '59612', '69611', '79618', '89626', '99634', '19642', '29651', '39658', - '49666', '59674', '69682', '79691', '89698', '99716', '19714', '23818', '33314', '43322', - '53331', '63338', '73346', '83354', '93362', '13371', '23378', '33386', '43394', '53412', - '63411', '73418', '83426', '93434', '13442', '23451', '33458', '43466', '53474', '63482', - '73491', '83498', '93516', '13514', '23522', '33531', '43538', '53546', '63554', '73562', - '83571', '93578', '13586', '23594', '33612', '43611', '53618', '63626', '73634', '83642', - '93651', '13658', '23666', '33674', '43682', '53691', '63698', '73716', '83714', '93722', - '13731', '23738', '33746', '43754', '53762', '63771', '73778', '83786', '93794', '13812', - '23811', '37914', '47411', '57418', '67426', '77434', '87442', '97451', '17458', '27466', - '37474', '47482', '57491', '67498', '77516', '87514', '97522', '17531', '27538', '37546', - '47554', '57562', '67571', '77578', '87586', '97594', '17612', '27611', '37618', '47626', - '57634', '67642', '77651', '87658', '97666', '17674', '27682', '37691', '47698', '57716', - '67714', '77722', '87731', '97738', '17746', '27754', '37762', '47771', '57778', '67786', - '77794', '87812', '97811', '17818', '27826', '37834', '47842', '57851', '67858', '77866', - '87874', '97882', '17891', '27898', '37916', '42111', '51516', '61514', '71522', '81531', - '91538', '11546', '21554', '31562', '41571', '51578', '61586', '71594', '81612', '91611', - '11618', '21626', '31634', '41642', '51651', '61658', '71666', '81674', '91682', '11691', - '21698', '31716', '41714', '51722', '61731', '71738', '81746', '91754', '11762', '21771', - '31778', '41786', '51794', '61812', '71811', '81818', '91826', '11834', '21842', '31851', - '41858', '51866', '61874', '71882', '81891', '91898', '11916', '21914', '31922', '41931', - '51938', '61946', '71954', '81962', '91971', '11978', '21986', '31994', '42912', '56116', - '65612', '75611', '85618', '95626', '15634', '25642', '35651', '45658', '55666', '65674', - '75682', '85691', '95698', '15716', '25714', '35722', '45731', '55738', '65746', '75754', - '85762', '95771', '15778', '25786', '35794', '45812', '55811', '65818', '75826', '85834', - '95842', '15851', '25858', '35866', '45874', '55882', '65891', '75898', '85916', '95914', - '15922', '25931', '35938', '45946', '55954', '65962', '75971', '85978', '95986', '15994', - '26112', '36111', '46118', '56126', '66134', '76142', '86151', '96158', '16166', '26174', - '36182', '46191', '56198', '61212', '79698', '89716', '99714', '19722', '29731', '39738', - '49746', '59754', '69762', '79771', '89778', '99786', '19794', '29812', '39811', '49818', - '59826', '69834', '79842', '89851', '99858', '19866', '29874', '39882', '49891', '59898', - '69916', '79914', '89922', '99931', '19938', '29946', '39954', '49962', '59971', '69978', - '79986', '89994', '91112', '12111', '21118', '31126', '41134', '51142', '61151', '71158', - '81166', '91174', '11182', '21191', '31198', '41116', '51114', '61122', '71131', '81138', - '91146', '11154', '21162', '31171', '41178', '51186', '61194', '71212', '81127', '91135', - '11143', '21151', '31159', '41167', '51175', '61183', '71191', '81199', '91117', '21115', - '21123', '31131', '41139', '51147', '61155', '71163', '81171', '91179', '11187', '21195', - '31213', '41211', '51219', '61227', '71235', '81243', '91251', '11259', '21267', '31275', - '41283', '51291', '61299', '71317', '81315', '91323', '11331', '21339', '31347', '41355', - '51363', '61371', '71379', '81387', '91395', '11413', '21411', '31419', '41427', '51435', - '61443', '71451', '81459', '91467', '11475', '21483', '31491', '41499', '51517', '61515', - '71523', '85627', '95123', '15131', '25139', '35147', '45155', '55163', '65171', '75179', - '85187', '95115', '15213', '25211', '35219', '45227', '55235', '65243', '75251', '85259', - '95267', '15275', '25283', '35291', '45299', '55317', '65315', '75323', '85331', '95339', - '15347', '25355', '35363', '45371', '55379', '65387', '75395', '85413', '95411', '15419', - '25427', '35435', '45443', '55451', '65459', '75467', '85475', '95483', '15491', '25499', - '35517', '45515', '55523', '65531', '75539', '85547', '96555', '15563', '25571', '35579', - '45587', '55595', '65613', '75611', '85619', '99723', '19219', '29227', '39235', '49243', - '59251', '69259', '79267', '89275', '99283', '19291', '29299', '39317', '49315', '59323', - '69331', '79339', '89347', '99355', '19363', '29371', '39379', '49387', '59395', '69413', - '79411', '89419', '99427', '19435', '29443', '39451', '49459', '59467', '69475', '79483', - '89491', '99499', '19517', '29515', '39523', '49531', '59539', '69547', '79555', '89563', - '99571', '19579', '29587', '39595', '49613', '59611', '69619', '79627', '89635', '99643', - '19651', '29659', '39667', '49675', '59683', '69691', '79699', '89717', '99715', '13819', - '23315', '33323', '43331', '53339', '63347', '73355', '83363', '93371', '13379', '23387', - '33395', '43413', '53411', '63419', '73427', '83435', '93443', '13451', '23459', '33467', - '43475', '53483', '63491', '73499', '83517', '93515', '13523', '23531', '33539', '43547', - '53555', '63563', '73571', '83579', '93587', '13595', '23613', '33611', '43619', '53627', - '63635', '73643', '83651', '93659', '13667', '23675', '33683', '43691', '53699', '63717', - '73715', '83723', '93731', '13739', '23747', '33755', '43763', '53771', '63779', '73787', - '83795', '93813', '13811', '27915', '37411', '47419', '57427', '67435', '77443', '87451', - '97459', '17467', '27475', '37483', '47491', '57499', '67517', '77515', '87523', '97531' - ); public /// - /// Get description for this calculator + /// Get description. /// function GetDescription: string; override; /// - /// Radio Code input validator + /// Validate. /// - /// - /// The serial number or any other needed input to calculate the - /// radio code. - /// - /// /// - /// A error message (Optional) that descibes why the input is invalid. - /// function Validate(const Input: string; var ErrorMessage: string): Boolean; override; /// - /// Radio Code Calculator + /// Calculate. /// - /// - /// The serial number or any other needed input to calculate the - /// radio code. - /// - /// - /// The calculated radio code. - /// - /// - /// A error message (Optional) that descibes why the input is invalid. - /// function Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; override; end; implementation -uses System.StrUtils; +uses + System.Classes, System.JSON, + OBD.Catalog.Path; + +const + CatalogFileName = 'radiocode-becker5.json'; + TableSize = 10000; + +var + GDatabase: array[0..TableSize - 1] of string; + GLoaded: Boolean = False; + +//------------------------------------------------------------------------------ +// LOAD CATALOG +//------------------------------------------------------------------------------ +procedure LoadCatalog; +var + Path, Raw: string; + Stream: TStringStream; + Doc: TJSONValue; + Arr: TJSONArray; + I: Integer; +begin + Path := ResolveCatalogPath(CatalogFileName); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('codes'); + if (Arr = nil) or (Arr.Count <> TableSize) then Exit; + for I := 0 to TableSize - 1 do + GDatabase[I] := Arr.Items[I].Value; + GLoaded := True; + finally + // Free the document + Doc.Free; + end; +end; //------------------------------------------------------------------------------ // GET DESCRIPTION @@ -1087,17 +119,10 @@ function TOBDRadioCodeBecker5.Validate(const Input: string; var ErrorMessage: st Result := True; // Clear the error message ErrorMessage := ''; - - // Sanitize input (remove whitespace, convert to uppercase) + // Sanitize the input Sanitized := SanitizeInput(Input); - - // Validate length using helper method - if not ValidateLength(Sanitized, 4, ErrorMessage) then - Exit(False); - - // Validate that all characters are digits using helper method - if not ValidateDigits(Sanitized, ErrorMessage) then - Exit(False); + if not ValidateLength(Sanitized, 4, ErrorMessage) then Exit(False); + if not ValidateDigits(Sanitized, ErrorMessage) then Exit(False); end; //------------------------------------------------------------------------------ @@ -1114,18 +139,20 @@ function TOBDRadioCodeBecker5.Calculate(const Input: string; var Output: string; Output := ''; // Clear the error message ErrorMessage := ''; - - // Sanitize input + // Sanitize the input Sanitized := SanitizeInput(Input); - - // Check if the input is valid if not Self.Validate(Sanitized, ErrorMessage) then Exit(False); - - // Convert the serial to a index + if not GLoaded then + begin + ErrorMessage := 'Becker5 code catalog not loaded; expected ' + + 'catalogs/' + CatalogFileName; + Exit(False); + end; I := StrToInt(Sanitized); - - // Format the code for the output - Output := Database[I]; + Output := GDatabase[I]; end; +initialization + LoadCatalog; + end. diff --git a/src/RadioCode/OBD.RadioCode.Blaupunkt.Advanced.pas b/src/RadioCode/OBD.RadioCode.Blaupunkt.Advanced.pas index 77e6d7f2..42868123 100644 --- a/src/RadioCode/OBD.RadioCode.Blaupunkt.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Blaupunkt.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeBlaupunktAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeBlaupunktAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeBlaupunktAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeBlaupunktAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeBlaupunktAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeBlaupunktAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeBlaupunktAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -146,6 +158,9 @@ function TOBDRadioCodeBlaupunktAdvanced.CalculateV1(const Serial: string): strin Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeBlaupunktAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -162,6 +177,9 @@ function TOBDRadioCodeBlaupunktAdvanced.CalculateV2(const Serial: string): strin Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeBlaupunktAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -178,11 +196,17 @@ function TOBDRadioCodeBlaupunktAdvanced.CalculateV3(const Serial: string): strin Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeBlaupunktAdvanced.GetDescription: string; begin Result := 'Advanced Blaupunkt Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeBlaupunktAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -194,6 +218,9 @@ procedure TOBDRadioCodeBlaupunktAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeBlaupunktAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -203,16 +230,25 @@ procedure TOBDRadioCodeBlaupunktAdvanced.SetVariant(const Region: TRadioCodeRegi FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeBlaupunktAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeBlaupunktAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeBlaupunktAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -235,6 +271,9 @@ function TOBDRadioCodeBlaupunktAdvanced.Validate(const Input: string; var ErrorM end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeBlaupunktAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Chrysler.Advanced.pas b/src/RadioCode/OBD.RadioCode.Chrysler.Advanced.pas index 58355044..9851391a 100644 --- a/src/RadioCode/OBD.RadioCode.Chrysler.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Chrysler.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeChryslerAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeChryslerAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeChryslerAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeChryslerAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeChryslerAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeChryslerAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeChryslerAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -147,6 +159,9 @@ function TOBDRadioCodeChryslerAdvanced.CalculateV1(const Serial: string): string Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeChryslerAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -163,6 +178,9 @@ function TOBDRadioCodeChryslerAdvanced.CalculateV2(const Serial: string): string Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeChryslerAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -179,11 +197,17 @@ function TOBDRadioCodeChryslerAdvanced.CalculateV3(const Serial: string): string Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeChryslerAdvanced.GetDescription: string; begin Result := 'Advanced Chrysler Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeChryslerAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -195,6 +219,9 @@ procedure TOBDRadioCodeChryslerAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeChryslerAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -204,16 +231,25 @@ procedure TOBDRadioCodeChryslerAdvanced.SetVariant(const Region: TRadioCodeRegio FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeChryslerAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeChryslerAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeChryslerAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -230,6 +266,9 @@ function TOBDRadioCodeChryslerAdvanced.Validate(const Input: string; var ErrorMe Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeChryslerAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Citroen.Advanced.pas b/src/RadioCode/OBD.RadioCode.Citroen.Advanced.pas index dfb84f98..6c458da7 100644 --- a/src/RadioCode/OBD.RadioCode.Citroen.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Citroen.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeCitroenAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeCitroenAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeCitroenAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeCitroenAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeCitroenAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeCitroenAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeCitroenAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -147,6 +159,9 @@ function TOBDRadioCodeCitroenAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeCitroenAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -163,6 +178,9 @@ function TOBDRadioCodeCitroenAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeCitroenAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -179,11 +197,17 @@ function TOBDRadioCodeCitroenAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeCitroenAdvanced.GetDescription: string; begin Result := 'Advanced Citroën Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeCitroenAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -195,6 +219,9 @@ procedure TOBDRadioCodeCitroenAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeCitroenAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -204,16 +231,25 @@ procedure TOBDRadioCodeCitroenAdvanced.SetVariant(const Region: TRadioCodeRegion FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeCitroenAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeCitroenAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeCitroenAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -229,6 +265,9 @@ function TOBDRadioCodeCitroenAdvanced.Validate(const Input: string; var ErrorMes Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeCitroenAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Clarion.Advanced.pas b/src/RadioCode/OBD.RadioCode.Clarion.Advanced.pas index 16626916..5b49d27b 100644 --- a/src/RadioCode/OBD.RadioCode.Clarion.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Clarion.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeClarionAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeClarionAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeClarionAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeClarionAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeClarionAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeClarionAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeClarionAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -144,6 +156,9 @@ function TOBDRadioCodeClarionAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeClarionAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -160,6 +175,9 @@ function TOBDRadioCodeClarionAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeClarionAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -176,11 +194,17 @@ function TOBDRadioCodeClarionAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeClarionAdvanced.GetDescription: string; begin Result := 'Advanced Clarion Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeClarionAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -192,6 +216,9 @@ procedure TOBDRadioCodeClarionAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeClarionAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -201,16 +228,25 @@ procedure TOBDRadioCodeClarionAdvanced.SetVariant(const Region: TRadioCodeRegion FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeClarionAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeClarionAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeClarionAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -226,6 +262,9 @@ function TOBDRadioCodeClarionAdvanced.Validate(const Input: string; var ErrorMes Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeClarionAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Fiat.Daiichi.Advanced.pas b/src/RadioCode/OBD.RadioCode.Fiat.Daiichi.Advanced.pas index 9fda00ba..6330ed8a 100644 --- a/src/RadioCode/OBD.RadioCode.Fiat.Daiichi.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Fiat.Daiichi.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeFiatDaiichiAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeFiatDaiichiAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeFiatDaiichiAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeFiatDaiichiAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeFiatDaiichiAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeFiatDaiichiAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatDaiichiAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -160,6 +172,9 @@ function TOBDRadioCodeFiatDaiichiAdvanced.CalculateV1(const Serial: string): str Output := Format('%d%d%d%d', [SNArr[0], SNArr[1], SNArr[2], SNArr[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatDaiichiAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -176,6 +191,9 @@ function TOBDRadioCodeFiatDaiichiAdvanced.CalculateV2(const Serial: string): str Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatDaiichiAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -192,11 +210,17 @@ function TOBDRadioCodeFiatDaiichiAdvanced.CalculateV3(const Serial: string): str Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatDaiichiAdvanced.GetDescription: string; begin Result := 'Advanced Fiat Daiichi Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeFiatDaiichiAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -208,6 +232,9 @@ procedure TOBDRadioCodeFiatDaiichiAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeFiatDaiichiAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -217,16 +244,25 @@ procedure TOBDRadioCodeFiatDaiichiAdvanced.SetVariant(const Region: TRadioCodeRe FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatDaiichiAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatDaiichiAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatDaiichiAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -248,6 +284,9 @@ function TOBDRadioCodeFiatDaiichiAdvanced.Validate(const Input: string; var Erro Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatDaiichiAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Fiat.VP.Advanced.pas b/src/RadioCode/OBD.RadioCode.Fiat.VP.Advanced.pas index 181d9d56..505bd934 100644 --- a/src/RadioCode/OBD.RadioCode.Fiat.VP.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Fiat.VP.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeFiatVPAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeFiatVPAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeFiatVPAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeFiatVPAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeFiatVPAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,7 +130,14 @@ procedure TOBDRadioCodeFiatVPAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatVPAdvanced.CalculateV1(const Serial: string): string; + +//------------------------------------------------------------------------------ +// GET FOURTH BYTE +//------------------------------------------------------------------------------ function GetFourthByte(Input: Integer): Integer; begin if (Input > 10) then Result := 0 else @@ -212,6 +228,9 @@ function GetFourthByte(Input: Integer): Integer; Output := Format('%.*d', [4, OutputCode]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatVPAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -228,6 +247,9 @@ function TOBDRadioCodeFiatVPAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatVPAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -244,11 +266,17 @@ function TOBDRadioCodeFiatVPAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatVPAdvanced.GetDescription: string; begin Result := 'Advanced Fiat VP Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeFiatVPAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -260,6 +288,9 @@ procedure TOBDRadioCodeFiatVPAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeFiatVPAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -269,16 +300,25 @@ procedure TOBDRadioCodeFiatVPAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatVPAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatVPAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatVPAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -300,6 +340,9 @@ function TOBDRadioCodeFiatVPAdvanced.Validate(const Input: string; var ErrorMess Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeFiatVPAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Ford.Advanced.pas b/src/RadioCode/OBD.RadioCode.Ford.Advanced.pas index c16ba705..7c788f53 100644 --- a/src/RadioCode/OBD.RadioCode.Ford.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Ford.Advanced.pas @@ -279,6 +279,9 @@ function TOBDRadioCodeFordAdvanced.CalculateRegionalEU(const Serial: string): st Result := Format('%.4d', [Code]); end; +//------------------------------------------------------------------------------ +// CALCULATE REGIONAL NA +//------------------------------------------------------------------------------ function TOBDRadioCodeFordAdvanced.CalculateRegionalNA(const Serial: string): string; var Code: Integer; @@ -308,6 +311,9 @@ function TOBDRadioCodeFordAdvanced.CalculateRegionalNA(const Serial: string): st Result := Format('%.4d', [Code]); end; +//------------------------------------------------------------------------------ +// CALCULATE REGIONAL AU +//------------------------------------------------------------------------------ function TOBDRadioCodeFordAdvanced.CalculateRegionalAU(const Serial: string): string; var Code: Integer; @@ -351,6 +357,9 @@ procedure TOBDRadioCodeFordAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeFordAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; diff --git a/src/RadioCode/OBD.RadioCode.GM.Advanced.pas b/src/RadioCode/OBD.RadioCode.GM.Advanced.pas index d702d9ee..f0644645 100644 --- a/src/RadioCode/OBD.RadioCode.GM.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.GM.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeGMAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeGMAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeGMAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeGMAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeGMAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeGMAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeGMAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -147,6 +159,9 @@ function TOBDRadioCodeGMAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeGMAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -163,6 +178,9 @@ function TOBDRadioCodeGMAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeGMAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -179,11 +197,17 @@ function TOBDRadioCodeGMAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeGMAdvanced.GetDescription: string; begin Result := 'Advanced GM/General Motors Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeGMAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -195,6 +219,9 @@ procedure TOBDRadioCodeGMAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeGMAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -204,16 +231,25 @@ procedure TOBDRadioCodeGMAdvanced.SetVariant(const Region: TRadioCodeRegion; con FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeGMAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeGMAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeGMAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -229,6 +265,9 @@ function TOBDRadioCodeGMAdvanced.Validate(const Input: string; var ErrorMessage: Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeGMAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Honda.Advanced.pas b/src/RadioCode/OBD.RadioCode.Honda.Advanced.pas index 72a0e8c5..dbe7576e 100644 --- a/src/RadioCode/OBD.RadioCode.Honda.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Honda.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeHondaAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeHondaAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeHondaAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeHondaAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeHondaAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -174,6 +183,9 @@ procedure TOBDRadioCodeHondaAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Australian market based on Japanese algorithm'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeHondaAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -205,6 +217,9 @@ function TOBDRadioCodeHondaAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeHondaAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -221,6 +236,9 @@ function TOBDRadioCodeHondaAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeHondaAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -237,6 +255,9 @@ function TOBDRadioCodeHondaAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE ALPINE +//------------------------------------------------------------------------------ function TOBDRadioCodeHondaRegional.CalculateAlpine(const Serial: string): string; var Code: Integer; @@ -264,6 +285,9 @@ function TOBDRadioCodeHondaRegional.CalculateAlpine(const Serial: string): strin Result := Format('%.4d', [Code]); end; +//------------------------------------------------------------------------------ +// CALCULATE PANASONIC +//------------------------------------------------------------------------------ function TOBDRadioCodeHondaRegional.CalculatePanasonic(const Serial: string): string; var Code: Integer; @@ -293,6 +317,9 @@ function TOBDRadioCodeHondaRegional.CalculatePanasonic(const Serial: string): st Result := Format('%.4d', [Code]); end; +//------------------------------------------------------------------------------ +// CALCULATE CLARION +//------------------------------------------------------------------------------ function TOBDRadioCodeHondaRegional.CalculateClarion(const Serial: string): string; var Code: Integer; @@ -318,11 +345,17 @@ function TOBDRadioCodeHondaRegional.CalculateClarion(const Serial: string): stri Result := Format('%.4d', [Code]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeHondaAdvanced.GetDescription: string; begin Result := 'Advanced Honda Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeHondaAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -334,6 +367,9 @@ procedure TOBDRadioCodeHondaAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeHondaAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -343,16 +379,25 @@ procedure TOBDRadioCodeHondaAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeHondaAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeHondaAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeHondaAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -384,6 +429,9 @@ function TOBDRadioCodeHondaAdvanced.Validate(const Input: string; var ErrorMessa end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeHondaAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Hyundai.Advanced.pas b/src/RadioCode/OBD.RadioCode.Hyundai.Advanced.pas index 24e07459..c23c8843 100644 --- a/src/RadioCode/OBD.RadioCode.Hyundai.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Hyundai.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeHyundaiAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeHyundaiAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeHyundaiAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeHyundaiAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeHyundaiAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeHyundaiAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeHyundaiAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -147,6 +159,9 @@ function TOBDRadioCodeHyundaiAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeHyundaiAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -163,6 +178,9 @@ function TOBDRadioCodeHyundaiAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeHyundaiAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -179,11 +197,17 @@ function TOBDRadioCodeHyundaiAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeHyundaiAdvanced.GetDescription: string; begin Result := 'Advanced Hyundai Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeHyundaiAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -195,6 +219,9 @@ procedure TOBDRadioCodeHyundaiAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeHyundaiAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -204,16 +231,25 @@ procedure TOBDRadioCodeHyundaiAdvanced.SetVariant(const Region: TRadioCodeRegion FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeHyundaiAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeHyundaiAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeHyundaiAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -229,6 +265,9 @@ function TOBDRadioCodeHyundaiAdvanced.Validate(const Input: string; var ErrorMes Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeHyundaiAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Infiniti.Advanced.pas b/src/RadioCode/OBD.RadioCode.Infiniti.Advanced.pas index d5f3d800..9a2a2cd8 100644 --- a/src/RadioCode/OBD.RadioCode.Infiniti.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Infiniti.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeInfinitiAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeInfinitiAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeInfinitiAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeInfinitiAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeInfinitiAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeInfinitiAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeInfinitiAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -154,6 +166,9 @@ function TOBDRadioCodeInfinitiAdvanced.CalculateV1(const Serial: string): string Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeInfinitiAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -170,6 +185,9 @@ function TOBDRadioCodeInfinitiAdvanced.CalculateV2(const Serial: string): string Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeInfinitiAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -186,11 +204,17 @@ function TOBDRadioCodeInfinitiAdvanced.CalculateV3(const Serial: string): string Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeInfinitiAdvanced.GetDescription: string; begin Result := 'Advanced Infiniti Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeInfinitiAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -202,6 +226,9 @@ procedure TOBDRadioCodeInfinitiAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeInfinitiAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -211,16 +238,25 @@ procedure TOBDRadioCodeInfinitiAdvanced.SetVariant(const Region: TRadioCodeRegio FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeInfinitiAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeInfinitiAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeInfinitiAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -238,6 +274,9 @@ function TOBDRadioCodeInfinitiAdvanced.Validate(const Input: string; var ErrorMe Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeInfinitiAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Jaguar.Advanced.pas b/src/RadioCode/OBD.RadioCode.Jaguar.Advanced.pas index 6203aec2..30cb03ee 100644 --- a/src/RadioCode/OBD.RadioCode.Jaguar.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Jaguar.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeJaguarAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeJaguarAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeJaguarAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeJaguarAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeJaguarAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeJaguarAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeJaguarAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -159,6 +171,9 @@ function TOBDRadioCodeJaguarAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeJaguarAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -175,6 +190,9 @@ function TOBDRadioCodeJaguarAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeJaguarAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -191,11 +209,17 @@ function TOBDRadioCodeJaguarAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeJaguarAdvanced.GetDescription: string; begin Result := 'Advanced Jaguar Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeJaguarAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -207,6 +231,9 @@ procedure TOBDRadioCodeJaguarAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeJaguarAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -216,16 +243,25 @@ procedure TOBDRadioCodeJaguarAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeJaguarAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeJaguarAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeJaguarAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -257,6 +293,9 @@ function TOBDRadioCodeJaguarAdvanced.Validate(const Input: string; var ErrorMess end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeJaguarAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.LandRover.Advanced.pas b/src/RadioCode/OBD.RadioCode.LandRover.Advanced.pas index e61ae4a3..6152013d 100644 --- a/src/RadioCode/OBD.RadioCode.LandRover.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.LandRover.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeLandRoverAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeLandRoverAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeLandRoverAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeLandRoverAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeLandRoverAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeLandRoverAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeLandRoverAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -159,6 +171,9 @@ function TOBDRadioCodeLandRoverAdvanced.CalculateV1(const Serial: string): strin Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeLandRoverAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -175,6 +190,9 @@ function TOBDRadioCodeLandRoverAdvanced.CalculateV2(const Serial: string): strin Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeLandRoverAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -191,11 +209,17 @@ function TOBDRadioCodeLandRoverAdvanced.CalculateV3(const Serial: string): strin Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeLandRoverAdvanced.GetDescription: string; begin Result := 'Advanced Land Rover Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeLandRoverAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -207,6 +231,9 @@ procedure TOBDRadioCodeLandRoverAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeLandRoverAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -216,16 +243,25 @@ procedure TOBDRadioCodeLandRoverAdvanced.SetVariant(const Region: TRadioCodeRegi FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeLandRoverAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeLandRoverAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeLandRoverAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -257,6 +293,9 @@ function TOBDRadioCodeLandRoverAdvanced.Validate(const Input: string; var ErrorM end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeLandRoverAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Lexus.Advanced.pas b/src/RadioCode/OBD.RadioCode.Lexus.Advanced.pas index 530fd724..cbf50bf2 100644 --- a/src/RadioCode/OBD.RadioCode.Lexus.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Lexus.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeLexusAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeLexusAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeLexusAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeLexusAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeLexusAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeLexusAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeLexusAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -155,6 +167,9 @@ function TOBDRadioCodeLexusAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeLexusAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -171,6 +186,9 @@ function TOBDRadioCodeLexusAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeLexusAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -187,11 +205,17 @@ function TOBDRadioCodeLexusAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeLexusAdvanced.GetDescription: string; begin Result := 'Advanced Lexus Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeLexusAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -203,6 +227,9 @@ procedure TOBDRadioCodeLexusAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeLexusAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -212,16 +239,25 @@ procedure TOBDRadioCodeLexusAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeLexusAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeLexusAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeLexusAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -239,6 +275,9 @@ function TOBDRadioCodeLexusAdvanced.Validate(const Input: string; var ErrorMessa Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeLexusAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Maserati.Advanced.pas b/src/RadioCode/OBD.RadioCode.Maserati.Advanced.pas index 74c641e3..6844f049 100644 --- a/src/RadioCode/OBD.RadioCode.Maserati.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Maserati.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeMaseratiAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeMaseratiAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeMaseratiAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeMaseratiAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMaseratiAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeMaseratiAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeMaseratiAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -159,6 +171,9 @@ function TOBDRadioCodeMaseratiAdvanced.CalculateV1(const Serial: string): string Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeMaseratiAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -175,6 +190,9 @@ function TOBDRadioCodeMaseratiAdvanced.CalculateV2(const Serial: string): string Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeMaseratiAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -191,11 +209,17 @@ function TOBDRadioCodeMaseratiAdvanced.CalculateV3(const Serial: string): string Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeMaseratiAdvanced.GetDescription: string; begin Result := 'Advanced Maserati Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMaseratiAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -207,6 +231,9 @@ procedure TOBDRadioCodeMaseratiAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMaseratiAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -216,16 +243,25 @@ procedure TOBDRadioCodeMaseratiAdvanced.SetVariant(const Region: TRadioCodeRegio FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeMaseratiAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeMaseratiAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeMaseratiAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -257,6 +293,9 @@ function TOBDRadioCodeMaseratiAdvanced.Validate(const Input: string; var ErrorMe end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeMaseratiAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Mazda.Advanced.pas b/src/RadioCode/OBD.RadioCode.Mazda.Advanced.pas index ae8c758d..58597aa4 100644 --- a/src/RadioCode/OBD.RadioCode.Mazda.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Mazda.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeMazdaAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeMazdaAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeMazdaAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeMazdaAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMazdaAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeMazdaAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeMazdaAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -144,6 +156,9 @@ function TOBDRadioCodeMazdaAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeMazdaAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -160,6 +175,9 @@ function TOBDRadioCodeMazdaAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeMazdaAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -176,11 +194,17 @@ function TOBDRadioCodeMazdaAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeMazdaAdvanced.GetDescription: string; begin Result := 'Advanced Mazda Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMazdaAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -192,6 +216,9 @@ procedure TOBDRadioCodeMazdaAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMazdaAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -201,16 +228,25 @@ procedure TOBDRadioCodeMazdaAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeMazdaAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeMazdaAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeMazdaAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -226,6 +262,9 @@ function TOBDRadioCodeMazdaAdvanced.Validate(const Input: string; var ErrorMessa Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeMazdaAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Mercedes.Advanced.pas b/src/RadioCode/OBD.RadioCode.Mercedes.Advanced.pas index 86548cd6..bb258031 100644 --- a/src/RadioCode/OBD.RadioCode.Mercedes.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Mercedes.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeMercedesAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeMercedesAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeMercedesAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeMercedesAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMercedesAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeMercedesAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeMercedesAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -157,6 +169,9 @@ function TOBDRadioCodeMercedesAdvanced.CalculateV1(const Serial: string): string Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeMercedesAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -173,6 +188,9 @@ function TOBDRadioCodeMercedesAdvanced.CalculateV2(const Serial: string): string Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeMercedesAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -189,11 +207,17 @@ function TOBDRadioCodeMercedesAdvanced.CalculateV3(const Serial: string): string Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeMercedesAdvanced.GetDescription: string; begin Result := 'Advanced Mercedes-Benz Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMercedesAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -205,6 +229,9 @@ procedure TOBDRadioCodeMercedesAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMercedesAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -214,16 +241,25 @@ procedure TOBDRadioCodeMercedesAdvanced.SetVariant(const Region: TRadioCodeRegio FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeMercedesAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeMercedesAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeMercedesAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -255,6 +291,9 @@ function TOBDRadioCodeMercedesAdvanced.Validate(const Input: string; var ErrorMe end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeMercedesAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Mini.Advanced.pas b/src/RadioCode/OBD.RadioCode.Mini.Advanced.pas index 31a2b3f6..d6f2ba62 100644 --- a/src/RadioCode/OBD.RadioCode.Mini.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Mini.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeMiniAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeMiniAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeMiniAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeMiniAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMiniAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeMiniAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeMiniAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -158,6 +170,9 @@ function TOBDRadioCodeMiniAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d%d', [Code[0], Code[1], Code[2], Code[3], Code[4]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeMiniAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -174,6 +189,9 @@ function TOBDRadioCodeMiniAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeMiniAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -190,11 +208,17 @@ function TOBDRadioCodeMiniAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeMiniAdvanced.GetDescription: string; begin Result := 'Advanced Mini Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMiniAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -206,6 +230,9 @@ procedure TOBDRadioCodeMiniAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMiniAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -215,16 +242,25 @@ procedure TOBDRadioCodeMiniAdvanced.SetVariant(const Region: TRadioCodeRegion; c FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeMiniAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeMiniAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeMiniAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -242,6 +278,9 @@ function TOBDRadioCodeMiniAdvanced.Validate(const Input: string; var ErrorMessag Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeMiniAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Mitsubishi.Advanced.pas b/src/RadioCode/OBD.RadioCode.Mitsubishi.Advanced.pas index 30cf1fda..794ae25c 100644 --- a/src/RadioCode/OBD.RadioCode.Mitsubishi.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Mitsubishi.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeMitsubishiAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeMitsubishiAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeMitsubishiAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeMitsubishiAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMitsubishiAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeMitsubishiAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeMitsubishiAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -147,6 +159,9 @@ function TOBDRadioCodeMitsubishiAdvanced.CalculateV1(const Serial: string): stri Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeMitsubishiAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -163,6 +178,9 @@ function TOBDRadioCodeMitsubishiAdvanced.CalculateV2(const Serial: string): stri Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeMitsubishiAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -179,11 +197,17 @@ function TOBDRadioCodeMitsubishiAdvanced.CalculateV3(const Serial: string): stri Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeMitsubishiAdvanced.GetDescription: string; begin Result := 'Advanced Mitsubishi Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMitsubishiAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -195,6 +219,9 @@ procedure TOBDRadioCodeMitsubishiAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeMitsubishiAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -204,16 +231,25 @@ procedure TOBDRadioCodeMitsubishiAdvanced.SetVariant(const Region: TRadioCodeReg FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeMitsubishiAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeMitsubishiAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeMitsubishiAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -229,6 +265,9 @@ function TOBDRadioCodeMitsubishiAdvanced.Validate(const Input: string; var Error Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeMitsubishiAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Nissan.Advanced.pas b/src/RadioCode/OBD.RadioCode.Nissan.Advanced.pas index b7b2b394..39e0fdee 100644 --- a/src/RadioCode/OBD.RadioCode.Nissan.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Nissan.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeNissanAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeNissanAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeNissanAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeNissanAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeNissanAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeNissanAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeNissanAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -150,6 +162,9 @@ function TOBDRadioCodeNissanAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeNissanAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -166,6 +181,9 @@ function TOBDRadioCodeNissanAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeNissanAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -182,11 +200,17 @@ function TOBDRadioCodeNissanAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeNissanAdvanced.GetDescription: string; begin Result := 'Advanced Nissan Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeNissanAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -198,6 +222,9 @@ procedure TOBDRadioCodeNissanAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeNissanAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -207,16 +234,25 @@ procedure TOBDRadioCodeNissanAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeNissanAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeNissanAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeNissanAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -234,6 +270,9 @@ function TOBDRadioCodeNissanAdvanced.Validate(const Input: string; var ErrorMess Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeNissanAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Opel.Advanced.pas b/src/RadioCode/OBD.RadioCode.Opel.Advanced.pas index 304446e7..307021eb 100644 --- a/src/RadioCode/OBD.RadioCode.Opel.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Opel.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeOpelAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeOpelAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeOpelAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeOpelAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeOpelAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeOpelAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeOpelAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -148,6 +160,9 @@ function TOBDRadioCodeOpelAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeOpelAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -164,6 +179,9 @@ function TOBDRadioCodeOpelAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeOpelAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -180,11 +198,17 @@ function TOBDRadioCodeOpelAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeOpelAdvanced.GetDescription: string; begin Result := 'Advanced Opel Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeOpelAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -196,6 +220,9 @@ procedure TOBDRadioCodeOpelAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeOpelAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -205,16 +232,25 @@ procedure TOBDRadioCodeOpelAdvanced.SetVariant(const Region: TRadioCodeRegion; c FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeOpelAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeOpelAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeOpelAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -232,6 +268,9 @@ function TOBDRadioCodeOpelAdvanced.Validate(const Input: string; var ErrorMessag Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeOpelAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Pending.pas b/src/RadioCode/OBD.RadioCode.Pending.pas new file mode 100644 index 00000000..b279d777 --- /dev/null +++ b/src/RadioCode/OBD.RadioCode.Pending.pas @@ -0,0 +1,185 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.RadioCode.Pending.pas +// CONTENTS : Data-pending stubs for radio-code brands not yet operational +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.RadioCode.Pending; + +interface + +uses + System.SysUtils, System.Classes, System.JSON, + + OBD.RadioCode, OBD.RadioCode.Registry, OBD.RadioCode.Variants, + OBD.Catalog.Path; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + /// + /// Common base for data-pending calculator stubs. Validate + /// returns False with a clear message; Calculate raises + /// EOBDRadioCodeDataMissing. + /// + TOBDRadioCodePending = class(TOBDRadioCode) + private + FBrandKey: string; + FDisplayName: string; + FDataNotes: string; + public + /// + /// Create. + /// + constructor Create(const BrandKey, DisplayName, DataNotes: string); + /// + /// Get description. + /// + function GetDescription: string; override; + /// + /// Validate. + /// + function Validate(const Input: string; var ErrorMessage: string): Boolean; override; + /// + /// Calculate. + /// + function Calculate(const Input: string; var Output: string; + var ErrorMessage: string): Boolean; override; + end; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +{ TOBDRadioCodePending } + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDRadioCodePending.Create(const BrandKey, DisplayName, + DataNotes: string); +begin + // Initialize the inherited class + inherited Create; + FBrandKey := BrandKey; + FDisplayName := DisplayName; + FDataNotes := DataNotes; +end; + +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ +function TOBDRadioCodePending.GetDescription: string; +begin + Result := Format( + '%s radio-code calculator (DATA-PENDING — algorithm or database not available in this build). %s', + [FDisplayName, FDataNotes]); +end; + +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ +function TOBDRadioCodePending.Validate(const Input: string; + var + ErrorMessage: string): Boolean; +begin + // Initialize result + Result := False; + ErrorMessage := Format( + '%s calculator is not yet operational. %s', + [FDisplayName, FDataNotes]); +end; + +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ +function TOBDRadioCodePending.Calculate(const Input: string; + var Output: string; var ErrorMessage: string): Boolean; +begin + // Clear the output + Output := ''; + ErrorMessage := Format( + '%s calculator is data-pending: %s', + [FDisplayName, FDataNotes]); + raise EOBDRadioCodeDataMissing.Create(ErrorMessage); +end; + +//------------------------------------------------------------------------------ +// REGISTRATION (catalog-driven) +//------------------------------------------------------------------------------ +function MakePendingFactory(const Key, Name, Notes: string): TOBDRadioCodeFactory; +begin + Result := function: IOBDRadioCode + begin + // Create Result + Result := TOBDRadioCodePending.Create(Key, Name, Notes); + end; +end; + +//------------------------------------------------------------------------------ +// LOAD PENDING BRANDS +//------------------------------------------------------------------------------ +procedure LoadPendingBrands; +var + Path, Raw, K, N, Notes: string; + Stream: TStringStream; + Doc: TJSONValue; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; +begin + // Resolve catalog path + Path := ResolveCatalogPath('radiocode-pending-brands.json'); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + K := Obj.GetValue('brand_key', ''); + if K = '' then Continue; + N := Obj.GetValue('display_name', ''); + Notes := Obj.GetValue('data_notes', ''); + TOBDRadioCodeRegistry.Instance.Register( + TOBDRadioCodeBrand.Create(K, N, False, Notes, + MakePendingFactory(K, N, Notes))); + end; + finally + // Free the document + Doc.Free; + end; +end; + +initialization + LoadPendingBrands; + +end. diff --git a/src/RadioCode/OBD.RadioCode.Peugeot.Advanced.pas b/src/RadioCode/OBD.RadioCode.Peugeot.Advanced.pas index cffd1f4f..efdcaf7c 100644 --- a/src/RadioCode/OBD.RadioCode.Peugeot.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Peugeot.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodePeugeotAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodePeugeotAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodePeugeotAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodePeugeotAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodePeugeotAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodePeugeotAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodePeugeotAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -162,6 +174,9 @@ function TOBDRadioCodePeugeotAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [SNArr[0], SNArr[1], SNArr[2], SNArr[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodePeugeotAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -178,6 +193,9 @@ function TOBDRadioCodePeugeotAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodePeugeotAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -194,11 +212,17 @@ function TOBDRadioCodePeugeotAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodePeugeotAdvanced.GetDescription: string; begin Result := 'Advanced Peugeot Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodePeugeotAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -210,6 +234,9 @@ procedure TOBDRadioCodePeugeotAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodePeugeotAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -219,16 +246,25 @@ procedure TOBDRadioCodePeugeotAdvanced.SetVariant(const Region: TRadioCodeRegion FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodePeugeotAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodePeugeotAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodePeugeotAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -250,6 +286,9 @@ function TOBDRadioCodePeugeotAdvanced.Validate(const Input: string; var ErrorMes Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodePeugeotAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Porsche.Advanced.pas b/src/RadioCode/OBD.RadioCode.Porsche.Advanced.pas index ca65cdb0..5d45adb6 100644 --- a/src/RadioCode/OBD.RadioCode.Porsche.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Porsche.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodePorscheAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodePorscheAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodePorscheAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodePorscheAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodePorscheAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodePorscheAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodePorscheAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -159,6 +171,9 @@ function TOBDRadioCodePorscheAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodePorscheAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -175,6 +190,9 @@ function TOBDRadioCodePorscheAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodePorscheAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -191,11 +209,17 @@ function TOBDRadioCodePorscheAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodePorscheAdvanced.GetDescription: string; begin Result := 'Advanced Porsche Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodePorscheAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -207,6 +231,9 @@ procedure TOBDRadioCodePorscheAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodePorscheAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -216,16 +243,25 @@ procedure TOBDRadioCodePorscheAdvanced.SetVariant(const Region: TRadioCodeRegion FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodePorscheAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodePorscheAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodePorscheAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -257,6 +293,9 @@ function TOBDRadioCodePorscheAdvanced.Validate(const Input: string; var ErrorMes end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodePorscheAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Registry.pas b/src/RadioCode/OBD.RadioCode.Registry.pas new file mode 100644 index 00000000..f9cf0335 --- /dev/null +++ b/src/RadioCode/OBD.RadioCode.Registry.pas @@ -0,0 +1,311 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.RadioCode.Registry.pas +// CONTENTS : Brand registry for radio-code calculators +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.RadioCode.Registry; + +interface + +uses + System.SysUtils, System.Classes, System.Generics.Collections, + System.SyncObjs, + + OBD.RadioCode, OBD.RadioCode.Variants; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + /// + /// Raised when a calculator is registered but its underlying + /// algorithm/database is not available in this build. + /// + EOBDRadioCodeDataMissing = class(Exception); + + TOBDRadioCodeFactory = reference to function: IOBDRadioCode; + + /// + /// One brand entry in the registry. + /// + TOBDRadioCodeBrand = class + private + FBrandKey: string; + FDisplayName: string; + FDataAvailable: Boolean; + FDataNotes: string; + FFactory: TOBDRadioCodeFactory; + FVariants: TRadioCodeVariantManager; + public + /// + /// Create. + /// + constructor Create(const BrandKey, DisplayName: string; + DataAvailable: Boolean; const DataNotes: string; + const Factory: TOBDRadioCodeFactory); + /// + /// Destroy. + /// + destructor Destroy; override; + + /// + /// Lower-case brand identifier (e.g. 'pioneer', 'philips'). + /// + property BrandKey: string read FBrandKey; + /// + /// Human-readable name shown in UIs. + /// + property DisplayName: string read FDisplayName; + /// + /// True when a real algorithm/database backs the calculator. + /// False indicates a data-pending stub that will raise on Calculate. + /// + property DataAvailable: Boolean read FDataAvailable; + /// + /// For data-pending brands, describes what reference data + /// would unblock the calculator. + /// + property DataNotes: string read FDataNotes; + /// + /// Variant manager for region/year/security-version dispatch. + /// + property Variants: TRadioCodeVariantManager read FVariants; + + /// + /// Create calculator. + /// + function CreateCalculator: IOBDRadioCode; + end; + + /// + /// Process-wide registry. Thread-safe; brands register at init. + /// + TOBDRadioCodeRegistry = class + private + class var FInstance: TOBDRadioCodeRegistry; + FLock: TCriticalSection; + FBrands: TObjectList; + FByKey: TDictionary; + public + /// + /// Create. + /// + constructor Create; + /// + /// Destroy. + /// + destructor Destroy; override; + + /// + /// Instance. + /// + class function Instance: TOBDRadioCodeRegistry; + /// + /// Free instance. + /// + class procedure FreeInstance; reintroduce; + + /// + /// Register. + /// + procedure Register(Brand: TOBDRadioCodeBrand); + /// + /// Find. + /// + function Find(const BrandKey: string): TOBDRadioCodeBrand; + /// + /// Get brand keys. + /// + procedure GetBrandKeys(Keys: TStrings); + /// + /// Count. + /// + function Count: Integer; + end; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +{ TOBDRadioCodeBrand } + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDRadioCodeBrand.Create(const BrandKey, DisplayName: string; + DataAvailable: Boolean; const DataNotes: string; + const Factory: TOBDRadioCodeFactory); +begin + // Initialize the inherited class + inherited Create; + FBrandKey := LowerCase(BrandKey); + FDisplayName := DisplayName; + FDataAvailable := DataAvailable; + FDataNotes := DataNotes; + FFactory := Factory; + // Create FVariants + FVariants := TRadioCodeVariantManager.Create(DisplayName); +end; + +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ +destructor TOBDRadioCodeBrand.Destroy; +begin + // Free FVariants + FVariants.Free; + // Call the inherited handler + inherited; +end; + +//------------------------------------------------------------------------------ +// CREATE CALCULATOR +//------------------------------------------------------------------------------ +function TOBDRadioCodeBrand.CreateCalculator: IOBDRadioCode; +begin + if not Assigned(FFactory) then + raise EOBDRadioCodeDataMissing.CreateFmt( + 'No factory registered for brand %s', [FBrandKey]); + Result := FFactory(); +end; + +{ TOBDRadioCodeRegistry } + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDRadioCodeRegistry.Create; +begin + // Call the inherited handler + inherited; + // Create FLock + FLock := TCriticalSection.Create; + // Create FBrands + FBrands := TObjectList.Create(True); + // Create FByKey + FByKey := TDictionary.Create; +end; + +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ +destructor TOBDRadioCodeRegistry.Destroy; +begin + // Free FByKey + FByKey.Free; + // Free FBrands + FBrands.Free; + // Free FLock + FLock.Free; + // Call the inherited handler + inherited; +end; + +//------------------------------------------------------------------------------ +// INSTANCE +//------------------------------------------------------------------------------ +class function TOBDRadioCodeRegistry.Instance: TOBDRadioCodeRegistry; +begin + if FInstance = nil then + // Create FInstance + FInstance := TOBDRadioCodeRegistry.Create; + Result := FInstance; +end; + +//------------------------------------------------------------------------------ +// FREE INSTANCE +//------------------------------------------------------------------------------ +class procedure TOBDRadioCodeRegistry.FreeInstance; +begin + // Free FInstance + FreeAndNil(FInstance); +end; + +//------------------------------------------------------------------------------ +// REGISTER +//------------------------------------------------------------------------------ +procedure TOBDRadioCodeRegistry.Register(Brand: TOBDRadioCodeBrand); +begin + if Brand = nil then Exit; + // Acquire the lock + FLock.Acquire; + try + if FByKey.ContainsKey(Brand.BrandKey) then + begin + // Free Brand + Brand.Free; + Exit; + end; + FBrands.Add(Brand); + FByKey.Add(Brand.BrandKey, Brand); + finally + // Release the lock + FLock.Release; + end; +end; + +//------------------------------------------------------------------------------ +// FIND +//------------------------------------------------------------------------------ +function TOBDRadioCodeRegistry.Find(const BrandKey: string): TOBDRadioCodeBrand; +begin + // Acquire the lock + FLock.Acquire; + try + if not FByKey.TryGetValue(LowerCase(BrandKey), Result) then + // Initialize result + Result := nil; + finally + // Release the lock + FLock.Release; + end; +end; + +//------------------------------------------------------------------------------ +// GET BRAND KEYS +//------------------------------------------------------------------------------ +procedure TOBDRadioCodeRegistry.GetBrandKeys(Keys: TStrings); +var + Brand: TOBDRadioCodeBrand; +begin + Keys.Clear; + // Acquire the lock + FLock.Acquire; + try + // Loop over FBrands + for Brand in FBrands do + Keys.Add(Brand.BrandKey); + finally + // Release the lock + FLock.Release; + end; +end; + +//------------------------------------------------------------------------------ +// COUNT +//------------------------------------------------------------------------------ +function TOBDRadioCodeRegistry.Count: Integer; +begin + // Acquire the lock + FLock.Acquire; + try + Result := FBrands.Count; + finally + // Release the lock + FLock.Release; + end; +end; + +initialization + +finalization + TOBDRadioCodeRegistry.FreeInstance; + +end. diff --git a/src/RadioCode/OBD.RadioCode.Renault.Advanced.pas b/src/RadioCode/OBD.RadioCode.Renault.Advanced.pas index 3019cada..63d39c1f 100644 --- a/src/RadioCode/OBD.RadioCode.Renault.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Renault.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeRenaultAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeRenaultAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeRenaultAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeRenaultAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeRenaultAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeRenaultAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeRenaultAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -153,6 +165,9 @@ function TOBDRadioCodeRenaultAdvanced.CalculateV1(const Serial: string): string; Output := Format('%.*d', [4, C]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeRenaultAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -169,6 +184,9 @@ function TOBDRadioCodeRenaultAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeRenaultAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -185,11 +203,17 @@ function TOBDRadioCodeRenaultAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeRenaultAdvanced.GetDescription: string; begin Result := 'Advanced Renault Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeRenaultAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -201,6 +225,9 @@ procedure TOBDRadioCodeRenaultAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeRenaultAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -210,16 +237,25 @@ procedure TOBDRadioCodeRenaultAdvanced.SetVariant(const Region: TRadioCodeRegion FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeRenaultAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeRenaultAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeRenaultAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -260,6 +296,9 @@ function TOBDRadioCodeRenaultAdvanced.Validate(const Input: string; var ErrorMes end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeRenaultAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.SEAT.Advanced.pas b/src/RadioCode/OBD.RadioCode.SEAT.Advanced.pas index 4072564a..cd1d4125 100644 --- a/src/RadioCode/OBD.RadioCode.SEAT.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.SEAT.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeSEATAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeSEATAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeSEATAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeSEATAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSEATAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeSEATAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeSEATAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -152,6 +164,9 @@ function TOBDRadioCodeSEATAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeSEATAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -168,6 +183,9 @@ function TOBDRadioCodeSEATAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeSEATAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -184,11 +202,17 @@ function TOBDRadioCodeSEATAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeSEATAdvanced.GetDescription: string; begin Result := 'Advanced SEAT Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSEATAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -200,6 +224,9 @@ procedure TOBDRadioCodeSEATAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSEATAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -209,16 +236,25 @@ procedure TOBDRadioCodeSEATAdvanced.SetVariant(const Region: TRadioCodeRegion; c FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeSEATAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeSEATAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeSEATAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -252,6 +288,9 @@ function TOBDRadioCodeSEATAdvanced.Validate(const Input: string; var ErrorMessag end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeSEATAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Saab.Advanced.pas b/src/RadioCode/OBD.RadioCode.Saab.Advanced.pas index 54459d1f..241ea86d 100644 --- a/src/RadioCode/OBD.RadioCode.Saab.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Saab.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeSaabAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeSaabAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeSaabAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeSaabAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSaabAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeSaabAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeSaabAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -159,6 +171,9 @@ function TOBDRadioCodeSaabAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeSaabAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -175,6 +190,9 @@ function TOBDRadioCodeSaabAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeSaabAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -191,11 +209,17 @@ function TOBDRadioCodeSaabAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeSaabAdvanced.GetDescription: string; begin Result := 'Advanced Saab Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSaabAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -207,6 +231,9 @@ procedure TOBDRadioCodeSaabAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSaabAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -216,16 +243,25 @@ procedure TOBDRadioCodeSaabAdvanced.SetVariant(const Region: TRadioCodeRegion; c FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeSaabAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeSaabAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeSaabAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -257,6 +293,9 @@ function TOBDRadioCodeSaabAdvanced.Validate(const Input: string; var ErrorMessag end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeSaabAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Skoda.Advanced.pas b/src/RadioCode/OBD.RadioCode.Skoda.Advanced.pas index 55192f0c..a7b7a39d 100644 --- a/src/RadioCode/OBD.RadioCode.Skoda.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Skoda.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeSkodaAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeSkodaAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeSkodaAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeSkodaAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSkodaAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeSkodaAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeSkodaAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -152,6 +164,9 @@ function TOBDRadioCodeSkodaAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeSkodaAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -168,6 +183,9 @@ function TOBDRadioCodeSkodaAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeSkodaAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -184,11 +202,17 @@ function TOBDRadioCodeSkodaAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeSkodaAdvanced.GetDescription: string; begin Result := 'Advanced Skoda Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSkodaAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -200,6 +224,9 @@ procedure TOBDRadioCodeSkodaAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSkodaAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -209,16 +236,25 @@ procedure TOBDRadioCodeSkodaAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeSkodaAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeSkodaAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeSkodaAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -251,6 +287,9 @@ function TOBDRadioCodeSkodaAdvanced.Validate(const Input: string; var ErrorMessa end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeSkodaAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Smart.Advanced.pas b/src/RadioCode/OBD.RadioCode.Smart.Advanced.pas index 21c0543a..5e89bdfc 100644 --- a/src/RadioCode/OBD.RadioCode.Smart.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Smart.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeSmartAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeSmartAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeSmartAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeSmartAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSmartAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeSmartAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeSmartAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -155,6 +167,9 @@ function TOBDRadioCodeSmartAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeSmartAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -171,6 +186,9 @@ function TOBDRadioCodeSmartAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeSmartAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -187,11 +205,17 @@ function TOBDRadioCodeSmartAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeSmartAdvanced.GetDescription: string; begin Result := 'Advanced Smart Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSmartAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -203,6 +227,9 @@ procedure TOBDRadioCodeSmartAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSmartAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -212,16 +239,25 @@ procedure TOBDRadioCodeSmartAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeSmartAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeSmartAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeSmartAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -246,6 +282,9 @@ function TOBDRadioCodeSmartAdvanced.Validate(const Input: string; var ErrorMessa end; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeSmartAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Subaru.Advanced.pas b/src/RadioCode/OBD.RadioCode.Subaru.Advanced.pas index 691e4ee3..724fb83b 100644 --- a/src/RadioCode/OBD.RadioCode.Subaru.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Subaru.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeSubaruAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeSubaruAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeSubaruAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeSubaruAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSubaruAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeSubaruAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeSubaruAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -144,6 +156,9 @@ function TOBDRadioCodeSubaruAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeSubaruAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -160,6 +175,9 @@ function TOBDRadioCodeSubaruAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeSubaruAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -176,11 +194,17 @@ function TOBDRadioCodeSubaruAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeSubaruAdvanced.GetDescription: string; begin Result := 'Advanced Subaru Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSubaruAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -192,6 +216,9 @@ procedure TOBDRadioCodeSubaruAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSubaruAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -201,16 +228,25 @@ procedure TOBDRadioCodeSubaruAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeSubaruAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeSubaruAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeSubaruAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -226,6 +262,9 @@ function TOBDRadioCodeSubaruAdvanced.Validate(const Input: string; var ErrorMess Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeSubaruAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Suzuki.Advanced.pas b/src/RadioCode/OBD.RadioCode.Suzuki.Advanced.pas index 351e33da..8d440ed8 100644 --- a/src/RadioCode/OBD.RadioCode.Suzuki.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Suzuki.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeSuzukiAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeSuzukiAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeSuzukiAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeSuzukiAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSuzukiAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeSuzukiAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeSuzukiAdvanced.CalculateV1(const Serial: string): string; var D1, D2, D3, D4: Integer; @@ -139,6 +151,9 @@ function TOBDRadioCodeSuzukiAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeSuzukiAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -155,6 +170,9 @@ function TOBDRadioCodeSuzukiAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeSuzukiAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -171,11 +189,17 @@ function TOBDRadioCodeSuzukiAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeSuzukiAdvanced.GetDescription: string; begin Result := 'Advanced Suzuki Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSuzukiAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -187,6 +211,9 @@ procedure TOBDRadioCodeSuzukiAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeSuzukiAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -196,16 +223,25 @@ procedure TOBDRadioCodeSuzukiAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeSuzukiAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeSuzukiAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeSuzukiAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -221,6 +257,9 @@ function TOBDRadioCodeSuzukiAdvanced.Validate(const Input: string; var ErrorMess Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeSuzukiAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Toyota.Advanced.pas b/src/RadioCode/OBD.RadioCode.Toyota.Advanced.pas index 1e6543dc..e077b4ee 100644 --- a/src/RadioCode/OBD.RadioCode.Toyota.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Toyota.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeToyotaAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeToyotaAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeToyotaAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeToyotaAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeToyotaAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -176,6 +185,9 @@ procedure TOBDRadioCodeToyotaAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Australian market using Japanese algorithm base'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeToyotaAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -200,6 +212,9 @@ function TOBDRadioCodeToyotaAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeToyotaAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -216,6 +231,9 @@ function TOBDRadioCodeToyotaAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeToyotaAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -232,6 +250,9 @@ function TOBDRadioCodeToyotaAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE FUJITSU TEN +//------------------------------------------------------------------------------ function TOBDRadioCodeToyotaRegional.CalculateFujitsuTen(const Serial: string): string; var Code: Integer; @@ -252,6 +273,9 @@ function TOBDRadioCodeToyotaRegional.CalculateFujitsuTen(const Serial: string): Result := Format('%.5d', [Code]); end; +//------------------------------------------------------------------------------ +// CALCULATE PANASONIC +//------------------------------------------------------------------------------ function TOBDRadioCodeToyotaRegional.CalculatePanasonic(const Serial: string): string; var Code: Integer; @@ -276,6 +300,9 @@ function TOBDRadioCodeToyotaRegional.CalculatePanasonic(const Serial: string): s Result := Format('%.5d', [Code]); end; +//------------------------------------------------------------------------------ +// CALCULATE DENSO +//------------------------------------------------------------------------------ function TOBDRadioCodeToyotaRegional.CalculateDenso(const Serial: string): string; var Code: Integer; @@ -300,6 +327,9 @@ function TOBDRadioCodeToyotaRegional.CalculateDenso(const Serial: string): strin Result := Format('%.5d', [Code]); end; +//------------------------------------------------------------------------------ +// CALCULATE JBL +//------------------------------------------------------------------------------ function TOBDRadioCodeToyotaRegional.CalculateJBL(const Serial: string): string; var Code: Integer; @@ -321,11 +351,17 @@ function TOBDRadioCodeToyotaRegional.CalculateJBL(const Serial: string): string; Result := Format('%.5d', [Code]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeToyotaAdvanced.GetDescription: string; begin Result := 'Advanced Toyota Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeToyotaAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -337,6 +373,9 @@ procedure TOBDRadioCodeToyotaAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeToyotaAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -346,16 +385,25 @@ procedure TOBDRadioCodeToyotaAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeToyotaAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeToyotaAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeToyotaAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -373,6 +421,9 @@ function TOBDRadioCodeToyotaAdvanced.Validate(const Input: string; var ErrorMess Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeToyotaAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.VW.Advanced.pas b/src/RadioCode/OBD.RadioCode.VW.Advanced.pas index ec4911ea..0699fe51 100644 --- a/src/RadioCode/OBD.RadioCode.VW.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.VW.Advanced.pas @@ -44,6 +44,9 @@ TOBDRadioCodeVWAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeVWAdvanced.Create; begin inherited Create; @@ -52,12 +55,18 @@ constructor TOBDRadioCodeVWAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeVWAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeVWAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -190,6 +199,9 @@ procedure TOBDRadioCodeVWAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Chinese market variant with region lock'; end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeVWAdvanced.GetDescription: string; begin if FCurrentVariant <> nil then @@ -198,6 +210,9 @@ function TOBDRadioCodeVWAdvanced.GetDescription: string; Result := 'VW Radio Code Calculator (Regional Variants)'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeVWAdvanced.SetVariant(const VariantID: string); begin FCurrentVariant := FVariantManager.FindVariant(VariantID); @@ -205,6 +220,9 @@ procedure TOBDRadioCodeVWAdvanced.SetVariant(const VariantID: string); FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeVWAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); begin @@ -213,16 +231,25 @@ procedure TOBDRadioCodeVWAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeVWAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeVWAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// CALCULATE GAMMA +//------------------------------------------------------------------------------ function TOBDRadioCodeVWAdvanced.CalculateGamma(const Serial: string): string; var Code: Integer; @@ -251,6 +278,9 @@ function TOBDRadioCodeVWAdvanced.CalculateGamma(const Serial: string): string; Result := Format('%.4d', [Code]); end; +//------------------------------------------------------------------------------ +// CALCULATE BETA +//------------------------------------------------------------------------------ function TOBDRadioCodeVWAdvanced.CalculateBeta(const Serial: string): string; var Code: Integer; @@ -278,6 +308,9 @@ function TOBDRadioCodeVWAdvanced.CalculateBeta(const Serial: string): string; Result := Format('%.4d', [Code]); end; +//------------------------------------------------------------------------------ +// CALCULATE ALPHA +//------------------------------------------------------------------------------ function TOBDRadioCodeVWAdvanced.CalculateAlpha(const Serial: string): string; var Code: Integer; @@ -309,6 +342,9 @@ function TOBDRadioCodeVWAdvanced.CalculateAlpha(const Serial: string): string; Result := Format('%.4d', [Code]); end; +//------------------------------------------------------------------------------ +// CALCULATE RCD +//------------------------------------------------------------------------------ function TOBDRadioCodeVWAdvanced.CalculateRCD(const Serial: string): string; var Code: Integer; @@ -336,6 +372,9 @@ function TOBDRadioCodeVWAdvanced.CalculateRCD(const Serial: string): string; Result := Format('%.4d', [Code]); end; +//------------------------------------------------------------------------------ +// CALCULATE RNS +//------------------------------------------------------------------------------ function TOBDRadioCodeVWAdvanced.CalculateRNS(const Serial: string): string; var Code: Integer; @@ -363,8 +402,12 @@ function TOBDRadioCodeVWAdvanced.CalculateRNS(const Serial: string): string; Result := Format('%.4d', [Code]); end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeVWAdvanced.Validate(const Input: string; - var ErrorMessage: string): Boolean; + var + ErrorMessage: string): Boolean; var Sanitized: string; begin @@ -382,6 +425,9 @@ function TOBDRadioCodeVWAdvanced.Validate(const Input: string; Result := True; end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeVWAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var diff --git a/src/RadioCode/OBD.RadioCode.Variants.pas b/src/RadioCode/OBD.RadioCode.Variants.pas index 6f9f3af4..e876bb89 100644 --- a/src/RadioCode/OBD.RadioCode.Variants.pas +++ b/src/RadioCode/OBD.RadioCode.Variants.pas @@ -153,13 +153,15 @@ TRadioCodeVariantManager = class /// Get list of all variants for a specific region /// procedure GetVariantsByRegion(const Region: TRadioCodeRegion; - var Variants: TList); + var + Variants: TList); /// /// Get list of all variants for a specific year /// procedure GetVariantsByYear(const ModelYear: Integer; - var Variants: TList); + var + Variants: TList); /// /// Brand name this manager handles @@ -400,7 +402,8 @@ procedure TRadioCodeVariantManager.GetVariantsByRegion( // TRADIOCODEVARIANTMANAGER - GET VARIANTS BY YEAR //------------------------------------------------------------------------------ procedure TRadioCodeVariantManager.GetVariantsByYear(const ModelYear: Integer; - var Variants: TList); + var + Variants: TList); var I: Integer; begin diff --git a/src/RadioCode/OBD.RadioCode.VinResolver.pas b/src/RadioCode/OBD.RadioCode.VinResolver.pas new file mode 100644 index 00000000..f11d593a --- /dev/null +++ b/src/RadioCode/OBD.RadioCode.VinResolver.pas @@ -0,0 +1,307 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.RadioCode.VinResolver.pas +// CONTENTS : VIN-aware radio-code calculator resolver +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.RadioCode.VinResolver; + +interface + +uses + System.SysUtils, + + OBD.RadioCode, OBD.RadioCode.Registry, OBD.RadioCode.Variants, + OBD.VIN.Decoder, OBD.VIN.Types; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + /// + /// Optional metadata supplied alongside the VIN. Any field + /// left blank is filled from the VIN itself or from the brand's + /// default variant. + /// + TRadioCodeResolveContext = record + /// + /// Brand key. + /// + BrandKey: string; + /// + /// Vin. + /// + VIN: string; + ModelYearOverride: Integer; // 0 = use ModelYear from VIN + ModelHint: string; // optional radio-model name + RegionOverride: TRadioCodeRegion; // rcrUnknown = derive from VIN + end; + + /// + /// Outcome of a resolution attempt. + /// + TRadioCodeResolveResult = record + /// + /// Calculator. + /// + Calculator: IOBDRadioCode; + Brand: TOBDRadioCodeBrand; // nil if not found + Variant: TRadioCodeVariant; // nil if no variant manager populated + DataAvailable: Boolean; // shortcut: Brand <> nil and Brand.DataAvailable + ResolutionNotes: string; // e.g. 'fell back to default variant' + end; + +/// +/// Map a TVINRegion.Name into the TRadioCodeRegion enum. +/// +function MapVINRegionToRadioCodeRegion(const VinRegionName: string): TRadioCodeRegion; + +/// +/// Resolve a calculator for the given brand and VIN. Returns +/// a result record that callers should inspect — Calculator may be nil +/// when the brand isn't registered, or non-nil but with a stub when +/// the brand is data-pending. +/// +function ResolveCalculator(const Ctx: TRadioCodeResolveContext): TRadioCodeResolveResult; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +uses + OBD.RadioCode.VW.Advanced, + OBD.RadioCode.Audi.Concert.Advanced, + OBD.RadioCode.Mercedes.Advanced, + OBD.RadioCode.BMW.Advanced; + +//------------------------------------------------------------------------------ +// MAP VINREGION TO RADIO CODE REGION +//------------------------------------------------------------------------------ +function MapVINRegionToRadioCodeRegion(const VinRegionName: string): TRadioCodeRegion; +var + N: string; +begin + N := LowerCase(VinRegionName); + if (N = '') then Exit(rcrUnknown); + if Pos('europe', N) > 0 then Exit(rcrEurope); + if Pos('north america', N) > 0 then Exit(rcrNorthAmerica); + if Pos('asia', N) > 0 then Exit(rcrAsia); + if Pos('oceania', N) > 0 then Exit(rcrAustralia); + if Pos('africa', N) > 0 then Exit(rcrAfrica); + if Pos('south america', N) > 0 then Exit(rcrSouthAmerica); + if Pos('middle east', N) > 0 then Exit(rcrMiddleEast); + Result := rcrUnknown; +end; + +//------------------------------------------------------------------------------ +// RESOLVE CALCULATOR +//------------------------------------------------------------------------------ +function ResolveCalculator(const Ctx: TRadioCodeResolveContext): TRadioCodeResolveResult; +var + Brand: TOBDRadioCodeBrand; + Parsed: TVINParseResult; + Region: TRadioCodeRegion; + Year: Integer; + Variant: TRadioCodeVariant; + Err: string; +begin + Result := Default(TRadioCodeResolveResult); + + Brand := TOBDRadioCodeRegistry.Instance.Find(Ctx.BrandKey); + Result.Brand := Brand; + if Brand = nil then + begin + Result.ResolutionNotes := 'brand not registered: ' + Ctx.BrandKey; + Exit; + end; + + Result.DataAvailable := Brand.DataAvailable; + Result.Calculator := Brand.CreateCalculator; + + // VIN parsing is best-effort: an invalid VIN doesn't fail the + // resolution; we just fall back to overrides + brand defaults. + Year := Ctx.ModelYearOverride; + Region := Ctx.RegionOverride; + + if (Ctx.VIN <> '') and TOBDVinDecoder.Validate(Ctx.VIN, Err) then + begin + Parsed := TOBDVinDecoder.Parse(Ctx.VIN); + if Year = 0 then + Year := Parsed.ModelYear; + if Region = rcrUnknown then + Region := MapVINRegionToRadioCodeRegion(Parsed.Region.Name); + end; + + Variant := Brand.Variants.FindBestMatch(Region, Year, Ctx.ModelHint); + if Variant = nil then + begin + Variant := Brand.Variants.GetDefaultVariant; + if Variant <> nil then + Result.ResolutionNotes := 'no exact variant match; using brand default'; + end; + Result.Variant := Variant; +end; + +//------------------------------------------------------------------------------ +// REGISTRATION OF DATA-AVAILABLE BRANDS +//------------------------------------------------------------------------------ +function MakeFactoryVW: TOBDRadioCodeFactory; +begin + Result := function: IOBDRadioCode + begin + // Create Result + Result := TOBDRadioCodeVWAdvanced.Create; + end; +end; + +//------------------------------------------------------------------------------ +// MAKE FACTORY AUDI CONCERT +//------------------------------------------------------------------------------ +function MakeFactoryAudiConcert: TOBDRadioCodeFactory; +begin + Result := function: IOBDRadioCode + begin + // Create Result + Result := TOBDRadioCodeAudiConcertAdvanced.Create; + end; +end; + +//------------------------------------------------------------------------------ +// MAKE FACTORY MERCEDES +//------------------------------------------------------------------------------ +function MakeFactoryMercedes: TOBDRadioCodeFactory; +begin + Result := function: IOBDRadioCode + begin + // Create Result + Result := TOBDRadioCodeMercedesAdvanced.Create; + end; +end; + +//------------------------------------------------------------------------------ +// MAKE FACTORY BMW +//------------------------------------------------------------------------------ +function MakeFactoryBMW: TOBDRadioCodeFactory; +begin + Result := function: IOBDRadioCode + begin + // Create Result + Result := TOBDRadioCodeBMWAdvanced.Create; + end; +end; + +//------------------------------------------------------------------------------ +// SEED VWVARIANTS +//------------------------------------------------------------------------------ +procedure SeedVWVariants(Brand: TOBDRadioCodeBrand); +begin + // Mirrors the variants TOBDRadioCodeVWAdvanced builds internally so + // the registry-side resolver can discriminate without instantiating + // the calculator. AlgorithmNotes here doc the underlying class. + with Brand.Variants do + begin + AddVariant('VW_EU_GAMMA', 'VW Europe Gamma (1995-2000)', rcrEurope, 1995, 2000, rcsvV1, True); + AddVariant('VW_EU_BETA', 'VW Europe Beta (1998-2003)', rcrEurope, 1998, 2003, rcsvV1); + AddVariant('VW_EU_ALPHA', 'VW Europe Alpha (2000-2005)', rcrEurope, 2000, 2005, rcsvV2); + AddVariant('VW_EU_RCD', 'VW Europe RCD (2003-2012)', rcrEurope, 2003, 2012, rcsvV2); + AddVariant('VW_EU_RCD_NEW','VW Europe RCD/Composition (2013+)', rcrEurope, 2013, 9999, rcsvV3); + AddVariant('VW_EU_RNS', 'VW Europe RNS Navigation (2005-2015)', rcrEurope, 2005, 2015, rcsvV2); + AddVariant('VW_EU_RNS_NEW','VW Europe Discover Media/Pro (2016+)', rcrEurope, 2016, 9999, rcsvV4); + AddVariant('VW_NA_STD', 'VW North America Standard (2000-2010)', rcrNorthAmerica, 2000, 2010, rcsvV1); + end; +end; + +//------------------------------------------------------------------------------ +// SEED AUDI VARIANTS +//------------------------------------------------------------------------------ +procedure SeedAudiVariants(Brand: TOBDRadioCodeBrand); +begin + with Brand.Variants do + begin + AddVariant('AUDI_CONCERT_1', 'Audi Concert I (pre-2003)', rcrEurope, 1995, 2003, rcsvV1, True); + AddVariant('AUDI_CONCERT_2', 'Audi Concert II/III (2003-2009)', rcrEurope, 2003, 2009, rcsvV2); + AddVariant('AUDI_SYMPHONY', 'Audi Symphony I/II', rcrEurope, 1996, 2007, rcsvV1); + AddVariant('AUDI_RNS_E', 'Audi RNS-E Navigation', rcrEurope, 2005, 2010, rcsvV2); + AddVariant('AUDI_MMI_2G', 'Audi MMI 2G (2003-2009)', rcrEurope, 2003, 2009, rcsvV2); + AddVariant('AUDI_MMI_3G', 'Audi MMI 3G/3G+ (2008-2017)', rcrEurope, 2008, 2017, rcsvV3); + AddVariant('AUDI_MIB', 'Audi MIB (2014+)', rcrEurope, 2014, 9999, rcsvV4); + end; +end; + +//------------------------------------------------------------------------------ +// SEED MERCEDES VARIANTS +//------------------------------------------------------------------------------ +procedure SeedMercedesVariants(Brand: TOBDRadioCodeBrand); +begin + with Brand.Variants do + begin + // Becker BE-series prefixes documented across multiple Mercedes + // service-info publications and community archives. + AddVariant('MB_BECKER_BE', 'Mercedes Becker BE (1990-2000)', rcrEurope, 1990, 2000, rcsvV1, True); + AddVariant('MB_BECKER_BE2', 'Mercedes Becker BE-2 (1998-2005)', rcrEurope, 1998, 2005, rcsvV2); + AddVariant('MB_AUDIO_5', 'Mercedes Audio 5/10/20 (1998-2004)', rcrEurope, 1998, 2004, rcsvV1); + AddVariant('MB_AUDIO_50_APS', 'Mercedes Audio 50 APS (2003-2014)', rcrEurope, 2003, 2014, rcsvV2); + AddVariant('MB_COMMAND_NTG2', 'Mercedes COMAND NTG2 (2002-2008)', rcrEurope, 2002, 2008, rcsvV2); + AddVariant('MB_COMMAND_NTG25', 'Mercedes COMAND NTG2.5 (2008-2014)', rcrEurope, 2008, 2014, rcsvV3); + AddVariant('MB_COMMAND_NTG5', 'Mercedes COMAND NTG5 (2014-2018)', rcrEurope, 2014, 2018, rcsvV4); + AddVariant('MB_MBUX', 'Mercedes MBUX (2018+)', rcrEurope, 2018, 9999, rcsvV5); + end; +end; + +//------------------------------------------------------------------------------ +// SEED BMWVARIANTS +//------------------------------------------------------------------------------ +procedure SeedBMWVariants(Brand: TOBDRadioCodeBrand); +begin + with Brand.Variants do + begin + AddVariant('BMW_BUSINESS', 'BMW Business radio (E-series)', rcrEurope, 1990, 2007, rcsvV1, True); + AddVariant('BMW_PROFESSIONAL','BMW Professional radio (E-series)', rcrEurope, 1995, 2007, rcsvV1); + AddVariant('BMW_DSP', 'BMW DSP amplifier (E-series)', rcrEurope, 1995, 2010, rcsvV2); + AddVariant('BMW_CCC', 'BMW CCC iDrive (E60/E90)', rcrEurope, 2003, 2010, rcsvV2); + AddVariant('BMW_CIC', 'BMW CIC iDrive (F-series transition)', rcrEurope, 2008, 2014, rcsvV3); + AddVariant('BMW_NBT', 'BMW NBT (F-series)', rcrEurope, 2012, 2017, rcsvV3); + AddVariant('BMW_NBT_EVO', 'BMW NBT EVO', rcrEurope, 2015, 2019, rcsvV4); + AddVariant('BMW_ID5_ID6', 'BMW iDrive 5/6 (G-series)', rcrEurope, 2017, 2020, rcsvV4); + AddVariant('BMW_ID7_ID8', 'BMW iDrive 7/8', rcrEurope, 2018, 9999, rcsvV5); + end; +end; + +//------------------------------------------------------------------------------ +// REGISTER DATA AVAILABLE BRANDS +//------------------------------------------------------------------------------ +procedure RegisterDataAvailableBrands; +var + VW, Audi, MB, BMW: TOBDRadioCodeBrand; +begin + // Create VW + VW := TOBDRadioCodeBrand.Create('vw', 'Volkswagen', True, '', MakeFactoryVW); + SeedVWVariants(VW); + TOBDRadioCodeRegistry.Instance.Register(VW); + + // Create Audi + Audi := TOBDRadioCodeBrand.Create('audi', 'Audi', True, '', MakeFactoryAudiConcert); + SeedAudiVariants(Audi); + TOBDRadioCodeRegistry.Instance.Register(Audi); + + // Create MB + MB := TOBDRadioCodeBrand.Create('mercedes', 'Mercedes-Benz', True, '', MakeFactoryMercedes); + SeedMercedesVariants(MB); + TOBDRadioCodeRegistry.Instance.Register(MB); + + // Create BMW + BMW := TOBDRadioCodeBrand.Create('bmw', 'BMW', True, '', MakeFactoryBMW); + SeedBMWVariants(BMW); + TOBDRadioCodeRegistry.Instance.Register(BMW); +end; + +initialization + RegisterDataAvailableBrands; + +end. diff --git a/src/RadioCode/OBD.RadioCode.Visteon.Advanced.pas b/src/RadioCode/OBD.RadioCode.Visteon.Advanced.pas index 0b1b19f7..0ad0a576 100644 --- a/src/RadioCode/OBD.RadioCode.Visteon.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Visteon.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeVisteonAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeVisteonAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeVisteonAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeVisteonAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeVisteonAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeVisteonAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeVisteonAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -144,6 +156,9 @@ function TOBDRadioCodeVisteonAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeVisteonAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -160,6 +175,9 @@ function TOBDRadioCodeVisteonAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeVisteonAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -176,11 +194,17 @@ function TOBDRadioCodeVisteonAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeVisteonAdvanced.GetDescription: string; begin Result := 'Advanced Visteon Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeVisteonAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -192,6 +216,9 @@ procedure TOBDRadioCodeVisteonAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeVisteonAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -201,16 +228,25 @@ procedure TOBDRadioCodeVisteonAdvanced.SetVariant(const Region: TRadioCodeRegion FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeVisteonAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeVisteonAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeVisteonAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -226,6 +262,9 @@ function TOBDRadioCodeVisteonAdvanced.Validate(const Input: string; var ErrorMes Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeVisteonAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/RadioCode/OBD.RadioCode.Volvo.Advanced.pas b/src/RadioCode/OBD.RadioCode.Volvo.Advanced.pas index 6a6a529f..8f53e8ef 100644 --- a/src/RadioCode/OBD.RadioCode.Volvo.Advanced.pas +++ b/src/RadioCode/OBD.RadioCode.Volvo.Advanced.pas @@ -46,6 +46,9 @@ TOBDRadioCodeVolvoAdvanced = class(TOBDRadioCode) implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRadioCodeVolvoAdvanced.Create; begin inherited Create; @@ -54,12 +57,18 @@ constructor TOBDRadioCodeVolvoAdvanced.Create; FCurrentVariant := FVariantManager.GetDefaultVariant; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRadioCodeVolvoAdvanced.Destroy; begin FVariantManager.Free; inherited Destroy; end; +//------------------------------------------------------------------------------ +// INITIALIZE VARIANTS +//------------------------------------------------------------------------------ procedure TOBDRadioCodeVolvoAdvanced.InitializeVariants; var Variant: TRadioCodeVariant; @@ -121,6 +130,9 @@ procedure TOBDRadioCodeVolvoAdvanced.InitializeVariants; Variant.AlgorithmNotes := 'Asian market variant'; end; +//------------------------------------------------------------------------------ +// CALCULATE V1 +//------------------------------------------------------------------------------ function TOBDRadioCodeVolvoAdvanced.CalculateV1(const Serial: string): string; var Sanitized: string; @@ -144,6 +156,9 @@ function TOBDRadioCodeVolvoAdvanced.CalculateV1(const Serial: string): string; Output := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V2 +//------------------------------------------------------------------------------ function TOBDRadioCodeVolvoAdvanced.CalculateV2(const Serial: string): string; var SerialNum: Integer; @@ -160,6 +175,9 @@ function TOBDRadioCodeVolvoAdvanced.CalculateV2(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// CALCULATE V3 +//------------------------------------------------------------------------------ function TOBDRadioCodeVolvoAdvanced.CalculateV3(const Serial: string): string; var SerialNum: Integer; @@ -176,11 +194,17 @@ function TOBDRadioCodeVolvoAdvanced.CalculateV3(const Serial: string): string; Result := Format('%d%d%d%d', [Code[0], Code[1], Code[2], Code[3]]); end; +//------------------------------------------------------------------------------ +// GET DESCRIPTION +//------------------------------------------------------------------------------ function TOBDRadioCodeVolvoAdvanced.GetDescription: string; begin Result := 'Advanced Volvo Radio Code Calculator with multiple algorithm variants'; end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeVolvoAdvanced.SetVariant(const VariantID: string); var Variant: TRadioCodeVariant; @@ -192,6 +216,9 @@ procedure TOBDRadioCodeVolvoAdvanced.SetVariant(const VariantID: string); raise Exception.CreateFmt('Variant "%s" not found', [VariantID]); end; +//------------------------------------------------------------------------------ +// SET VARIANT +//------------------------------------------------------------------------------ procedure TOBDRadioCodeVolvoAdvanced.SetVariant(const Region: TRadioCodeRegion; const ModelYear: Integer); var Variant: TRadioCodeVariant; @@ -201,16 +228,25 @@ procedure TOBDRadioCodeVolvoAdvanced.SetVariant(const Region: TRadioCodeRegion; FCurrentVariant := Variant; end; +//------------------------------------------------------------------------------ +// GET CURRENT VARIANT +//------------------------------------------------------------------------------ function TOBDRadioCodeVolvoAdvanced.GetCurrentVariant: TRadioCodeVariant; begin Result := FCurrentVariant; end; +//------------------------------------------------------------------------------ +// GET AVAILABLE VARIANTS +//------------------------------------------------------------------------------ function TOBDRadioCodeVolvoAdvanced.GetAvailableVariants: TRadioCodeVariantManager; begin Result := FVariantManager; end; +//------------------------------------------------------------------------------ +// VALIDATE +//------------------------------------------------------------------------------ function TOBDRadioCodeVolvoAdvanced.Validate(const Input: string; var ErrorMessage: string): Boolean; var Sanitized: string; @@ -226,6 +262,9 @@ function TOBDRadioCodeVolvoAdvanced.Validate(const Input: string; var ErrorMessa Exit(False); end; +//------------------------------------------------------------------------------ +// CALCULATE +//------------------------------------------------------------------------------ function TOBDRadioCodeVolvoAdvanced.Calculate(const Input: string; var Output: string; var ErrorMessage: string): Boolean; var Sanitized: string; diff --git a/src/Services/OBD.Catalog.Path.pas b/src/Services/OBD.Catalog.Path.pas new file mode 100644 index 00000000..b2ea9e6d --- /dev/null +++ b/src/Services/OBD.Catalog.Path.pas @@ -0,0 +1,80 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.Catalog.Path.pas +// CONTENTS : Dependency-free catalog file path resolver +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.Catalog.Path; + +interface + +uses + System.SysUtils, System.IOUtils; + +/// +/// Override the catalog search path. Pass empty to revert. +/// +procedure SetGlobalCatalogPath(const Path: string); + +/// +/// Resolve a catalog file by name. Probes (in order): +/// user override / exe-dir/catalogs/ / exe-dir/../catalogs/ / cwd/catalogs/ +/// and the four v3.77 vehicle-class subdirectories under each root. +/// Returns '' if nothing matches. +/// +function ResolveCatalogPath(const FileName: string): string; + +implementation + +var + GGlobalCatalogPath: string = ''; + +//------------------------------------------------------------------------------ +// SET GLOBAL CATALOG PATH +//------------------------------------------------------------------------------ +procedure SetGlobalCatalogPath(const Path: string); +begin + GGlobalCatalogPath := Path; +end; + +//------------------------------------------------------------------------------ +// RESOLVE CATALOG PATH +//------------------------------------------------------------------------------ +function ResolveCatalogPath(const FileName: string): string; +const + Subdirs: array[0..3] of string = + ('motorcycle', 'agricultural', 'marine', 'powersports'); +var + Roots: TArray; + Root, Sub, Candidate: string; +begin + Roots := []; + if GGlobalCatalogPath <> '' then + Roots := Roots + [GGlobalCatalogPath]; + Roots := Roots + [ + TPath.Combine(TPath.GetDirectoryName(ParamStr(0)), 'catalogs'), + TPath.Combine(TPath.Combine(TPath.GetDirectoryName(ParamStr(0)), '..'), 'catalogs'), + TPath.Combine(GetCurrentDir, 'catalogs') + ]; + // Loop over Roots + for Root in Roots do + begin + Candidate := TPath.Combine(Root, FileName); + if TFile.Exists(Candidate) then Exit(TPath.GetFullPath(Candidate)); + // Loop over Subdirs + for Sub in Subdirs do + begin + Candidate := TPath.Combine(TPath.Combine(Root, Sub), FileName); + if TFile.Exists(Candidate) then Exit(TPath.GetFullPath(Candidate)); + end; + end; + // Initialize result + Result := ''; +end; + +end. diff --git a/src/Services/OBD.DriveCycle.Advisor.pas b/src/Services/OBD.DriveCycle.Advisor.pas new file mode 100644 index 00000000..2e1b6d10 --- /dev/null +++ b/src/Services/OBD.DriveCycle.Advisor.pas @@ -0,0 +1,196 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.DriveCycle.Advisor.pas +// CONTENTS : Per-monitor drive-cycle advisor +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.DriveCycle.Advisor; + +interface + +uses + System.SysUtils, System.Generics.Collections, + + OBD.Protocol.WWHOBD.Readiness; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + TDriveCycleStep = record + /// + /// Short-name of the monitor the step targets. + /// + Monitor: string; + /// + /// One sentence the operator can act on. + /// + Description: string; + /// + /// Approx duration in seconds; 0 if not applicable. + /// + DurationSeconds: Integer; + end; + + /// + /// Per-OEM drive-cycle override hook. Implementers return + /// the per-monitor step the operator should perform; nil/empty + /// description means "use the ISO 15031-7 generic step". + /// + TDriveCycleResolver = reference to function( + const MonitorName: string; const OEMKey: string): TDriveCycleStep; + +/// +/// Build the list of drive-cycle steps from a readiness set. +/// Every Supported-but-not-Complete monitor produces one step. +/// OEMKey is optional; pass '' for the ISO 15031-7 generic cycle. +/// +function BuildDriveCycle(const Readiness: TWWHOBDReadinessSet; + const OEMKey: string = ''): TArray; + +/// +/// Register an OEM-specific resolver. Subsequent BuildDriveCycle +/// calls with that OEMKey will consult it before falling back to the +/// generic table. +/// +procedure RegisterDriveCycleResolver(const OEMKey: string; + const Resolver: TDriveCycleResolver); + +/// +/// Generic ISO 15031-7 step for a monitor name. Public so +/// custom resolvers can compose with it. +/// +function GenericStepFor(const MonitorName: string): TDriveCycleStep; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +uses + System.Classes, System.JSON, + OBD.Catalog.Path; + +var + GResolvers: TDictionary; + GGeneric: TDictionary = nil; + +//------------------------------------------------------------------------------ +// LOAD GENERIC CATALOG +//------------------------------------------------------------------------------ +procedure LoadGenericCatalog; +var + Path, Raw: string; + Stream: TStringStream; + Doc: TJSONValue; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + Step: TDriveCycleStep; +begin + // Resolve catalog path + Path := ResolveCatalogPath('drive-cycle-generic.json'); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + Step.Monitor := Obj.GetValue('monitor', ''); + if Step.Monitor = '' then Continue; + Step.Description := Obj.GetValue('description', ''); + Step.DurationSeconds := Obj.GetValue('duration_seconds', 0); + GGeneric.AddOrSetValue(Step.Monitor, Step); + end; + finally + // Free the document + Doc.Free; + end; +end; + +//------------------------------------------------------------------------------ +// GENERIC STEP FOR +//------------------------------------------------------------------------------ +function GenericStepFor(const MonitorName: string): TDriveCycleStep; +begin + if (GGeneric <> nil) and GGeneric.TryGetValue(MonitorName, Result) then Exit; + Result.Monitor := MonitorName; + Result.Description := 'Complete the OEM-specific drive cycle for this monitor.'; + Result.DurationSeconds := 0; +end; + +//------------------------------------------------------------------------------ +// BUILD DRIVE CYCLE +//------------------------------------------------------------------------------ +function BuildDriveCycle(const Readiness: TWWHOBDReadinessSet; + const OEMKey: string): TArray; +var + Pending: TArray; + M: string; + Resolver: TDriveCycleResolver; + Step: TDriveCycleStep; + HasResolver: Boolean; +begin + Pending := Readiness.PendingMonitors; + HasResolver := (OEMKey <> '') and GResolvers.TryGetValue(LowerCase(OEMKey), Resolver); + // Loop over Pending + for M in Pending do + begin + if HasResolver then + begin + Step := Resolver(M, OEMKey); + if Step.Description = '' then + Step := GenericStepFor(M); + end + else + Step := GenericStepFor(M); + Result := Result + [Step]; + end; +end; + +//------------------------------------------------------------------------------ +// REGISTER DRIVE CYCLE RESOLVER +//------------------------------------------------------------------------------ +procedure RegisterDriveCycleResolver(const OEMKey: string; + const Resolver: TDriveCycleResolver); +begin + GResolvers.AddOrSetValue(LowerCase(OEMKey), Resolver); +end; + +initialization + GResolvers := TDictionary.Create; + GGeneric := TDictionary.Create; + LoadGenericCatalog; + +finalization + GGeneric.Free; + GResolvers.Free; + +end. diff --git a/src/Services/OBD.DriveCycle.Resolvers.pas b/src/Services/OBD.DriveCycle.Resolvers.pas new file mode 100644 index 00000000..30655037 --- /dev/null +++ b/src/Services/OBD.DriveCycle.Resolvers.pas @@ -0,0 +1,180 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.DriveCycle.Resolvers.pas +// CONTENTS : Per-OEM drive-cycle resolvers (VW/BMW/MB/Ford/Toyota) +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.DriveCycle.Resolvers; + +interface + +uses + System.SysUtils, + + OBD.DriveCycle.Advisor; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// STEP +//------------------------------------------------------------------------------ +function Step(const Mon, Desc: string; Dur: Integer): TDriveCycleStep; +begin + Result.Monitor := Mon; + Result.Description := Desc; + Result.DurationSeconds := Dur; +end; + +//------------------------------------------------------------------------------ +// VW — Self-Study Program 388 + 605 (EOBD readiness drive cycle) +//------------------------------------------------------------------------------ +function ResolveVW(const Monitor, OEMKey: string): TDriveCycleStep; +begin + if Monitor = 'Catalyst' then + Result := Step(Monitor, + 'VW SSP 388: cold start. Idle 3 min in Drive (auto) or 1st (manual). ' + + 'Cruise at 65–80 km/h in closed loop for 5 min, then 90–110 km/h ' + + 'for 5 min, then decelerate without braking from 90 km/h.', 800) + else if Monitor = 'EvaporativeSystem' then + Result := Step(Monitor, + 'VW SSP 388: EVAP requires fuel level 25–75% AND ambient 4–35°C AND ' + + 'cold start (engine coolant within 6°C of intake air). Idle 4 min, ' + + 'then cruise 50–80 km/h for 10 min without sharp accelerations.', 900) + else if Monitor = 'OxygenSensor' then + Result := Step(Monitor, + 'VW SSP 605: cruise at 60 km/h in closed loop for 10 min after ' + + 'reaching coolant > 80°C. Avoid throttle transients.', 600) + else if Monitor = 'EGRorVVTSystem' then + Result := Step(Monitor, + 'VW SSP 605: cruise 80 km/h for 5 min, then decelerate to 30 km/h ' + + 'with foot off accelerator. Repeat 3×.', 1080) + else + Result := Default(TDriveCycleStep); // signals fall-through to generic +end; + +//------------------------------------------------------------------------------ +// BMW — TIS published OBD II readiness drive cycle +//------------------------------------------------------------------------------ +function ResolveBMW(const Monitor, OEMKey: string): TDriveCycleStep; +begin + if Monitor = 'Catalyst' then + Result := Step(Monitor, + 'BMW TIS: warm engine to operating temp. Cruise at 80 km/h in 4th/5th ' + + 'or D for 10 min, then a partial-throttle deceleration to 30 km/h. ' + + 'Repeat the cruise + decel pair twice.', 1500) + else if Monitor = 'EvaporativeSystem' then + Result := Step(Monitor, + 'BMW TIS: EVAP needs cold start (coolant 4–32°C, ambient 4–32°C). ' + + 'Drive normally with mixed city/highway for 20–30 min. Refuel must ' + + 'NOT be performed during the cycle.', 1800) + else if Monitor = 'SecondaryAirSystem' then + Result := Step(Monitor, + 'BMW TIS: cold start with engine coolant < 30°C. Let the secondary ' + + 'air pump complete its post-start cycle (~90–120 s).', 120) + else if Monitor = 'OxygenSensor' then + Result := Step(Monitor, + 'BMW TIS: cruise at 90 km/h in closed loop for 12 min after warm-up.', 720) + else + Result := Default(TDriveCycleStep); +end; + +//------------------------------------------------------------------------------ +// Mercedes — WIS readiness drive cycle (EOBD) +//------------------------------------------------------------------------------ +function ResolveMercedes(const Monitor, OEMKey: string): TDriveCycleStep; +begin + if Monitor = 'Catalyst' then + Result := Step(Monitor, + 'MB WIS: warm engine to operating temp (>80°C). Drive at 80–100 km/h ' + + 'in top gear for 5 min, then decelerate without braking from ' + + '90 km/h. Repeat once.', 720) + else if Monitor = 'EvaporativeSystem' then + Result := Step(Monitor, + 'MB WIS: cold start, fuel 1/4–3/4. Idle 4 min then drive 50–80 km/h ' + + 'for 12 min minimum. EVAP runs after a complete shutdown soak.', 960) + else if Monitor = 'OxygenSensor' then + Result := Step(Monitor, + 'MB WIS: cruise at constant 60–80 km/h closed loop for 10 min after ' + + 'lambda sensors reach operating temperature.', 600) + else if Monitor = 'PMFilter' then + Result := Step(Monitor, + 'MB WIS (diesel): cruise > 70 km/h for 25 min minimum to allow PM ' + + 'filter regen monitor to complete.', 1500) + else + Result := Default(TDriveCycleStep); +end; + +//------------------------------------------------------------------------------ +// Ford — TSB OBD II readiness drive cycle +//------------------------------------------------------------------------------ +function ResolveFord(const Monitor, OEMKey: string): TDriveCycleStep; +begin + if Monitor = 'Catalyst' then + Result := Step(Monitor, + 'Ford TSB: warm engine fully. Cruise at constant 70–100 km/h in OD ' + + 'for 5 min, decelerate without braking to 30 km/h, repeat the ' + + 'cycle 3×. AC and rear-defrost OFF.', 1200) + else if Monitor = 'EvaporativeSystem' then + Result := Step(Monitor, + 'Ford TSB: cold start (coolant within 6°C of ambient, both 4–32°C). ' + + 'Drive at steady 70–110 km/h for 15 min without rapid throttle ' + + 'transitions; fuel level 15–85%.', 900) + else if Monitor = 'OxygenSensor' then + Result := Step(Monitor, + 'Ford TSB: cruise at 65–95 km/h closed loop for 10 min, foot light ' + + 'on the throttle.', 600) + else if Monitor = 'EGRorVVTSystem' then + Result := Step(Monitor, + 'Ford TSB: 4 deceleration events from 90 to 30 km/h, foot off ' + + 'accelerator each time, 30 s apart.', 360) + else + Result := Default(TDriveCycleStep); +end; + +//------------------------------------------------------------------------------ +// Toyota — Repair Manual EOBD readiness drive cycle + Techstream notes +//------------------------------------------------------------------------------ +function ResolveToyota(const Monitor, OEMKey: string): TDriveCycleStep; +begin + if Monitor = 'Catalyst' then + Result := Step(Monitor, + 'Toyota RM: warm engine. Cruise at 65–80 km/h in D for 8 min, then ' + + 'decelerate without braking to 0 km/h. Repeat 2×. Catalyst monitor ' + + 'completes on the second deceleration.', 1100) + else if Monitor = 'EvaporativeSystem' then + Result := Step(Monitor, + 'Toyota RM: 8 hours soak with ignition off, fuel 1/2–3/4, ambient ' + + '4.5–35°C. Cold start, idle 5 min, drive 50–80 km/h for 20 min ' + + 'including a 10-min steady cruise.', 1500) + else if Monitor = 'HeatedCatalyst' then + Result := Step(Monitor, + 'Toyota RM: cold start; let catalyst reach light-off temperature ' + + '(~3 min of normal driving from cold).', 240) + else if Monitor = 'OxygenSensor' then + Result := Step(Monitor, + 'Toyota RM: drive at 65–95 km/h for 12 min in closed loop. Avoid ' + + 'throttle transients.', 720) + else if Monitor = 'OxygenSensorHeater' then + Result := Step(Monitor, + 'Toyota RM: cold start; oxygen sensor heaters complete within ' + + '60 s of run.', 60) + else + Result := Default(TDriveCycleStep); +end; + +initialization + RegisterDriveCycleResolver('vw', ResolveVW); + RegisterDriveCycleResolver('bmw', ResolveBMW); + RegisterDriveCycleResolver('mercedes', ResolveMercedes); + RegisterDriveCycleResolver('ford', ResolveFord); + RegisterDriveCycleResolver('toyota', ResolveToyota); + +end. diff --git a/src/Services/OBD.ECU.Flashing.Checkpoint.pas b/src/Services/OBD.ECU.Flashing.Checkpoint.pas new file mode 100644 index 00000000..1eeb668f --- /dev/null +++ b/src/Services/OBD.ECU.Flashing.Checkpoint.pas @@ -0,0 +1,287 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.ECU.Flashing.Checkpoint.pas +// CONTENTS : Resumable-flashing checkpoint sidecar +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows 7, 8/8.1, 10, 11 +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.ECU.Flashing.Checkpoint; + +interface + +uses + System.SysUtils, System.Classes, System.IOUtils, System.JSON, + System.Hash, System.DateUtils; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDFlashCheckpoint = class(Exception); + + TOBDFlashCheckpointState = record + Sha256: string; // hex of firmware SHA-256 at checkpoint create + /// + /// Block size. + /// + BlockSize: Integer; + /// + /// Total blocks. + /// + TotalBlocks: Integer; + LastCompletedBlock: Integer; // -1 = nothing completed + /// + /// Snapshot path. + /// + SnapshotPath: string; + /// + /// Updated at utc. + /// + UpdatedAtUtc: TDateTime; + end; + + TOBDFlashCheckpointVerifyResult = record + /// + /// Resumable. + /// + Resumable: Boolean; + NextBlock: Integer; // next block to write (= LastCompletedBlock + 1) + /// + /// State. + /// + State: TOBDFlashCheckpointState; + /// + /// Reason. + /// + Reason: string; + end; + + TOBDFlashCheckpoint = class + private + FSidecarPath: string; + FState: TOBDFlashCheckpointState; + /// + /// Save. + /// + procedure Save; + public + /// + /// Compute the SHA-256 hex digest of FirmwarePath. + /// Used both at create time (recorded into the sidecar) and at + /// resume time (compared against the sidecar to detect a swapped + /// firmware). + /// + class function Sha256OfFile(const FirmwarePath: string): string; + + /// + /// Create a fresh checkpoint and persist it. + /// + class function Initialise(const ASidecarPath, AFirmwarePath: string; + ABlockSize, ATotalBlocks: Integer; + const ASnapshotPath: string): TOBDFlashCheckpoint; + + /// + /// Load an existing sidecar and check it matches the firmware. + /// On mismatch Resumable is False and Reason tells you why. + /// + class function LoadAndVerify(const ASidecarPath, AFirmwarePath: string): + TOBDFlashCheckpointVerifyResult; + + /// + /// Mark a block done and persist immediately. Idempotent — + /// re-marking a block that's already <= LastCompletedBlock is a + /// no-op. + /// + procedure MarkBlockComplete(BlockIndex: Integer); + + /// + /// Delete the sidecar (call on successful flash completion). + /// + procedure Clear; + + /// + /// State. + /// + property State: TOBDFlashCheckpointState read FState; + /// + /// Sidecar path. + /// + property SidecarPath: string read FSidecarPath; + end; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// SHA256 OF FILE +//------------------------------------------------------------------------------ +class function TOBDFlashCheckpoint.Sha256OfFile(const FirmwarePath: string): string; +var + Stream: TFileStream; + Hash: THashSHA2; + Buf: TBytes; + N: Integer; +begin + Hash := THashSHA2.Create(SHA256); + // Allocate Buf + SetLength(Buf, 64 * 1024); + // Create Stream + Stream := TFileStream.Create(FirmwarePath, fmOpenRead or fmShareDenyWrite); + try + repeat + N := Stream.Read(Buf[0], Length(Buf)); + if N > 0 then Hash.Update(Buf, N); + until N = 0; + finally + // Free the stream + Stream.Free; + end; + Result := Hash.HashAsString; +end; + +//------------------------------------------------------------------------------ +// INITIALISE +//------------------------------------------------------------------------------ +class function TOBDFlashCheckpoint.Initialise( + const ASidecarPath, AFirmwarePath: string; + ABlockSize, ATotalBlocks: Integer; + const ASnapshotPath: string): TOBDFlashCheckpoint; +begin + if (ABlockSize <= 0) or (ATotalBlocks <= 0) then + raise EOBDFlashCheckpoint.Create( + 'Block size and total-block count must be positive'); + if not TFile.Exists(AFirmwarePath) then + raise EOBDFlashCheckpoint.CreateFmt( + 'Firmware not found: %s', [AFirmwarePath]); + + // Create Result + Result := TOBDFlashCheckpoint.Create; + Result.FSidecarPath := ASidecarPath; + Result.FState.Sha256 := Sha256OfFile(AFirmwarePath); + Result.FState.BlockSize := ABlockSize; + Result.FState.TotalBlocks := ATotalBlocks; + Result.FState.LastCompletedBlock := -1; + Result.FState.SnapshotPath := ASnapshotPath; + Result.FState.UpdatedAtUtc := TTimeZone.Local.ToUniversalTime(Now); + Result.Save; +end; + +//------------------------------------------------------------------------------ +// LOAD AND VERIFY +//------------------------------------------------------------------------------ +class function TOBDFlashCheckpoint.LoadAndVerify( + const ASidecarPath, AFirmwarePath: string): TOBDFlashCheckpointVerifyResult; +var + Json: TJSONObject; + Body: string; + Sha: string; + TS: string; +begin + Result := Default(TOBDFlashCheckpointVerifyResult); + + if not TFile.Exists(ASidecarPath) then + begin + Result.Reason := 'no checkpoint sidecar at ' + ASidecarPath; + Exit; + end; + if not TFile.Exists(AFirmwarePath) then + begin + Result.Reason := 'firmware missing: ' + AFirmwarePath; + Exit; + end; + + Body := TFile.ReadAllText(ASidecarPath, TEncoding.UTF8); + Json := TJSONObject.ParseJSONValue(Body) as TJSONObject; + if Json = nil then + begin + Result.Reason := 'sidecar is not valid JSON'; + Exit; + end; + try + Result.State.Sha256 := Json.GetValue('sha256', ''); + Result.State.BlockSize := Json.GetValue('block_size', 0); + Result.State.TotalBlocks := Json.GetValue('total_blocks', 0); + Result.State.LastCompletedBlock := Json.GetValue('last_completed', -1); + Result.State.SnapshotPath := Json.GetValue('snapshot', ''); + Json.TryGetValue('updated_at_utc', TS); + if TS <> '' then + Result.State.UpdatedAtUtc := ISO8601ToDate(TS, True); + finally + // Free Json + Json.Free; + end; + + Sha := Sha256OfFile(AFirmwarePath); + if SameText(Sha, Result.State.Sha256) then + begin + if Result.State.LastCompletedBlock >= Result.State.TotalBlocks - 1 then + begin + Result.Reason := 'all blocks already completed'; + Exit; + end; + Result.Resumable := True; + Result.NextBlock := Result.State.LastCompletedBlock + 1; + end + else + Result.Reason := 'firmware SHA-256 mismatch: sidecar=' + + Result.State.Sha256 + ' actual=' + Sha; +end; + +//------------------------------------------------------------------------------ +// MARK BLOCK COMPLETE +//------------------------------------------------------------------------------ +procedure TOBDFlashCheckpoint.MarkBlockComplete(BlockIndex: Integer); +begin + if BlockIndex < 0 then Exit; + if BlockIndex >= FState.TotalBlocks then + raise EOBDFlashCheckpoint.CreateFmt( + 'Block index %d out of range (total=%d)', + [BlockIndex, FState.TotalBlocks]); + if BlockIndex <= FState.LastCompletedBlock then + Exit; // idempotent + FState.LastCompletedBlock := BlockIndex; + FState.UpdatedAtUtc := TTimeZone.Local.ToUniversalTime(Now); + Save; +end; + +//------------------------------------------------------------------------------ +// CLEAR +//------------------------------------------------------------------------------ +procedure TOBDFlashCheckpoint.Clear; +begin + if TFile.Exists(FSidecarPath) then + TFile.Delete(FSidecarPath); +end; + +//------------------------------------------------------------------------------ +// SAVE +//------------------------------------------------------------------------------ +procedure TOBDFlashCheckpoint.Save; +var + Json: TJSONObject; + Body: string; +begin + // Create Json + Json := TJSONObject.Create; + try + Json.AddPair('sha256', FState.Sha256); + Json.AddPair('block_size', TJSONNumber.Create(FState.BlockSize)); + Json.AddPair('total_blocks', TJSONNumber.Create(FState.TotalBlocks)); + Json.AddPair('last_completed', TJSONNumber.Create(FState.LastCompletedBlock)); + Json.AddPair('snapshot', FState.SnapshotPath); + Json.AddPair('updated_at_utc', DateToISO8601(FState.UpdatedAtUtc, True)); + Body := Json.ToJSON; + finally + // Free Json + Json.Free; + end; + TFile.WriteAllText(FSidecarPath, Body, TEncoding.UTF8); +end; + +end. diff --git a/src/Services/OBD.ECU.Flashing.VoltageGate.pas b/src/Services/OBD.ECU.Flashing.VoltageGate.pas new file mode 100644 index 00000000..89498072 --- /dev/null +++ b/src/Services/OBD.ECU.Flashing.VoltageGate.pas @@ -0,0 +1,248 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.ECU.Flashing.VoltageGate.pas +// CONTENTS : Pre-flash battery-voltage gate +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows 7, 8/8.1, 10, 11 +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.ECU.Flashing.VoltageGate; + +interface + +uses + System.SysUtils, System.Generics.Collections; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDProgrammingVoltageTooLow = class(Exception); + EOBDProgrammingVoltageUnavailable = class(Exception); + + /// + /// Caller-supplied voltage source. Returns the current pack + /// voltage in volts; raise on hardware error. Implementations + /// typically forward to TOBDAdapter.GetVoltage. + /// + TOBDVoltageReader = reference to function: Single; + + TOBDVoltageGateConfig = record + /// + /// Minimum acceptable voltage in volts. Default 12.5 + /// (ISO 22900-2 informative annex). + /// + MinimumVolts: Single; + /// + /// Optional per-OEM threshold override. Empty key uses + /// MinimumVolts. Lookup is case-insensitive on OEM key. + /// + PerOEM: TDictionary; + end; + + TOBDVoltageGateResult = record + /// + /// Passed. + /// + Passed: Boolean; + /// + /// Measured volts. + /// + MeasuredVolts: Single; + /// + /// Required volts. + /// + RequiredVolts: Single; + OEMUsed: string; // empty if generic + /// + /// Reason. + /// + Reason: string; + end; + + TOBDProgrammingVoltageGate = class + private + FConfig: TOBDVoltageGateConfig; + /// + /// Resolve threshold. + /// + function ResolveThreshold(const OEMKey: string; + out OEMUsed: string): Single; + public + /// + /// Create. + /// + constructor Create; + /// + /// Destroy. + /// + destructor Destroy; override; + + /// + /// Set the generic minimum threshold (default 12.5 V). + /// + procedure SetMinimumVolts(V: Single); + + /// + /// Add or replace a per-OEM threshold (e.g. 'tesla-hv' + /// might require 13.0 V because the LV pack must be at the right + /// SoC for the contactor sequencer). + /// + procedure SetOEMThreshold(const OEMKey: string; V: Single); + + /// + /// Run the check. Reads the voltage via Reader and + /// compares against the resolved threshold. + /// + function Check(const Reader: TOBDVoltageReader; + const OEMKey: string = ''): TOBDVoltageGateResult; + + /// + /// Same as Check but raises EOBDProgrammingVoltageTooLow + /// on failure instead of returning a result record. + /// + procedure RequirePass(const Reader: TOBDVoltageReader; + const OEMKey: string = ''); + end; + +//------------------------------------------------------------------------------ +// CONSTANTS +//------------------------------------------------------------------------------ +const + /// + /// Conservative passenger-car minimum from ISO 22900-2 + /// informative annex. + /// + DEFAULT_PROGRAMMING_VOLTAGE_MIN: Single = 12.5; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDProgrammingVoltageGate.Create; +begin + // Call the inherited handler + inherited; + FConfig.MinimumVolts := DEFAULT_PROGRAMMING_VOLTAGE_MIN; + FConfig.PerOEM := TDictionary.Create; +end; + +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ +destructor TOBDProgrammingVoltageGate.Destroy; +begin + FConfig.PerOEM.Free; + // Call the inherited handler + inherited; +end; + +//------------------------------------------------------------------------------ +// SET MINIMUM VOLTS +//------------------------------------------------------------------------------ +procedure TOBDProgrammingVoltageGate.SetMinimumVolts(V: Single); +begin + if V <= 0 then + raise EOBDProgrammingVoltageTooLow.Create( + 'Threshold must be positive'); + FConfig.MinimumVolts := V; +end; + +//------------------------------------------------------------------------------ +// SET OEMTHRESHOLD +//------------------------------------------------------------------------------ +procedure TOBDProgrammingVoltageGate.SetOEMThreshold(const OEMKey: string; + V: Single); +begin + if OEMKey = '' then + raise EOBDProgrammingVoltageTooLow.Create( + 'OEM key cannot be empty'); + if V <= 0 then + raise EOBDProgrammingVoltageTooLow.Create( + 'Threshold must be positive'); + FConfig.PerOEM.AddOrSetValue(LowerCase(OEMKey), V); +end; + +//------------------------------------------------------------------------------ +// RESOLVE THRESHOLD +//------------------------------------------------------------------------------ +function TOBDProgrammingVoltageGate.ResolveThreshold(const OEMKey: string; + out OEMUsed: string): Single; +var + Lookup: string; +begin + OEMUsed := ''; + if OEMKey <> '' then + begin + Lookup := LowerCase(OEMKey); + if FConfig.PerOEM.TryGetValue(Lookup, Result) then + begin + OEMUsed := Lookup; + Exit; + end; + end; + Result := FConfig.MinimumVolts; +end; + +//------------------------------------------------------------------------------ +// CHECK +//------------------------------------------------------------------------------ +function TOBDProgrammingVoltageGate.Check(const Reader: TOBDVoltageReader; + const OEMKey: string): TOBDVoltageGateResult; +begin + Result := Default(TOBDVoltageGateResult); + if not Assigned(Reader) then + begin + Result.Reason := 'voltage reader callback not supplied'; + Exit; + end; + Result.RequiredVolts := ResolveThreshold(OEMKey, Result.OEMUsed); + try + Result.MeasuredVolts := Reader(); + except + on E: Exception do + begin + Result.Reason := 'reader raised: ' + E.Message; + Exit; + end; + end; + if Result.MeasuredVolts <= 0 then + begin + Result.Reason := Format( + 'reader returned non-positive voltage (%.2f V)', + [Result.MeasuredVolts]); + Exit; + end; + Result.Passed := Result.MeasuredVolts >= Result.RequiredVolts; + if not Result.Passed then + Result.Reason := Format( + 'measured %.2f V < required %.2f V', + [Result.MeasuredVolts, Result.RequiredVolts]); +end; + +//------------------------------------------------------------------------------ +// REQUIRE PASS +//------------------------------------------------------------------------------ +procedure TOBDProgrammingVoltageGate.RequirePass(const Reader: TOBDVoltageReader; + const OEMKey: string); +var + R: TOBDVoltageGateResult; +begin + R := Check(Reader, OEMKey); + if R.Passed then Exit; + if (R.MeasuredVolts <= 0) or (R.Reason.Contains('reader')) then + raise EOBDProgrammingVoltageUnavailable.Create( + 'cannot read battery voltage: ' + R.Reason) + else + raise EOBDProgrammingVoltageTooLow.Create( + 'flashing aborted: ' + R.Reason); +end; + +end. diff --git a/src/Services/OBD.ECU.Flashing.pas b/src/Services/OBD.ECU.Flashing.pas index e4233d5b..d86124e4 100644 --- a/src/Services/OBD.ECU.Flashing.pas +++ b/src/Services/OBD.ECU.Flashing.pas @@ -136,11 +136,15 @@ TOBDECUFlashing = class(TComponent) /// procedure RequestCancel; - /// Current pipeline stage. + /// + /// Current pipeline stage. + /// function Stage: TOBDFlashStage; published - /// Maximum block size when streaming firmware to the ECU. + /// + /// Maximum block size when streaming firmware to the ECU. + /// property BlockSize: Integer read FBlockSize write FBlockSize default 1024; /// /// Where to write the pre-flash snapshot. Empty string disables @@ -155,28 +159,43 @@ TOBDECUFlashing = class(TComponent) property OnFailed: TOBDFlashStageEvent read FOnFailed write FOnFailed; public - /// Pluggable signature verifier (default: SHA-256). + /// + /// Pluggable signature verifier (default: SHA-256). + /// property SignatureVerifier: IFirmwareSignatureVerifier read FSignatureVerifier write FSignatureVerifier; - /// Pre-flash health check (battery, ignition, comm). + /// + /// Pre-flash health check (battery, ignition, comm). + /// property OnHealthCheck: TOBDFlashHealthCheck read FHealthCheck write FHealthCheck; - /// ECU read-back implementation. + /// + /// ECU read-back implementation. + /// property OnSnapshot: TOBDFlashSnapshotProc read FSnapshot write FSnapshot; - /// Per-block write implementation. + /// + /// Per-block write implementation. + /// property OnWriteChunk: TOBDFlashWriteChunkProc read FWriteChunk write FWriteChunk; - /// Post-write finalisation (RequestTransferExit, checksum). + /// + /// Post-write finalisation (RequestTransferExit, checksum). + /// property OnFinalise: TOBDFlashFinaliseProc read FFinalise write FFinalise; - /// Post-flash verification (re-read + signature recheck). + /// + /// Post-flash verification (re-read + signature recheck). + /// property OnVerifyEcu: TOBDFlashVerifyProc read FVerifyEcu write FVerifyEcu; end; implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDECUFlashing.Create(AOwner: TComponent); begin inherited Create(AOwner); @@ -186,12 +205,18 @@ constructor TOBDECUFlashing.Create(AOwner: TComponent); FSignatureVerifier := TOBDSha256SignatureVerifier.Create; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDECUFlashing.Destroy; begin FStageLock.Free; inherited; end; +//------------------------------------------------------------------------------ +// SET STAGE +//------------------------------------------------------------------------------ procedure TOBDECUFlashing.SetStage(NewStage: TOBDFlashStage); var Cb: TOBDFlashStageEvent; @@ -201,6 +226,9 @@ procedure TOBDECUFlashing.SetStage(NewStage: TOBDFlashStage); if Assigned(Cb) then try Cb(Self, NewStage); except end; end; +//------------------------------------------------------------------------------ +// REPORT PROGRESS +//------------------------------------------------------------------------------ procedure TOBDECUFlashing.ReportProgress(Percent: Single; const Msg: string); var Cb: TOBDFlashProgressEvent; @@ -211,18 +239,27 @@ procedure TOBDECUFlashing.ReportProgress(Percent: Single; const Msg: string); if Assigned(Cb) then try Cb(Self, Stg, Percent, Msg); except end; end; +//------------------------------------------------------------------------------ +// REQUEST CANCEL +//------------------------------------------------------------------------------ procedure TOBDECUFlashing.RequestCancel; begin FStageLock.Enter; try FCancelRequested := True; finally FStageLock.Leave; end; end; +//------------------------------------------------------------------------------ +// STAGE +//------------------------------------------------------------------------------ function TOBDECUFlashing.Stage: TOBDFlashStage; begin FStageLock.Enter; try Result := FStage; finally FStageLock.Leave; end; end; +//------------------------------------------------------------------------------ +// PERFORM PRE CHECK +//------------------------------------------------------------------------------ function TOBDECUFlashing.PerformPreCheck(out Reason: string): Boolean; begin Reason := ''; @@ -230,6 +267,9 @@ function TOBDECUFlashing.PerformPreCheck(out Reason: string): Boolean; Result := FHealthCheck(Reason); end; +//------------------------------------------------------------------------------ +// PERFORM SNAPSHOT +//------------------------------------------------------------------------------ function TOBDECUFlashing.PerformSnapshot(out Snapshot: TBytes; out Reason: string): Boolean; begin @@ -255,6 +295,9 @@ function TOBDECUFlashing.PerformSnapshot(out Snapshot: TBytes; end; end; +//------------------------------------------------------------------------------ +// PERSIST SNAPSHOT +//------------------------------------------------------------------------------ procedure TOBDECUFlashing.PersistSnapshot(const Snapshot: TBytes); begin if (FBackupPath = '') or (Length(Snapshot) = 0) then Exit; @@ -267,6 +310,9 @@ procedure TOBDECUFlashing.PersistSnapshot(const Snapshot: TBytes); end; end; +//------------------------------------------------------------------------------ +// PERFORM VERIFY SIGNATURE +//------------------------------------------------------------------------------ function TOBDECUFlashing.PerformVerifySignature(const Firmware, Signature: TBytes; out Reason: string): Boolean; begin @@ -285,6 +331,9 @@ function TOBDECUFlashing.PerformVerifySignature(const Firmware, Result := True; end; +//------------------------------------------------------------------------------ +// PERFORM WRITE +//------------------------------------------------------------------------------ function TOBDECUFlashing.PerformWrite(const Firmware: TBytes; out Reason: string): Boolean; var @@ -329,7 +378,10 @@ function TOBDECUFlashing.PerformWrite(const Firmware: TBytes; end; except on E: Exception do - begin Reason := Format('Block %d threw: %s', [Block, E.Message]); Exit(False); end; + begin + Reason := Format('Block %d threw: %s', [Block, E.Message]); + Exit(False); + end; end; Inc(Offset, ChunkLen); @@ -341,6 +393,9 @@ function TOBDECUFlashing.PerformWrite(const Firmware: TBytes; Result := True; end; +//------------------------------------------------------------------------------ +// PERFORM FINALISE +//------------------------------------------------------------------------------ function TOBDECUFlashing.PerformFinalise(out Reason: string): Boolean; begin Reason := ''; @@ -350,6 +405,9 @@ function TOBDECUFlashing.PerformFinalise(out Reason: string): Boolean; end; end; +//------------------------------------------------------------------------------ +// PERFORM VERIFY ECU +//------------------------------------------------------------------------------ function TOBDECUFlashing.PerformVerifyEcu(out Reason: string): Boolean; begin Reason := ''; @@ -359,6 +417,9 @@ function TOBDECUFlashing.PerformVerifyEcu(out Reason: string): Boolean; end; end; +//------------------------------------------------------------------------------ +// PERFORM ROLLBACK +//------------------------------------------------------------------------------ procedure TOBDECUFlashing.PerformRollback(const Snapshot: TBytes); var Reason: string; @@ -373,6 +434,9 @@ procedure TOBDECUFlashing.PerformRollback(const Snapshot: TBytes); // already failed and rollback is best-effort. end; +//------------------------------------------------------------------------------ +// START FLASH +//------------------------------------------------------------------------------ function TOBDECUFlashing.StartFlash(const Firmware, Signature: TBytes): Boolean; var Reason: string; @@ -395,7 +459,11 @@ function TOBDECUFlashing.StartFlash(const Firmware, Signature: TBytes): Boolean; Exit; end; - if FCancelRequested then begin SetStage(fsCancelled); Exit; end; + if FCancelRequested then + begin + SetStage(fsCancelled); + Exit; + end; // Signature ---------------------------------------------------------- SetStage(fsVerifySignature); @@ -407,7 +475,11 @@ function TOBDECUFlashing.StartFlash(const Firmware, Signature: TBytes): Boolean; Exit; end; - if FCancelRequested then begin SetStage(fsCancelled); Exit; end; + if FCancelRequested then + begin + SetStage(fsCancelled); + Exit; + end; // Snapshot ----------------------------------------------------------- SetStage(fsSnapshot); @@ -420,7 +492,11 @@ function TOBDECUFlashing.StartFlash(const Firmware, Signature: TBytes): Boolean; end; PersistSnapshot(Snapshot); - if FCancelRequested then begin SetStage(fsCancelled); Exit; end; + if FCancelRequested then + begin + SetStage(fsCancelled); + Exit; + end; // Erase is implicit in most UDS flows — RequestDownload (SID $34) // covers it. We expose a stage marker for UI feedback even though @@ -428,7 +504,11 @@ function TOBDECUFlashing.StartFlash(const Firmware, Signature: TBytes): Boolean; SetStage(fsErase); ReportProgress(0, 'preparing ECU for download'); - if FCancelRequested then begin SetStage(fsCancelled); Exit; end; + if FCancelRequested then + begin + SetStage(fsCancelled); + Exit; + end; // Write -------------------------------------------------------------- SetStage(fsWrite); @@ -441,7 +521,11 @@ function TOBDECUFlashing.StartFlash(const Firmware, Signature: TBytes): Boolean; Exit; end; - if FCancelRequested then begin SetStage(fsCancelled); Exit; end; + if FCancelRequested then + begin + SetStage(fsCancelled); + Exit; + end; // Finalise ----------------------------------------------------------- SetStage(fsFinalise); diff --git a/src/Services/OBD.ECU.Signature.BCrypt.pas b/src/Services/OBD.ECU.Signature.BCrypt.pas index 1f3a4c5d..52685437 100644 --- a/src/Services/OBD.ECU.Signature.BCrypt.pas +++ b/src/Services/OBD.ECU.Signature.BCrypt.pas @@ -88,6 +88,9 @@ TBCryptPkcs1PaddingInfo = record const BCRYPT_SHA256_ALGORITHM: PWideChar = 'SHA256'; +//------------------------------------------------------------------------------ +// CRYPT IMPORT PUBLIC KEY INFO EX2 +//------------------------------------------------------------------------------ function CryptImportPublicKeyInfoEx2( dwCertEncodingType: DWORD; pInfo: Pointer; // PCERT_PUBLIC_KEY_INFO @@ -96,6 +99,9 @@ function CryptImportPublicKeyInfoEx2( out phKey: NativeUInt): BOOL; stdcall; external 'crypt32.dll' name 'CryptImportPublicKeyInfoEx2'; +//------------------------------------------------------------------------------ +// CRYPT DECODE OBJECT EX +//------------------------------------------------------------------------------ function CryptDecodeObjectEx( dwCertEncodingType: DWORD; lpszStructType: PAnsiChar; @@ -107,6 +113,9 @@ function CryptDecodeObjectEx( var pcbStructInfo: DWORD): BOOL; stdcall; external 'crypt32.dll' name 'CryptDecodeObjectEx'; +//------------------------------------------------------------------------------ +// BCRYPT VERIFY SIGNATURE +//------------------------------------------------------------------------------ function BCryptVerifySignature( hKey: NativeUInt; pPaddingInfo: Pointer; @@ -117,9 +126,15 @@ function BCryptVerifySignature( dwFlags: ULONG): NativeInt; stdcall; external 'bcrypt.dll' name 'BCryptVerifySignature'; +//------------------------------------------------------------------------------ +// BCRYPT DESTROY KEY +//------------------------------------------------------------------------------ function BCryptDestroyKey(hKey: NativeUInt): NativeInt; stdcall; external 'bcrypt.dll' name 'BCryptDestroyKey'; +//------------------------------------------------------------------------------ +// LOCAL FREE2 +//------------------------------------------------------------------------------ function LocalFree2(hMem: HLOCAL): HLOCAL; stdcall; external kernel32 name 'LocalFree'; @@ -156,6 +171,10 @@ function ComputeSha256Digest(const Data: TBytes): TBytes; //============================================================================== // TOBDBCryptVerifier //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDBCryptVerifier.Create(const PublicKeyDer: TBytes); var Key: NativeUInt; @@ -179,16 +198,25 @@ constructor TOBDBCryptVerifier.Create(const PublicKeyDer: TBytes); end; end; +//------------------------------------------------------------------------------ +// ALGORITHM NAME +//------------------------------------------------------------------------------ function TOBDBCryptVerifier.AlgorithmName: string; begin Result := FAlgorithmName; end; +//------------------------------------------------------------------------------ +// DESTROY KEY +//------------------------------------------------------------------------------ procedure TOBDBCryptVerifier.DestroyKey(KeyHandle: NativeUInt); begin if KeyHandle <> 0 then BCryptDestroyKey(KeyHandle); end; +//------------------------------------------------------------------------------ +// IMPORT KEY +//------------------------------------------------------------------------------ function TOBDBCryptVerifier.ImportKey(out KeyHandle: NativeUInt; out IsRSA, IsECDSA: Boolean): Boolean; type @@ -250,6 +278,9 @@ TCertPublicKeyInfo = record end; end; +//------------------------------------------------------------------------------ +// VERIFY RSA +//------------------------------------------------------------------------------ function TOBDBCryptVerifier.VerifyRSA(KeyHandle: NativeUInt; const Hash, Signature: TBytes): Boolean; var @@ -264,6 +295,9 @@ function TOBDBCryptVerifier.VerifyRSA(KeyHandle: NativeUInt; Result := Status = 0; end; +//------------------------------------------------------------------------------ +// PARSE DERSIGNATURE RS +//------------------------------------------------------------------------------ function TOBDBCryptVerifier.ParseDERSignatureRS(const DerSig: TBytes): TBytes; // Convert ASN.1 DER ECDSA-Sig-Value (SEQUENCE { INTEGER r, INTEGER s }) into // the fixed-size R||S form BCrypt expects. P-256 means R and S are 32 bytes @@ -319,6 +353,9 @@ function TOBDBCryptVerifier.ParseDERSignatureRS(const DerSig: TBytes): TBytes; if SLen > 0 then Move(S[0], Result[FixedLen + (FixedLen - SLen)], SLen); end; +//------------------------------------------------------------------------------ +// VERIFY ECDSA +//------------------------------------------------------------------------------ function TOBDBCryptVerifier.VerifyECDSA(KeyHandle: NativeUInt; const Hash, Signature: TBytes): Boolean; var @@ -338,6 +375,9 @@ function TOBDBCryptVerifier.VerifyECDSA(KeyHandle: NativeUInt; Result := Status = 0; end; +//------------------------------------------------------------------------------ +// VERIFY +//------------------------------------------------------------------------------ function TOBDBCryptVerifier.Verify(const Firmware, Signature: TBytes): Boolean; var Key: NativeUInt; diff --git a/src/Services/OBD.ECU.Signature.HSM.pas b/src/Services/OBD.ECU.Signature.HSM.pas index 9f680095..2c0e9a49 100644 --- a/src/Services/OBD.ECU.Signature.HSM.pas +++ b/src/Services/OBD.ECU.Signature.HSM.pas @@ -34,12 +34,16 @@ interface /// IOBDHSMSession = interface ['{F5ECC5DD-9D0A-4F1B-9D08-7B70F3F5E0B6}'] - /// Human-readable name of the HSM session ("AWS CloudHSM - /// us-east-1", "PKCS#11 slot 0", …). Surfaces in audit logs. + /// + /// Human-readable name of the HSM session ("AWS CloudHSM + /// us-east-1", "PKCS#11 slot 0", …). Surfaces in audit logs. + /// function SessionName: string; - /// Identifier of the key used for verification — typically a - /// PKCS#11 CKA_LABEL, a Key Vault key URI, or a vendor key handle. + /// + /// Identifier of the key used for verification — typically a + /// PKCS#11 CKA_LABEL, a Key Vault key URI, or a vendor key handle. + /// function KeyIdentifier: string; /// @@ -67,12 +71,17 @@ TOBDHSMVerifier = class(TInterfacedObject, IFirmwareSignatureVerifier) constructor Create(const ASession: IOBDHSMSession); function AlgorithmName: string; function Verify(const Firmware, Signature: TBytes): Boolean; - /// The wrapped session — exposed for audit-log enrichment. + /// + /// The wrapped session — exposed for audit-log enrichment. + /// property Session: IOBDHSMSession read FSession; end; implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDHSMVerifier.Create(const ASession: IOBDHSMSession); begin inherited Create; @@ -82,12 +91,18 @@ constructor TOBDHSMVerifier.Create(const ASession: IOBDHSMSession); FSession := ASession; end; +//------------------------------------------------------------------------------ +// ALGORITHM NAME +//------------------------------------------------------------------------------ function TOBDHSMVerifier.AlgorithmName: string; begin Result := Format('HSM (%s, key=%s)', [FSession.SessionName, FSession.KeyIdentifier]); end; +//------------------------------------------------------------------------------ +// VERIFY +//------------------------------------------------------------------------------ function TOBDHSMVerifier.Verify(const Firmware, Signature: TBytes): Boolean; begin if not FSession.IsReady then Exit(False); diff --git a/src/Services/OBD.ECU.Signature.OpenSSL.pas b/src/Services/OBD.ECU.Signature.OpenSSL.pas index dc42a332..713e8334 100644 --- a/src/Services/OBD.ECU.Signature.OpenSSL.pas +++ b/src/Services/OBD.ECU.Signature.OpenSSL.pas @@ -50,7 +50,9 @@ TOBDOpenSSLVerifier = class(TInterfacedObject, IFirmwareSignatureVerifier) function Verify(const Firmware, Signature: TBytes): Boolean; end; -/// True after libcrypto has been successfully loaded once. +/// +/// True after libcrypto has been successfully loaded once. +/// function OpenSSLAvailable: Boolean; implementation @@ -86,6 +88,9 @@ implementation EVP_PKEY_free: TEVP_PKEY_free; EVP_PKEY_id: TEVP_PKEY_id; +//------------------------------------------------------------------------------ +// RESOLVE SYMBOL +//------------------------------------------------------------------------------ function ResolveSymbol(const Name: AnsiString): Pointer; begin Result := GetProcAddress(GLib, PAnsiChar(Name)); @@ -93,6 +98,9 @@ function ResolveSymbol(const Name: AnsiString): Pointer; raise EOBDOpenSSLError.CreateFmt('libcrypto symbol %s missing', [Name]); end; +//------------------------------------------------------------------------------ +// TRY LOAD LIB CRYPTO +//------------------------------------------------------------------------------ function TryLoadLibCrypto: Boolean; const Candidates: array[0..3] of string = ( @@ -131,6 +139,9 @@ function TryLoadLibCrypto: Boolean; Result := True; end; +//------------------------------------------------------------------------------ +// OPEN SSLAVAILABLE +//------------------------------------------------------------------------------ function OpenSSLAvailable: Boolean; begin Result := TryLoadLibCrypto; @@ -139,6 +150,10 @@ function OpenSSLAvailable: Boolean; //============================================================================== // TOBDOpenSSLVerifier //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDOpenSSLVerifier.Create(const PublicKeyDer: TBytes); var Key: Pointer; @@ -172,11 +187,17 @@ constructor TOBDOpenSSLVerifier.Create(const PublicKeyDer: TBytes); end; end; +//------------------------------------------------------------------------------ +// ALGORITHM NAME +//------------------------------------------------------------------------------ function TOBDOpenSSLVerifier.AlgorithmName: string; begin Result := FAlgorithmName; end; +//------------------------------------------------------------------------------ +// IMPORT KEY +//------------------------------------------------------------------------------ function TOBDOpenSSLVerifier.ImportKey: Pointer; var Cursor: Pointer; @@ -186,11 +207,17 @@ function TOBDOpenSSLVerifier.ImportKey: Pointer; Result := d2i_PUBKEY(nil, @Cursor, Length(FPublicKeyDer)); end; +//------------------------------------------------------------------------------ +// FREE KEY +//------------------------------------------------------------------------------ procedure TOBDOpenSSLVerifier.FreeKey(KeyPtr: Pointer); begin if KeyPtr <> nil then EVP_PKEY_free(KeyPtr); end; +//------------------------------------------------------------------------------ +// VERIFY +//------------------------------------------------------------------------------ function TOBDOpenSSLVerifier.Verify(const Firmware, Signature: TBytes): Boolean; var Ctx: Pointer; diff --git a/src/Services/OBD.ECU.Signature.PQC.pas b/src/Services/OBD.ECU.Signature.PQC.pas new file mode 100644 index 00000000..0fc5c673 --- /dev/null +++ b/src/Services/OBD.ECU.Signature.PQC.pas @@ -0,0 +1,248 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.ECU.Signature.PQC.pas +// CONTENTS : Post-quantum signature verifier scaffolding +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.ECU.Signature.PQC; + +interface + +uses + System.SysUtils, + + OBD.ECU.Signature; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + /// + /// Algorithm tag stored inside the envelope. Stable wire + /// values; never renumber. + /// + TOBDPQCAlgorithm = ( + pqcUnknown = 0, + pqcMlDsa44 = 1, // FIPS 204 ML-DSA-44 + pqcMlDsa65 = 2, // FIPS 204 ML-DSA-65 (recommended baseline) + pqcMlDsa87 = 3, // FIPS 204 ML-DSA-87 + pqcSlhDsaShake128s = 16, // FIPS 205 SLH-DSA-SHAKE-128s + pqcSlhDsaSha2128s = 17 // FIPS 205 SLH-DSA-SHA2-128s + ); + + EOBDPQCSignature = class(Exception); + EOBDPQCNotAvailable = class(EOBDPQCSignature); + + /// + /// Decoded envelope: algorithm + key-id + raw signature. + /// + TOBDPQCEnvelope = record + /// + /// Algorithm. + /// + Algorithm: TOBDPQCAlgorithm; + KeyId: TBytes; // up to 32 bytes; opaque to this unit + /// + /// Signature. + /// + Signature: TBytes; + end; + + /// + /// Verifier scaffolding. The Verify implementation raises + /// EOBDPQCNotAvailable until the OpenSSL 3.x EVP binding is wired + /// (tracked in docs/DATA_GAPS.md). The envelope codec is fixed and + /// fully tested in this build. + /// + TOBDPQCSignatureVerifier = class(TInterfacedObject, IFirmwareSignatureVerifier) + private + FAlgorithm: TOBDPQCAlgorithm; + FPublicKey: TBytes; + public + /// + /// Create. + /// + constructor Create(const AAlgorithm: TOBDPQCAlgorithm; + const APublicKey: TBytes); + /// + /// Algorithm name. + /// + function AlgorithmName: string; + /// + /// Verify. + /// + function Verify(const Firmware, Signature: TBytes): Boolean; + end; + +/// +/// Encode an envelope: +/// uint8 algorithm-tag +/// uint8 key-id-length (0..32) +/// bytes key-id +/// uint32 signature-length (BE) +/// bytes signature +/// +function EncodePQCEnvelope(const Env: TOBDPQCEnvelope): TBytes; + +/// +/// Decode an envelope. Raises on malformed input. +/// +function DecodePQCEnvelope(const Bytes: TBytes): TOBDPQCEnvelope; + +/// +/// Human-readable algorithm name. +/// +function PQCAlgorithmName(const A: TOBDPQCAlgorithm): string; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// PQCALGORITHM NAME +//------------------------------------------------------------------------------ +function PQCAlgorithmName(const A: TOBDPQCAlgorithm): string; +begin + case A of + pqcMlDsa44: Result := 'ML-DSA-44'; + pqcMlDsa65: Result := 'ML-DSA-65'; + pqcMlDsa87: Result := 'ML-DSA-87'; + pqcSlhDsaShake128s: Result := 'SLH-DSA-SHAKE-128s'; + pqcSlhDsaSha2128s: Result := 'SLH-DSA-SHA2-128s'; + else + Result := 'PQC-UNKNOWN'; + end; +end; + +//------------------------------------------------------------------------------ +// ENCODE PQCENVELOPE +//------------------------------------------------------------------------------ +function EncodePQCEnvelope(const Env: TOBDPQCEnvelope): TBytes; +var + Out_: TBytes; + KeyLen: Integer; + SigLen: UInt32; + Cursor: Integer; +begin + KeyLen := Length(Env.KeyId); + if KeyLen > 32 then + raise EOBDPQCSignature.Create('Key-id must not exceed 32 bytes'); + SigLen := UInt32(Length(Env.Signature)); + + // Allocate Out_ + SetLength(Out_, 2 + KeyLen + 4 + Length(Env.Signature)); + Cursor := 0; + Out_[Cursor] := Byte(Env.Algorithm); Inc(Cursor); + Out_[Cursor] := Byte(KeyLen); Inc(Cursor); + if KeyLen > 0 then + begin + Move(Env.KeyId[0], Out_[Cursor], KeyLen); + Inc(Cursor, KeyLen); + end; + Out_[Cursor] := Byte(SigLen shr 24); + Out_[Cursor + 1] := Byte(SigLen shr 16); + Out_[Cursor + 2] := Byte(SigLen shr 8); + Out_[Cursor + 3] := Byte(SigLen); + Inc(Cursor, 4); + if Length(Env.Signature) > 0 then + Move(Env.Signature[0], Out_[Cursor], Length(Env.Signature)); + Result := Out_; +end; + +//------------------------------------------------------------------------------ +// DECODE PQCENVELOPE +//------------------------------------------------------------------------------ +function DecodePQCEnvelope(const Bytes: TBytes): TOBDPQCEnvelope; +var + Cursor, KeyLen: Integer; + SigLen: UInt32; +begin + if Length(Bytes) < 6 then + raise EOBDPQCSignature.Create('Envelope too short (< 6 bytes)'); + Cursor := 0; + Result.Algorithm := TOBDPQCAlgorithm(Bytes[Cursor]); Inc(Cursor); + KeyLen := Bytes[Cursor]; Inc(Cursor); + if KeyLen > 32 then + raise EOBDPQCSignature.Create('Key-id length > 32'); + if Cursor + KeyLen + 4 > Length(Bytes) then + raise EOBDPQCSignature.Create('Envelope truncated at key-id/sig-len header'); + // Allocate Result.KeyId + SetLength(Result.KeyId, KeyLen); + if KeyLen > 0 then + begin + Move(Bytes[Cursor], Result.KeyId[0], KeyLen); + Inc(Cursor, KeyLen); + end; + SigLen := (UInt32(Bytes[Cursor]) shl 24) + or (UInt32(Bytes[Cursor + 1]) shl 16) + or (UInt32(Bytes[Cursor + 2]) shl 8) + or UInt32(Bytes[Cursor + 3]); + Inc(Cursor, 4); + if Cursor + Integer(SigLen) > Length(Bytes) then + raise EOBDPQCSignature.CreateFmt( + 'Envelope truncated: declared %d signature bytes, %d remaining', + [SigLen, Length(Bytes) - Cursor]); + // Allocate Result.Signature + SetLength(Result.Signature, SigLen); + if SigLen > 0 then + Move(Bytes[Cursor], Result.Signature[0], SigLen); +end; + +{ TOBDPQCSignatureVerifier } + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDPQCSignatureVerifier.Create(const AAlgorithm: TOBDPQCAlgorithm; + const APublicKey: TBytes); +begin + // Initialize the inherited class + inherited Create; + if AAlgorithm = pqcUnknown then + raise EOBDPQCSignature.Create('Algorithm must be specified'); + if Length(APublicKey) = 0 then + raise EOBDPQCSignature.Create('Public key required'); + FAlgorithm := AAlgorithm; + FPublicKey := Copy(APublicKey); +end; + +//------------------------------------------------------------------------------ +// ALGORITHM NAME +//------------------------------------------------------------------------------ +function TOBDPQCSignatureVerifier.AlgorithmName: string; +begin + Result := PQCAlgorithmName(FAlgorithm); +end; + +//------------------------------------------------------------------------------ +// VERIFY +//------------------------------------------------------------------------------ +function TOBDPQCSignatureVerifier.Verify(const Firmware, Signature: TBytes): Boolean; +var + Env: TOBDPQCEnvelope; +begin + // Always parse the envelope first so a malformed signature blob is + // rejected with the same error regardless of OpenSSL availability. + Env := DecodePQCEnvelope(Signature); + if Env.Algorithm <> FAlgorithm then + raise EOBDPQCSignature.CreateFmt( + 'Algorithm mismatch: envelope=%s, verifier=%s', + [PQCAlgorithmName(Env.Algorithm), PQCAlgorithmName(FAlgorithm)]); + + // OpenSSL 3.x EVP_PKEY_verify with the appropriate OID is the + // production path. The binding is intentionally absent from this + // build until an OEM ships a wire spec; see docs/DATA_GAPS.md + // (4.5.pqc_openssl_binding). Until then we fail closed. + raise EOBDPQCNotAvailable.Create( + 'PQC verifier scaffolding only. OpenSSL 3.x EVP binding for ' + + PQCAlgorithmName(FAlgorithm) + + ' is not present in this build (see docs/DATA_GAPS.md).'); +end; + +end. diff --git a/src/Services/OBD.ECU.Signature.pas b/src/Services/OBD.ECU.Signature.pas index 303a57bf..1bf74c4a 100644 --- a/src/Services/OBD.ECU.Signature.pas +++ b/src/Services/OBD.ECU.Signature.pas @@ -29,7 +29,9 @@ interface /// IFirmwareSignatureVerifier = interface ['{1B6F4C9B-9F32-4DC4-8C4E-8B87A6F7E8B0}'] - /// Algorithm name for diagnostics ("SHA-256", "RSA-SHA256", …). + /// + /// Algorithm name for diagnostics ("SHA-256", "RSA-SHA256", …). + /// function AlgorithmName: string; /// /// True if Firmware matches Signature. Both blobs are @@ -61,11 +63,16 @@ TOBDPermissiveSignatureVerifier = class(TInterfacedObject, IFirmwareSignatureV function Verify(const Firmware, Signature: TBytes): Boolean; end; -/// SHA-256 helper. +/// +/// SHA-256 helper. +/// function ComputeSha256(const Data: TBytes): TBytes; implementation +//------------------------------------------------------------------------------ +// COMPUTE SHA256 +//------------------------------------------------------------------------------ function ComputeSha256(const Data: TBytes): TBytes; var H: THashSHA2; @@ -76,6 +83,9 @@ function ComputeSha256(const Data: TBytes): TBytes; Result := H.HashAsBytes; end; +//------------------------------------------------------------------------------ +// BYTES EQUAL +//------------------------------------------------------------------------------ function BytesEqual(const A, B: TBytes): Boolean; var I: Integer; @@ -93,11 +103,18 @@ function BytesEqual(const A, B: TBytes): Boolean; //============================================================================== // TOBDSha256SignatureVerifier //============================================================================== + +//------------------------------------------------------------------------------ +// ALGORITHM NAME +//------------------------------------------------------------------------------ function TOBDSha256SignatureVerifier.AlgorithmName: string; begin Result := 'SHA-256'; end; +//------------------------------------------------------------------------------ +// VERIFY +//------------------------------------------------------------------------------ function TOBDSha256SignatureVerifier.Verify(const Firmware, Signature: TBytes): Boolean; begin Result := BytesEqual(ComputeSha256(Firmware), Signature); @@ -106,11 +123,18 @@ function TOBDSha256SignatureVerifier.Verify(const Firmware, Signature: TBytes): //============================================================================== // TOBDPermissiveSignatureVerifier //============================================================================== + +//------------------------------------------------------------------------------ +// ALGORITHM NAME +//------------------------------------------------------------------------------ function TOBDPermissiveSignatureVerifier.AlgorithmName: string; begin Result := 'PERMISSIVE (development only)'; end; +//------------------------------------------------------------------------------ +// VERIFY +//------------------------------------------------------------------------------ function TOBDPermissiveSignatureVerifier.Verify(const Firmware, Signature: TBytes): Boolean; begin Result := True; diff --git a/src/Services/OBD.EV.BatteryHealth.pas b/src/Services/OBD.EV.BatteryHealth.pas new file mode 100644 index 00000000..e41c6d37 --- /dev/null +++ b/src/Services/OBD.EV.BatteryHealth.pas @@ -0,0 +1,243 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.EV.BatteryHealth.pas +// CONTENTS : EV battery state-of-health and cell-imbalance helpers +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.EV.BatteryHealth; + +interface + +uses + System.SysUtils, System.Math; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDBatteryHealth = class(Exception); + + /// + /// Cell-imbalance summary computed from the per-cell array. + /// + TOBDCellImbalance = record + /// + /// Cell count. + /// + CellCount: Integer; + /// + /// Min voltage. + /// + MinVoltage: Single; + /// + /// Max voltage. + /// + MaxVoltage: Single; + /// + /// Mean voltage. + /// + MeanVoltage: Single; + StdDev: Single; // population standard deviation + SpreadVolts: Single; // Max - Min, the workshop-friendly figure + OutlierIndex: Integer; // -1 if no cell deviates > 3 sigma; else its index + /// + /// Outlier delta sigma. + /// + OutlierDeltaSigma: Single; + end; + + /// + /// State-of-health computed from observed pack capacity vs + /// rated capacity. SoHFromCapacity is the canonical form; the other + /// fields are intermediate values shown to the workshop UI. + /// + TOBDBatterySoH = record + /// + /// Rated capacity kwh. + /// + RatedCapacityKwh: Single; + /// + /// Observed capacity kwh. + /// + ObservedCapacityKwh: Single; + SoHFromCapacity: Single; // 0..1 (1.0 = brand new) + /// + /// Equivalent full cycles. + /// + EquivalentFullCycles: Integer; + DeratingFromTemperature: Single; // 0..1 multiplier; 1.0 = no derating + CompositeSoH: Single; // SoHFromCapacity * DeratingFromTemperature + end; + + /// + /// One charging-session record. The OEM catalog DIDs that + /// feed this come in slightly different units across OEMs; the + /// caller normalises before constructing. + /// + TOBDChargingSession = record + /// + /// Start so c percent. + /// + StartSoCPercent: Single; + /// + /// End so c percent. + /// + EndSoCPercent: Single; + /// + /// Energy delivered kwh. + /// + EnergyDeliveredKwh: Single; + /// + /// Peak power kw. + /// + PeakPowerKw: Single; + /// + /// Average battery temp c. + /// + AverageBatteryTempC: Single; + /// + /// Duration seconds. + /// + DurationSeconds: Integer; + SessionType: string; // 'AC', 'DC', 'V2L', 'V2G', etc. + end; + +/// +/// Compute imbalance metrics across the per-cell voltage +/// array (volts). Raises on empty input. +/// +function ComputeCellImbalance(const CellVolts: array of Single): TOBDCellImbalance; + +/// +/// Compute SoH from a (rated, observed) capacity pair. Both +/// must be positive. Optional temperature derating multiplier in +/// 0..1; default 1.0 (no derating). +/// +function ComputeBatterySoH(const RatedKwh, ObservedKwh: Single; + /// + /// Equivalent full cycles. + /// + EquivalentFullCycles: Integer = 0; + /// + /// Derating from temperature. + /// + DeratingFromTemperature: Single = 1.0): TOBDBatterySoH; + +/// +/// Normalise a charging-session record. Validates the +/// SoC pair (start < end, 0..100) and the duration. +/// +function NormaliseChargingSession(const Raw: TOBDChargingSession): + TOBDChargingSession; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// COMPUTE CELL IMBALANCE +//------------------------------------------------------------------------------ +function ComputeCellImbalance(const CellVolts: array of Single): TOBDCellImbalance; +var + I: Integer; + Sum, SumSq, Diff, Sigma: Double; + MaxAbs: Double; +begin + if Length(CellVolts) = 0 then + raise EOBDBatteryHealth.Create('Need at least one cell voltage'); + Result := Default(TOBDCellImbalance); + Result.CellCount := Length(CellVolts); + Result.MinVoltage := CellVolts[0]; + Result.MaxVoltage := CellVolts[0]; + Sum := 0; + for I := 0 to High(CellVolts) do + begin + if CellVolts[I] < Result.MinVoltage then Result.MinVoltage := CellVolts[I]; + if CellVolts[I] > Result.MaxVoltage then Result.MaxVoltage := CellVolts[I]; + Sum := Sum + CellVolts[I]; + end; + Result.MeanVoltage := Sum / Length(CellVolts); + Result.SpreadVolts := Result.MaxVoltage - Result.MinVoltage; + + SumSq := 0; + for I := 0 to High(CellVolts) do + begin + Diff := CellVolts[I] - Result.MeanVoltage; + SumSq := SumSq + Diff * Diff; + end; + Result.StdDev := Sqrt(SumSq / Length(CellVolts)); + + Result.OutlierIndex := -1; + Result.OutlierDeltaSigma := 0; + if Result.StdDev > 0 then + begin + MaxAbs := 0; + for I := 0 to High(CellVolts) do + begin + Diff := Abs(CellVolts[I] - Result.MeanVoltage) / Result.StdDev; + if Diff > MaxAbs then + begin + MaxAbs := Diff; + Result.OutlierIndex := I; + Result.OutlierDeltaSigma := Diff; + end; + end; + // Only flag if >3-sigma; below that it's noise. + if MaxAbs <= 3.0 then + begin + Result.OutlierIndex := -1; + Result.OutlierDeltaSigma := MaxAbs; + end; + end; +end; + +//------------------------------------------------------------------------------ +// COMPUTE BATTERY SO H +//------------------------------------------------------------------------------ +function ComputeBatterySoH(const RatedKwh, ObservedKwh: Single; + EquivalentFullCycles: Integer; DeratingFromTemperature: Single): TOBDBatterySoH; +begin + if RatedKwh <= 0 then + raise EOBDBatteryHealth.Create('Rated capacity must be positive'); + if ObservedKwh < 0 then + raise EOBDBatteryHealth.Create('Observed capacity cannot be negative'); + if (DeratingFromTemperature < 0) or (DeratingFromTemperature > 1) then + raise EOBDBatteryHealth.Create( + 'DeratingFromTemperature must be in [0, 1]'); + Result.RatedCapacityKwh := RatedKwh; + Result.ObservedCapacityKwh := ObservedKwh; + Result.SoHFromCapacity := ObservedKwh / RatedKwh; + if Result.SoHFromCapacity > 1 then + Result.SoHFromCapacity := 1; + Result.EquivalentFullCycles := EquivalentFullCycles; + Result.DeratingFromTemperature := DeratingFromTemperature; + Result.CompositeSoH := Result.SoHFromCapacity * DeratingFromTemperature; +end; + +//------------------------------------------------------------------------------ +// NORMALISE CHARGING SESSION +//------------------------------------------------------------------------------ +function NormaliseChargingSession(const Raw: TOBDChargingSession): + TOBDChargingSession; +begin + Result := Raw; + if (Raw.StartSoCPercent < 0) or (Raw.StartSoCPercent > 100) then + raise EOBDBatteryHealth.CreateFmt( + 'StartSoC out of range: %.2f', [Raw.StartSoCPercent]); + if (Raw.EndSoCPercent < 0) or (Raw.EndSoCPercent > 100) then + raise EOBDBatteryHealth.CreateFmt( + 'EndSoC out of range: %.2f', [Raw.EndSoCPercent]); + if Raw.EndSoCPercent < Raw.StartSoCPercent then + raise EOBDBatteryHealth.Create( + 'EndSoC must be >= StartSoC for a charging session'); + if Raw.DurationSeconds < 0 then + raise EOBDBatteryHealth.Create('Duration cannot be negative'); +end; + +end. diff --git a/src/Services/OBD.FreezeFrame.pas b/src/Services/OBD.FreezeFrame.pas index 1c0c7823..651747bf 100644 --- a/src/Services/OBD.FreezeFrame.pas +++ b/src/Services/OBD.FreezeFrame.pas @@ -26,47 +26,67 @@ interface type EOBDFreezeFrameError = class(Exception); - /// One PID/data pair from a freeze-frame snapshot. + /// + /// One PID/data pair from a freeze-frame snapshot. + /// TOBDFreezeFrameEntry = record - /// Frame number (almost always 0; ECUs that store - /// multiple frames keep them numbered sequentially). + /// + /// Frame number (almost always 0; ECUs that store + /// multiple frames keep them numbered sequentially). + /// FrameNumber: Byte; - /// The Service 01 PID this entry mirrors. + /// + /// The Service 01 PID this entry mirrors. + /// PID: Byte; - /// Raw payload bytes (everything past the SID + PID - /// + frame echo). Decode through your usual OBD-II decoder - /// (`OBD.OEM.Catalog.JSON.DecodePayload` against - /// `obd2-pids.json`). + /// + /// Raw payload bytes (everything past the SID + PID + /// + frame echo). Decode through your usual OBD-II decoder + /// (`OBD.OEM.Catalog.JSON.DecodePayload` against + /// `obd2-pids.json`). + /// Payload: TBytes; end; - /// One full freeze-frame snapshot — typically the DTC - /// that triggered the snapshot plus a handful of correlated - /// PIDs (engine load, coolant temp, RPM, vehicle speed, …). + /// + /// One full freeze-frame snapshot — typically the DTC + /// that triggered the snapshot plus a handful of correlated + /// PIDs (engine load, coolant temp, RPM, vehicle speed, …). + /// TOBDFreezeFrameSnapshot = record FrameNumber: Byte; - /// The DTC that was being set when the freeze frame - /// was captured. Decoded from PID 0x02 (DTC that caused freeze - /// frame). Empty when PID 0x02 wasn't queried. + /// + /// The DTC that was being set when the freeze frame + /// was captured. Decoded from PID 0x02 (DTC that caused freeze + /// frame). Empty when PID 0x02 wasn't queried. + /// TriggerDTC: string; - /// Per-PID payload entries collected for this frame. + /// + /// Per-PID payload entries collected for this frame. + /// Entries: TArray; end; -/// Build the request frame for Service 02. +/// +/// Build the request frame for Service 02. +/// function BuildFreezeFrameRequest(const PID: Byte; const FrameNum: Byte = 0): TBytes; -/// Parse a positive Service 02 reply (42 PID FrameNum -/// DATA…). Throws on a negative response (7F 02 NRC) or -/// a malformed frame. +/// +/// Parse a positive Service 02 reply (42 PID FrameNum +/// DATA…). Throws on a negative response (7F 02 NRC) or +/// a malformed frame. +/// function ParseFreezeFrameResponse(const Response: TBytes; const ExpectedPID: Byte): TOBDFreezeFrameEntry; -/// Format the trigger-DTC bytes from PID 0x02 into the -/// canonical 5-character string (e.g. "P0301"). Mirrors -/// OBD.OEM.DTC.FormatDtc but specialised for the 2-byte PID 0x02 -/// payload. +/// +/// Format the trigger-DTC bytes from PID 0x02 into the +/// canonical 5-character string (e.g. "P0301"). Mirrors +/// OBD.OEM.DTC.FormatDtc but specialised for the 2-byte PID 0x02 +/// payload. +/// function FormatFreezeFrameTriggerDTC(const Bytes: TBytes): string; implementation @@ -74,12 +94,18 @@ implementation uses OBD.OEM.DTC; +//------------------------------------------------------------------------------ +// BUILD FREEZE FRAME REQUEST +//------------------------------------------------------------------------------ function BuildFreezeFrameRequest(const PID: Byte; const FrameNum: Byte): TBytes; begin Result := TBytes.Create($02, PID, FrameNum); end; +//------------------------------------------------------------------------------ +// PARSE FREEZE FRAME RESPONSE +//------------------------------------------------------------------------------ function ParseFreezeFrameResponse(const Response: TBytes; const ExpectedPID: Byte): TOBDFreezeFrameEntry; begin @@ -115,6 +141,9 @@ function ParseFreezeFrameResponse(const Response: TBytes; Result.Payload := Copy(Response, 3, Length(Response) - 3); end; +//------------------------------------------------------------------------------ +// FORMAT FREEZE FRAME TRIGGER DTC +//------------------------------------------------------------------------------ function FormatFreezeFrameTriggerDTC(const Bytes: TBytes): string; begin // PID 0x02 returns 2 bytes (encoded per ISO 15031-5 / SAE J2012). diff --git a/src/Services/OBD.OEM.Agricultural.pas b/src/Services/OBD.OEM.Agricultural.pas index dc788060..a01c3719 100644 --- a/src/Services/OBD.OEM.Agricultural.pas +++ b/src/Services/OBD.OEM.Agricultural.pas @@ -23,13 +23,18 @@ TOBDOEMAgriculturalBase = class abstract(TOBDOEMExtensionBase) protected function JsonFilename: string; virtual; abstract; procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; function DtcCatalogFileName: string; override; @@ -83,72 +88,183 @@ implementation uses OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMAgriculturalBase.ApplicableToVIN(const VIN: string): Boolean; begin Result := VINMatchesCatalog(JsonFilename, VIN); end; +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMAgriculturalBase.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin MergeCatalogJSON(JsonFilename, DIDs, Routines, ECUs); MergeCatalogJSON('uds-standard.json', DIDs, Routines, ECUs); end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMAgriculturalBase.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON(JsonFilename, CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMAgriculturalBase.DtcCatalogFileName: string; begin Result := 'dtc-' + JsonFilename; end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMAgriculturalBase.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin MergeDtcCatalog('dtc-iso-15031.json', Cat); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMJohnDeere.JsonFilename: string; begin Result := 'john-deere.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMJohnDeere.ManufacturerKey: string; begin Result := 'JOHN-DEERE'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMJohnDeere.DisplayName: string; begin Result := 'John Deere (Deere & Company)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMCnh.JsonFilename: string; begin Result := 'cnh.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMCnh.ManufacturerKey: string; begin Result := 'CNH'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMCnh.DisplayName: string; begin Result := 'CNH Industrial (Case IH / New Holland / Steyr)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMCaterpillarAgri.JsonFilename: string; begin Result := 'caterpillar.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMCaterpillarAgri.ManufacturerKey: string; begin Result := 'CATERPILLAR'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMCaterpillarAgri.DisplayName: string; begin Result := 'Caterpillar (Cat)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMKomatsu.JsonFilename: string; begin Result := 'komatsu.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMKomatsu.ManufacturerKey: string; begin Result := 'KOMATSU'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMKomatsu.DisplayName: string; begin Result := 'Komatsu'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMKubota.JsonFilename: string; begin Result := 'kubota.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMKubota.ManufacturerKey: string; begin Result := 'KUBOTA'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMKubota.DisplayName: string; begin Result := 'Kubota Corporation'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMAgco.JsonFilename: string; begin Result := 'agco.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMAgco.ManufacturerKey: string; begin Result := 'AGCO'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMAgco.DisplayName: string; begin Result := 'AGCO (Massey Ferguson / Fendt / Valtra / Challenger)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMClaas.JsonFilename: string; begin Result := 'claas.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMClaas.ManufacturerKey: string; begin Result := 'CLAAS'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMClaas.DisplayName: string; begin Result := 'Claas KGaA'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMVolvoCe.JsonFilename: string; begin Result := 'volvo-ce.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMVolvoCe.ManufacturerKey: string; begin Result := 'VOLVO-CE'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMVolvoCe.DisplayName: string; begin Result := 'Volvo Construction Equipment'; end; initialization diff --git a/src/Services/OBD.OEM.AstonMartin.pas b/src/Services/OBD.OEM.AstonMartin.pas index 0d5c2258..828d4592 100644 --- a/src/Services/OBD.OEM.AstonMartin.pas +++ b/src/Services/OBD.OEM.AstonMartin.pas @@ -25,13 +25,18 @@ interface TOBDOEMExtensionAstonMartin = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; function DtcCatalogFileName: string; override; @@ -47,21 +52,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionAstonMartin.ManufacturerKey: string; -begin Result := 'ASTON_MARTIN'; end; +begin + Result := 'ASTON_MARTIN'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionAstonMartin.DisplayName: string; -begin Result := 'Aston Martin Lagonda Ltd.'; end; +begin + Result := 'Aston Martin Lagonda Ltd.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionAstonMartin.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in aston-martin.json. Result := VINMatchesCatalog('aston-martin.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionAstonMartin.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are aston-martin.json // + uds-standard.json. Hardcoded entries removed. @@ -72,16 +97,28 @@ procedure TOBDOEMExtensionAstonMartin.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionAstonMartin.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('aston-martin.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionAstonMartin.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -89,9 +126,17 @@ procedure TOBDOEMExtensionAstonMartin.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog) MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionAstonMartin.DtcCatalogFileName: string; -begin Result := 'dtc-aston-martin.json'; end; +begin + Result := 'dtc-aston-martin.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionAstonMartin.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.BMW.pas b/src/Services/OBD.OEM.BMW.pas index c7a3e894..3c29a1c1 100644 --- a/src/Services/OBD.OEM.BMW.pas +++ b/src/Services/OBD.OEM.BMW.pas @@ -39,13 +39,18 @@ TOBDBMWSessionNegotiator = class(TOBDStandardSessionNegotiator) TOBDOEMExtensionBMW = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -63,6 +68,9 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// REQUIRES SECURITY ACCESS +//------------------------------------------------------------------------------ function TOBDBMWSessionNegotiator.RequiresSecurityAccess( SessionType: TOBDSessionType): Boolean; begin @@ -72,21 +80,33 @@ function TOBDBMWSessionNegotiator.RequiresSecurityAccess( sstOEMSpecific1, sstOEMSpecific2]; end; +//------------------------------------------------------------------------------ +// DEFAULT TESTER PRESENT MS +//------------------------------------------------------------------------------ function TOBDBMWSessionNegotiator.DefaultTesterPresentMs: Cardinal; begin Result := 1500; // E-series DMEs occasionally drop sessions at 2000 ms. end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDBMWSessionNegotiator.DisplayName: string; begin Result := 'BMW E-Sys / ISTA'; end; +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionBMW.CreateSessionNegotiator: IOBDSessionNegotiator; begin Result := TOBDBMWSessionNegotiator.Create; end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBMW.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); const @@ -104,6 +124,9 @@ procedure TOBDOEMExtensionBMW.SeedDefaultSeedKeyAlgorithms( 'BMW community XOR-mask placeholder', 'community-pr', False)); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBMW.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -111,33 +134,61 @@ procedure TOBDOEMExtensionBMW.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionBMW.DtcCatalogFileName: string; -begin Result := 'dtc-bmw.json'; end; +begin + Result := 'dtc-bmw.json'; +end; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionBMW.ManufacturerKey: string; begin Result := 'BMW'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionBMW.DisplayName: string; begin Result := 'Bayerische Motoren Werke'; end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionBMW.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in bmw.json. Result := VINMatchesCatalog('bmw.json', VIN); end; +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBMW.BuildCatalog(var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are bmw.json + uds-standard.json. MergeCatalogJSON('bmw.json', DIDs, Routines, ECUs); MergeCatalogJSON('uds-standard.json', DIDs, Routines, ECUs); end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBMW.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin // Coding blocks, adaptations, actuator tests, live PIDs and DTC // extended-data records all live in bmw.json. @@ -145,6 +196,9 @@ procedure TOBDOEMExtensionBMW.BuildExtendedCatalog( CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionBMW.DecodeDID(const DID: Word; const Payload: TBytes): string; var diff --git a/src/Services/OBD.OEM.BYD.pas b/src/Services/OBD.OEM.BYD.pas index 747a1c7d..3a4ae930 100644 --- a/src/Services/OBD.OEM.BYD.pas +++ b/src/Services/OBD.OEM.BYD.pas @@ -26,13 +26,18 @@ interface TOBDOEMExtensionBYD = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -49,21 +54,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionBYD.ManufacturerKey: string; -begin Result := 'BYD'; end; +begin + Result := 'BYD'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionBYD.DisplayName: string; -begin Result := 'BYD Auto Co. Ltd.'; end; +begin + Result := 'BYD Auto Co. Ltd.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionBYD.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in byd.json. Result := VINMatchesCatalog('byd.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBYD.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are byd.json // + uds-standard.json. Hardcoded entries removed. @@ -74,22 +99,37 @@ procedure TOBDOEMExtensionBYD.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBYD.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('byd.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBYD.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBYD.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -97,9 +137,17 @@ procedure TOBDOEMExtensionBYD.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionBYD.DtcCatalogFileName: string; -begin Result := 'dtc-byd.json'; end; +begin + Result := 'dtc-byd.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionBYD.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Bentley.pas b/src/Services/OBD.OEM.Bentley.pas index b126a4f0..6d58048c 100644 --- a/src/Services/OBD.OEM.Bentley.pas +++ b/src/Services/OBD.OEM.Bentley.pas @@ -24,13 +24,18 @@ interface TOBDOEMExtensionBentley = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; function DtcCatalogFileName: string; override; @@ -46,21 +51,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionBentley.ManufacturerKey: string; -begin Result := 'BENTLEY'; end; +begin + Result := 'BENTLEY'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionBentley.DisplayName: string; -begin Result := 'Bentley Motors Ltd.'; end; +begin + Result := 'Bentley Motors Ltd.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionBentley.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in bentley.json. Result := VINMatchesCatalog('bentley.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBentley.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are bentley.json // + uds-standard.json. Hardcoded entries removed. @@ -71,16 +96,28 @@ procedure TOBDOEMExtensionBentley.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBentley.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('bentley.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBentley.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -88,9 +125,17 @@ procedure TOBDOEMExtensionBentley.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionBentley.DtcCatalogFileName: string; -begin Result := 'dtc-bentley.json'; end; +begin + Result := 'dtc-bentley.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionBentley.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Captures.pas b/src/Services/OBD.OEM.Captures.pas index fee2849a..505b5411 100644 --- a/src/Services/OBD.OEM.Captures.pas +++ b/src/Services/OBD.OEM.Captures.pas @@ -26,67 +26,99 @@ interface type EOBDCaptureError = class(Exception); - /// One paired request/response in a capture. + /// + /// One paired request/response in a capture. + /// TOBDCapturePair = record - /// Wall-clock millisecond when the request was sent - /// (relative to the capture's Start). + /// + /// Wall-clock millisecond when the request was sent + /// (relative to the capture's Start). + /// SentAt: Int64; - /// Raw request text exactly as the recorder saw it - /// (whitespace + ELM327 prompts stripped). + /// + /// Raw request text exactly as the recorder saw it + /// (whitespace + ELM327 prompts stripped). + /// RequestText: string; - /// Raw response text. + /// + /// Raw response text. + /// ResponseText: string; - /// Best-effort UDS service ID inferred from the request - /// (0 if the request didn't start with a valid hex byte). + /// + /// Best-effort UDS service ID inferred from the request + /// (0 if the request didn't start with a valid hex byte). + /// ServiceID: Byte; - /// For a 0x22 ReadDataByIdentifier request, the DID - /// extracted from bytes 1-2; 0 otherwise. + /// + /// For a 0x22 ReadDataByIdentifier request, the DID + /// extracted from bytes 1-2; 0 otherwise. + /// DID: Word; - /// Response payload past the SID + DID echo. For a - /// 0x22 reply, this is the bytes after 62 HiDID LoDID; - /// for everything else it's the bytes after the SID echo. + /// + /// Response payload past the SID + DID echo. For a + /// 0x22 reply, this is the bytes after 62 HiDID LoDID; + /// for everything else it's the bytes after the SID echo. + /// PayloadBytes: TBytes; - /// True if the response is a 7F <SID> NRC - /// negative reply. + /// + /// True if the response is a 7F <SID> NRC + /// negative reply. + /// IsNegative: Boolean; - /// NRC value when IsNegative is True. + /// + /// NRC value when IsNegative is True. + /// NegativeResponseCode: Byte; end; TOBDCaptureDecoded = record Pair: TOBDCapturePair; - /// True if the OEM extension catalogues this DID. + /// + /// True if the OEM extension catalogues this DID. + /// DidIsCatalogued: Boolean; - /// The catalogued name when DidIsCatalogued; - /// empty otherwise. + /// + /// The catalogued name when DidIsCatalogued; + /// empty otherwise. + /// DidName: string; - /// The OEM's DecodeDID output. Always populated - /// when the request was a 0x22 (the base implementation falls - /// back to a hex dump for unknown DIDs). + /// + /// The OEM's DecodeDID output. Always populated + /// when the request was a 0x22 (the base implementation falls + /// back to a hex dump for unknown DIDs). + /// Display: string; end; -/// Walk Entries and emit one TOBDCapturePair -/// per Sent → next-Received pair. Info / Error lines are skipped. -/// Sent lines without a matching Received before the next Sent are -/// emitted with an empty ResponseText. +/// +/// Walk Entries and emit one TOBDCapturePair +/// per Sent → next-Received pair. Info / Error lines are skipped. +/// Sent lines without a matching Received before the next Sent are +/// emitted with an empty ResponseText. +/// function ExtractCapturePairs( const Entries: TArray): TArray; -/// Run every 22 HiDID LoDID pair through -/// Ext.DecodeDID and report the results. +/// +/// Run every 22 HiDID LoDID pair through +/// Ext.DecodeDID and report the results. +/// function ValidateAgainstExtension( const Pairs: TArray; const Ext: IOBDOEMExtension): TArray; -/// Convenience: load .obdlog, extract pairs, and -/// validate against Ext. +/// +/// Convenience: load .obdlog, extract pairs, and +/// validate against Ext. +/// function ValidateCaptureFile(const FilePath: string; const Ext: IOBDOEMExtension): TArray; -/// Strip ELM327-style framing (whitespace, prompts, -/// SEARCHING..., multi-line response prefixes like -/// 0: / 1:) and return the contiguous hex payload. +/// +/// Strip ELM327-style framing (whitespace, prompts, +/// SEARCHING..., multi-line response prefixes like +/// 0: / 1:) and return the contiguous hex payload. +/// function NormalizeResponseText(const Raw: string): string; implementation @@ -94,6 +126,9 @@ implementation uses System.Character, OBD.OEM.Coding; +//------------------------------------------------------------------------------ +// NORMALIZE RESPONSE TEXT +//------------------------------------------------------------------------------ function NormalizeResponseText(const Raw: string): string; var Lines: TArray; @@ -130,6 +165,9 @@ function NormalizeResponseText(const Raw: string): string; end; end; +//------------------------------------------------------------------------------ +// TRY HEX BYTES +//------------------------------------------------------------------------------ function TryHexBytes(const S: string; out Bytes: TBytes): Boolean; begin try @@ -141,6 +179,9 @@ function TryHexBytes(const S: string; out Bytes: TBytes): Boolean; end; end; +//------------------------------------------------------------------------------ +// BUILD PAIR +//------------------------------------------------------------------------------ function BuildPair(const SentEntry, ReceivedEntry: TOBDRecordedEntry): TOBDCapturePair; var ReqBytes, RespBytes: TBytes; @@ -184,6 +225,9 @@ function BuildPair(const SentEntry, ReceivedEntry: TOBDRecordedEntry): TOBDCaptu Result.PayloadBytes := RespBytes; end; +//------------------------------------------------------------------------------ +// EXTRACT CAPTURE PAIRS +//------------------------------------------------------------------------------ function ExtractCapturePairs( const Entries: TArray): TArray; var @@ -228,6 +272,9 @@ function ExtractCapturePairs( end; end; +//------------------------------------------------------------------------------ +// VALIDATE AGAINST EXTENSION +//------------------------------------------------------------------------------ function ValidateAgainstExtension( const Pairs: TArray; const Ext: IOBDOEMExtension): TArray; @@ -261,6 +308,9 @@ function ValidateAgainstExtension( end; end; +//------------------------------------------------------------------------------ +// VALIDATE CAPTURE FILE +//------------------------------------------------------------------------------ function ValidateCaptureFile(const FilePath: string; const Ext: IOBDOEMExtension): TArray; var diff --git a/src/Services/OBD.OEM.Catalog.CSV.pas b/src/Services/OBD.OEM.Catalog.CSV.pas index 7a336afe..bda9498c 100644 --- a/src/Services/OBD.OEM.Catalog.CSV.pas +++ b/src/Services/OBD.OEM.Catalog.CSV.pas @@ -44,9 +44,13 @@ TOBDCatalogCSVImporter = class constructor Create(const AManufacturerKey, ADisplayName: string; const AApplicableWMIs: TArray; const ADefaultSource: string = ''); - /// Read CsvPath, write a v1 JSON catalog to JsonPath. + /// + /// Read CsvPath, write a v1 JSON catalog to JsonPath. + /// procedure Convert(const CsvPath, JsonPath: string); - /// In-memory variant — useful for tests. + /// + /// In-memory variant — useful for tests. + /// function ConvertText(const CsvText: string): string; property ManufacturerKey: string read FManufacturerKey; @@ -59,6 +63,9 @@ implementation uses System.IOUtils; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDCatalogCSVImporter.Create( const AManufacturerKey, ADisplayName: string; const AApplicableWMIs: TArray; @@ -73,6 +80,9 @@ constructor TOBDCatalogCSVImporter.Create( FDefaultSource := ADefaultSource; end; +//------------------------------------------------------------------------------ +// PARSE CSVLINE +//------------------------------------------------------------------------------ function ParseCSVLine(const Line: string): TArray; // Minimal RFC-4180-style parser: comma-separated, fields may be quoted with // double-quotes, embedded `""` decodes to a single `"`. CSV data with @@ -141,14 +151,21 @@ function ParseCSVLine(const Line: string): TArray; end; end; +//------------------------------------------------------------------------------ +// FIND INDEX +//------------------------------------------------------------------------------ function FindIndex(const Headers: TArray; const Name: string): Integer; -var I: Integer; +var + I: Integer; begin for I := 0 to High(Headers) do if SameText(Trim(Headers[I]), Name) then Exit(I); Result := -1; end; +//------------------------------------------------------------------------------ +// CONVERT TEXT +//------------------------------------------------------------------------------ function TOBDCatalogCSVImporter.ConvertText(const CsvText: string): string; var Reader: TStringList; @@ -240,6 +257,9 @@ function TOBDCatalogCSVImporter.ConvertText(const CsvText: string): string; end; end; +//------------------------------------------------------------------------------ +// CONVERT +//------------------------------------------------------------------------------ procedure TOBDCatalogCSVImporter.Convert(const CsvPath, JsonPath: string); var CsvText, JsonText: string; diff --git a/src/Services/OBD.OEM.Catalog.JSON.pas b/src/Services/OBD.OEM.Catalog.JSON.pas index cf8165bb..747c94bb 100644 --- a/src/Services/OBD.OEM.Catalog.JSON.pas +++ b/src/Services/OBD.OEM.Catalog.JSON.pas @@ -93,9 +93,11 @@ TOBDOEMDtcRange = record Source: string; end; - /// v3.29 — extended-catalog parser entries. These mirror - /// the public records in OBD.OEM but carry the same source + - /// verified provenance flags as the legacy DID / routine entries. + /// + /// v3.29 — extended-catalog parser entries. These mirror + /// the public records in OBD.OEM but carry the same source + + /// verified provenance flags as the legacy DID / routine entries. + /// TOBDCodingFieldEntry = record Name: string; Label_: string; @@ -218,20 +220,30 @@ TOBDOEMJSONCatalog = class constructor CreateFromText(const JsonText: string); overload; destructor Destroy; override; - /// Cast catalog DIDs to the TOBDOEMDataIdentifier shape - /// expected by TOBDOEMExtensionBase. Drops decoder/source. + /// + /// Cast catalog DIDs to the TOBDOEMDataIdentifier shape + /// expected by TOBDOEMExtensionBase. Drops decoder/source. + /// function AsBaseDIDs: TArray; - /// Cast catalog routines to the base shape. + /// + /// Cast catalog routines to the base shape. + /// function AsBaseRoutines: TArray; - /// Apply the decoder for DID (if any) to Payload. - /// Returns the formatted string, or empty if no decoder / payload too short. + /// + /// Apply the decoder for DID (if any) to Payload. + /// Returns the formatted string, or empty if no decoder / payload too short. + /// function DecodePayload(const DID: Word; const Payload: TBytes): string; - /// Find a DID; returns False if absent. + /// + /// Find a DID; returns False if absent. + /// function FindDID(const DID: Word; out Entry: TOBDOEMDIDEntry): Boolean; - /// ECUs declared by the catalog (top-level ecus array). + /// + /// ECUs declared by the catalog (top-level ecus array). + /// function AsBaseECUs: TArray; property Version: Integer read FVersion; @@ -249,7 +261,9 @@ TOBDOEMJSONCatalog = class function ECUCount: Integer; function ECU(Index: Integer): TOBDOEMECUEntry; - /// v3.29 — extended-catalog accessors. + /// + /// v3.29 — extended-catalog accessors. + /// function CodingBlockCount: Integer; function CodingBlock(Index: Integer): TOBDCodingBlockEntry; function AdaptationCount: Integer; @@ -262,9 +276,11 @@ TOBDOEMJSONCatalog = class function DtcExtended(Index: Integer): TOBDDtcExtendedDataEntry; end; -/// Convert the JSON-side decoder-kind string to the -/// TOBDOEMDecoderKind the public schema uses. Returns -/// dkUnknown for unrecognised strings. +/// +/// Convert the JSON-side decoder-kind string to the +/// TOBDOEMDecoderKind the public schema uses. Returns +/// dkUnknown for unrecognised strings. +/// function ParseOEMDecoderKind(const S: string): TOBDOEMDecoderKind; function ParseCodingFieldKind(const S: string): TOBDCodingFieldKind; function ParseAdaptationKind(const S: string): TOBDAdaptationKind; @@ -277,6 +293,9 @@ implementation uses System.Math, System.NetEncoding; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDOEMJSONCatalog.Create(const FilePath: string); var Text: string; @@ -287,6 +306,9 @@ constructor TOBDOEMJSONCatalog.Create(const FilePath: string); CreateFromText(Text); end; +//------------------------------------------------------------------------------ +// CREATE FROM TEXT +//------------------------------------------------------------------------------ constructor TOBDOEMJSONCatalog.CreateFromText(const JsonText: string); var Value: TJSONValue; @@ -312,6 +334,9 @@ constructor TOBDOEMJSONCatalog.CreateFromText(const JsonText: string); end; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDOEMJSONCatalog.Destroy; var I: Integer; @@ -334,6 +359,9 @@ destructor TOBDOEMJSONCatalog.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// PARSE HEX OR INT +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.ParseHexOrInt(const S: string): Cardinal; var Trimmed: string; @@ -345,6 +373,9 @@ function TOBDOEMJSONCatalog.ParseHexOrInt(const S: string): Cardinal; Result := StrToInt(Trimmed); end; +//------------------------------------------------------------------------------ +// PARSE DECODER +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.ParseDecoder(Obj: TJSONObject): TOBDDecoderSpec; const KindMap: array[0..10] of record Tag: string; Kind: TOBDDecoderKind end = ( @@ -375,7 +406,10 @@ function TOBDOEMJSONCatalog.ParseDecoder(Obj: TJSONObject): TOBDDecoderSpec; KindStr := Obj.GetValue('kind', ''); for I := Low(KindMap) to High(KindMap) do if KindMap[I].Tag = KindStr then - begin Result.Kind := KindMap[I].Kind; Break; end; + begin + Result.Kind := KindMap[I].Kind; + Break; + end; Result.Size := Obj.GetValue('size', 0); Result.Scale := Obj.GetValue('scale', 1.0); @@ -405,6 +439,9 @@ function TOBDOEMJSONCatalog.ParseDecoder(Obj: TJSONObject): TOBDDecoderSpec; end; end; +//------------------------------------------------------------------------------ +// LOAD FROM JSON +//------------------------------------------------------------------------------ procedure TOBDOEMJSONCatalog.LoadFromJSON(Root: TJSONObject); var WMIArr: TJSONArray; @@ -460,6 +497,9 @@ procedure TOBDOEMJSONCatalog.LoadFromJSON(Root: TJSONObject); if Assigned(Arr) then LoadDtcExtended(Arr); end; +//------------------------------------------------------------------------------ +// LOAD DIDS +//------------------------------------------------------------------------------ procedure TOBDOEMJSONCatalog.LoadDIDs(Arr: TJSONArray); var I: Integer; @@ -487,6 +527,9 @@ procedure TOBDOEMJSONCatalog.LoadDIDs(Arr: TJSONArray); end; end; +//------------------------------------------------------------------------------ +// LOAD ROUTINES +//------------------------------------------------------------------------------ procedure TOBDOEMJSONCatalog.LoadRoutines(Arr: TJSONArray); var I: Integer; @@ -513,6 +556,9 @@ procedure TOBDOEMJSONCatalog.LoadRoutines(Arr: TJSONArray); end; end; +//------------------------------------------------------------------------------ +// LOAD ECUS +//------------------------------------------------------------------------------ procedure TOBDOEMJSONCatalog.LoadECUs(Arr: TJSONArray); var I: Integer; @@ -531,6 +577,9 @@ procedure TOBDOEMJSONCatalog.LoadECUs(Arr: TJSONArray); end; end; +//------------------------------------------------------------------------------ +// LOAD DTC RANGES +//------------------------------------------------------------------------------ procedure TOBDOEMJSONCatalog.LoadDtcRanges(Arr: TJSONArray); var I: Integer; @@ -577,6 +626,9 @@ function ParseOEMDecoderKind(const S: string): TOBDOEMDecoderKind; Result := dkUnknown; end; +//------------------------------------------------------------------------------ +// PARSE CODING FIELD KIND +//------------------------------------------------------------------------------ function ParseCodingFieldKind(const S: string): TOBDCodingFieldKind; const Map: array[0..8] of record Tag: string; Kind: TOBDCodingFieldKind end = ( @@ -600,6 +652,9 @@ function ParseCodingFieldKind(const S: string): TOBDCodingFieldKind; Result := cfkUnknown; end; +//------------------------------------------------------------------------------ +// PARSE ADAPTATION KIND +//------------------------------------------------------------------------------ function ParseAdaptationKind(const S: string): TOBDAdaptationKind; const Map: array[0..5] of record Tag: string; Kind: TOBDAdaptationKind end = ( @@ -620,8 +675,12 @@ function ParseAdaptationKind(const S: string): TOBDAdaptationKind; Result := adkUnknown; end; +//------------------------------------------------------------------------------ +// PARSE ACTUATOR RESPONSE KIND +//------------------------------------------------------------------------------ function ParseActuatorResponseKind(const S: string): TOBDActuatorResponseKind; -var Lower: string; +var + Lower: string; begin Lower := LowerCase(S); if Lower = 'boolean' then Exit(arkBoolean); @@ -631,8 +690,12 @@ function ParseActuatorResponseKind(const S: string): TOBDActuatorResponseKind; Result := arkNone; end; +//------------------------------------------------------------------------------ +// PARSE LIVE PIDMODE +//------------------------------------------------------------------------------ function ParseLivePIDMode(const S: string): TOBDLivePIDMode; -var Lower: string; +var + Lower: string; begin Lower := LowerCase(S); if Lower = 'service01' then Exit(lpmService01); @@ -640,6 +703,9 @@ function ParseLivePIDMode(const S: string): TOBDLivePIDMode; Result := lpmUnknown; end; +//------------------------------------------------------------------------------ +// PARSE DTC EXTENDED KIND +//------------------------------------------------------------------------------ function ParseDtcExtendedKind(const S: string): TOBDDtcExtendedDataKind; const Map: array[0..5] of record Tag: string; Kind: TOBDDtcExtendedDataKind end = ( @@ -660,6 +726,9 @@ function ParseDtcExtendedKind(const S: string): TOBDDtcExtendedDataKind; Result := xdkUnknown; end; +//------------------------------------------------------------------------------ +// PARSE ENUM VALUES +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.ParseEnumValues(Obj: TJSONObject; const Key: string): TArray>; var @@ -683,6 +752,9 @@ function TOBDOEMJSONCatalog.ParseEnumValues(Obj: TJSONObject; end; end; +//------------------------------------------------------------------------------ +// LOAD CODING BLOCKS +//------------------------------------------------------------------------------ procedure TOBDOEMJSONCatalog.LoadCodingBlocks(Arr: TJSONArray); var I, J: Integer; @@ -738,6 +810,9 @@ procedure TOBDOEMJSONCatalog.LoadCodingBlocks(Arr: TJSONArray); end; end; +//------------------------------------------------------------------------------ +// LOAD ADAPTATIONS +//------------------------------------------------------------------------------ procedure TOBDOEMJSONCatalog.LoadAdaptations(Arr: TJSONArray); var I: Integer; @@ -770,6 +845,9 @@ procedure TOBDOEMJSONCatalog.LoadAdaptations(Arr: TJSONArray); end; end; +//------------------------------------------------------------------------------ +// LOAD ACTUATOR TESTS +//------------------------------------------------------------------------------ procedure TOBDOEMJSONCatalog.LoadActuatorTests(Arr: TJSONArray); var I: Integer; @@ -800,6 +878,9 @@ procedure TOBDOEMJSONCatalog.LoadActuatorTests(Arr: TJSONArray); end; end; +//------------------------------------------------------------------------------ +// LOAD LIVE PIDS +//------------------------------------------------------------------------------ procedure TOBDOEMJSONCatalog.LoadLivePIDs(Arr: TJSONArray); var I: Integer; @@ -839,6 +920,9 @@ procedure TOBDOEMJSONCatalog.LoadLivePIDs(Arr: TJSONArray); end; end; +//------------------------------------------------------------------------------ +// LOAD DTC EXTENDED +//------------------------------------------------------------------------------ procedure TOBDOEMJSONCatalog.LoadDtcExtended(Arr: TJSONArray); var I: Integer; @@ -891,6 +975,9 @@ function TOBDOEMJSONCatalog.AsBaseDIDs: TArray; end; end; +//------------------------------------------------------------------------------ +// AS BASE ROUTINES +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.AsBaseRoutines: TArray; var I: Integer; @@ -908,6 +995,9 @@ function TOBDOEMJSONCatalog.AsBaseRoutines: TArray; end; end; +//------------------------------------------------------------------------------ +// AS BASE ECUS +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.AsBaseECUs: TArray; var I: Integer; @@ -917,13 +1007,20 @@ function TOBDOEMJSONCatalog.AsBaseECUs: TArray; Result[I] := MakeOEMECU(FECUs[I].Address, FECUs[I].Name, FECUs[I].CommonName); end; +//------------------------------------------------------------------------------ +// FIND DID +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.FindDID(const DID: Word; out Entry: TOBDOEMDIDEntry): Boolean; -var I: Integer; +var + I: Integer; begin for I := 0 to FDIDs.Count - 1 do if FDIDs[I].DID = DID then - begin Entry := FDIDs[I]; Exit(True); end; + begin + Entry := FDIDs[I]; + Exit(True); + end; Result := False; end; @@ -931,12 +1028,16 @@ function TOBDOEMJSONCatalog.FindDID(const DID: Word; // PAYLOAD DECODING //------------------------------------------------------------------------------ function ReadUInt(const Payload: TBytes; Size: Integer): UInt64; -var I: Integer; +var + I: Integer; begin Result := 0; for I := 0 to Size - 1 do Result := (Result shl 8) or Payload[I]; end; +//------------------------------------------------------------------------------ +// READ INT +//------------------------------------------------------------------------------ function ReadInt(const Payload: TBytes; Size: Integer): Int64; var Mask: UInt64; @@ -950,8 +1051,12 @@ function ReadInt(const Payload: TBytes; Size: Integer): Int64; Result := Int64(U); end; +//------------------------------------------------------------------------------ +// FORMAT NUMERIC +//------------------------------------------------------------------------------ function FormatNumeric(Value: Double; const Spec: TOBDDecoderSpec): string; -var Combined: Double; +var + Combined: Double; begin Combined := (Value * Spec.Scale) + Spec.Offset; if Spec.Unit_ <> '' then @@ -960,6 +1065,9 @@ function FormatNumeric(Value: Double; const Spec: TOBDDecoderSpec): string; Result := Format('%.6g', [Combined]); end; +//------------------------------------------------------------------------------ +// HEX FALLBACK +//------------------------------------------------------------------------------ function HexFallback(const Payload: TBytes): string; var I: Integer; @@ -978,6 +1086,9 @@ function HexFallback(const Payload: TBytes): string; end; end; +//------------------------------------------------------------------------------ +// DECODE PAYLOAD +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.DecodePayload(const DID: Word; const Payload: TBytes): string; var @@ -1065,58 +1176,148 @@ function TOBDOEMJSONCatalog.DecodePayload(const DID: Word; end; end; +//------------------------------------------------------------------------------ +// DIDCOUNT +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.DIDCount: Integer; -begin Result := FDIDs.Count; end; +begin + Result := FDIDs.Count; +end; +//------------------------------------------------------------------------------ +// DID +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.DID(Index: Integer): TOBDOEMDIDEntry; -begin Result := FDIDs[Index]; end; +begin + Result := FDIDs[Index]; +end; +//------------------------------------------------------------------------------ +// ROUTINE COUNT +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.RoutineCount: Integer; -begin Result := FRoutines.Count; end; +begin + Result := FRoutines.Count; +end; +//------------------------------------------------------------------------------ +// ROUTINE +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.Routine(Index: Integer): TOBDOEMRoutineEntry; -begin Result := FRoutines[Index]; end; +begin + Result := FRoutines[Index]; +end; +//------------------------------------------------------------------------------ +// DTC RANGE COUNT +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.DtcRangeCount: Integer; -begin Result := FDtcRanges.Count; end; +begin + Result := FDtcRanges.Count; +end; +//------------------------------------------------------------------------------ +// DTC RANGE +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.DtcRange(Index: Integer): TOBDOEMDtcRange; -begin Result := FDtcRanges[Index]; end; +begin + Result := FDtcRanges[Index]; +end; +//------------------------------------------------------------------------------ +// ECUCOUNT +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.ECUCount: Integer; -begin Result := FECUs.Count; end; +begin + Result := FECUs.Count; +end; +//------------------------------------------------------------------------------ +// ECU +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.ECU(Index: Integer): TOBDOEMECUEntry; -begin Result := FECUs[Index]; end; +begin + Result := FECUs[Index]; +end; +//------------------------------------------------------------------------------ +// CODING BLOCK COUNT +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.CodingBlockCount: Integer; -begin Result := FCodingBlocks.Count; end; +begin + Result := FCodingBlocks.Count; +end; +//------------------------------------------------------------------------------ +// CODING BLOCK +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.CodingBlock(Index: Integer): TOBDCodingBlockEntry; -begin Result := FCodingBlocks[Index]; end; +begin + Result := FCodingBlocks[Index]; +end; +//------------------------------------------------------------------------------ +// ADAPTATION COUNT +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.AdaptationCount: Integer; -begin Result := FAdaptations.Count; end; +begin + Result := FAdaptations.Count; +end; +//------------------------------------------------------------------------------ +// ADAPTATION +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.Adaptation(Index: Integer): TOBDAdaptationEntry; -begin Result := FAdaptations[Index]; end; +begin + Result := FAdaptations[Index]; +end; +//------------------------------------------------------------------------------ +// ACTUATOR TEST COUNT +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.ActuatorTestCount: Integer; -begin Result := FActuatorTests.Count; end; +begin + Result := FActuatorTests.Count; +end; +//------------------------------------------------------------------------------ +// ACTUATOR TEST +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.ActuatorTest(Index: Integer): TOBDActuatorTestEntry; -begin Result := FActuatorTests[Index]; end; +begin + Result := FActuatorTests[Index]; +end; +//------------------------------------------------------------------------------ +// LIVE PIDCOUNT +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.LivePIDCount: Integer; -begin Result := FLivePIDs.Count; end; +begin + Result := FLivePIDs.Count; +end; +//------------------------------------------------------------------------------ +// LIVE PID +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.LivePID(Index: Integer): TOBDLivePIDEntry; -begin Result := FLivePIDs[Index]; end; +begin + Result := FLivePIDs[Index]; +end; +//------------------------------------------------------------------------------ +// DTC EXTENDED COUNT +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.DtcExtendedCount: Integer; -begin Result := FDtcExtended.Count; end; +begin + Result := FDtcExtended.Count; +end; +//------------------------------------------------------------------------------ +// DTC EXTENDED +//------------------------------------------------------------------------------ function TOBDOEMJSONCatalog.DtcExtended(Index: Integer): TOBDDtcExtendedDataEntry; -begin Result := FDtcExtended[Index]; end; +begin + Result := FDtcExtended[Index]; +end; end. diff --git a/src/Services/OBD.OEM.Catalog.Loader.pas b/src/Services/OBD.OEM.Catalog.Loader.pas index 9fc92081..549ae430 100644 --- a/src/Services/OBD.OEM.Catalog.Loader.pas +++ b/src/Services/OBD.OEM.Catalog.Loader.pas @@ -47,7 +47,8 @@ function ResolveCatalogPath(const FileName: string): string; /// fallback continues to work. /// procedure MergeCatalogJSON(const FileName: string; - var DIDs: TArray; + var + DIDs: TArray; var Routines: TArray); overload; /// @@ -57,8 +58,10 @@ procedure MergeCatalogJSON(const FileName: string; /// appended. /// procedure MergeCatalogJSON(const FileName: string; - var DIDs: TArray; - var Routines: TArray; + var + DIDs: TArray; + var + Routines: TArray; var ECUs: TArray); overload; /// @@ -71,11 +74,16 @@ procedure MergeCatalogJSON(const FileName: string; /// no-ops when the file isn't found. /// procedure MergeExtendedCatalogJSON(const FileName: string; - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); /// /// v3.31 — JSON-driven VIN routing. Returns True if the catalog's @@ -90,16 +98,25 @@ implementation var GCatalogSearchPath: string = ''; +//------------------------------------------------------------------------------ +// SET CATALOG SEARCH PATH +//------------------------------------------------------------------------------ procedure SetCatalogSearchPath(const Path: string); begin GCatalogSearchPath := Path; end; +//------------------------------------------------------------------------------ +// EXECUTABLE DIR +//------------------------------------------------------------------------------ function ExecutableDir: string; begin Result := TPath.GetDirectoryName(ParamStr(0)); end; +//------------------------------------------------------------------------------ +// RESOLVE CATALOG PATH +//------------------------------------------------------------------------------ function ResolveCatalogPath(const FileName: string): string; const // Vehicle-class subdirectories introduced in v3.77 (Phase B): @@ -145,6 +162,9 @@ function ResolveCatalogPath(const FileName: string): string; Result := ''; end; +//------------------------------------------------------------------------------ +// MERGE DIDS +//------------------------------------------------------------------------------ procedure MergeDIDs(var Existing: TArray; const Loaded: TArray); var @@ -167,6 +187,9 @@ procedure MergeDIDs(var Existing: TArray; end; end; +//------------------------------------------------------------------------------ +// MERGE ROUTINES +//------------------------------------------------------------------------------ procedure MergeRoutines(var Existing: TArray; const Loaded: TArray); var @@ -188,6 +211,9 @@ procedure MergeRoutines(var Existing: TArray; end; end; +//------------------------------------------------------------------------------ +// MERGE ECUS +//------------------------------------------------------------------------------ procedure MergeECUs(var Existing: TArray; const Loaded: TArray); var @@ -209,9 +235,14 @@ procedure MergeECUs(var Existing: TArray; end; end; +//------------------------------------------------------------------------------ +// MERGE CATALOG JSON +//------------------------------------------------------------------------------ procedure MergeCatalogJSON(const FileName: string; - var DIDs: TArray; - var Routines: TArray); + var + DIDs: TArray; + var + Routines: TArray); var Discard: TArray; begin @@ -219,10 +250,16 @@ procedure MergeCatalogJSON(const FileName: string; MergeCatalogJSON(FileName, DIDs, Routines, Discard); end; +//------------------------------------------------------------------------------ +// MERGE CATALOG JSON +//------------------------------------------------------------------------------ procedure MergeCatalogJSON(const FileName: string; - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); var Path: string; Catalog: TOBDOEMJSONCatalog; @@ -248,6 +285,10 @@ procedure MergeCatalogJSON(const FileName: string; //============================================================================== // v3.29 Phase A — extended-catalog merge //============================================================================== + +//------------------------------------------------------------------------------ +// CONVERT CODING BLOCK +//------------------------------------------------------------------------------ function ConvertCodingBlock(const E: TOBDCodingBlockEntry): TOBDOEMCodingBlock; var I: Integer; @@ -281,6 +322,9 @@ function ConvertCodingBlock(const E: TOBDCodingBlockEntry): TOBDOEMCodingBlock; end; end; +//------------------------------------------------------------------------------ +// CONVERT ADAPTATION +//------------------------------------------------------------------------------ function ConvertAdaptation(const E: TOBDAdaptationEntry): TOBDOEMAdaptation; begin Result := Default(TOBDOEMAdaptation); @@ -296,6 +340,9 @@ function ConvertAdaptation(const E: TOBDAdaptationEntry): TOBDOEMAdaptation; Result.EnumValues := E.EnumValues; end; +//------------------------------------------------------------------------------ +// CONVERT ACTUATOR TEST +//------------------------------------------------------------------------------ function ConvertActuatorTest(const E: TOBDActuatorTestEntry): TOBDOEMActuatorTest; begin Result := Default(TOBDOEMActuatorTest); @@ -309,6 +356,9 @@ function ConvertActuatorTest(const E: TOBDActuatorTestEntry): TOBDOEMActuatorTes Result.ExpectedResponseLabel := E.ExpectedResponseLabel; end; +//------------------------------------------------------------------------------ +// CONVERT LIVE PID +//------------------------------------------------------------------------------ function ConvertLivePID(const E: TOBDLivePIDEntry): TOBDOEMLivePID; begin Result := Default(TOBDOEMLivePID); @@ -324,6 +374,9 @@ function ConvertLivePID(const E: TOBDLivePIDEntry): TOBDOEMLivePID; Result.Unit_ := E.Unit_; end; +//------------------------------------------------------------------------------ +// CONVERT DTC EXTENDED +//------------------------------------------------------------------------------ function ConvertDtcExtended(const E: TOBDDtcExtendedDataEntry): TOBDDtcExtendedDataRecord; begin Result := Default(TOBDDtcExtendedDataRecord); @@ -337,6 +390,9 @@ function ConvertDtcExtended(const E: TOBDDtcExtendedDataEntry): TOBDDtcExtendedD Result.Unit_ := E.Unit_; end; +//------------------------------------------------------------------------------ +// MERGE CODING BLOCKS +//------------------------------------------------------------------------------ procedure MergeCodingBlocks(var Existing: TArray; const Loaded: TArray); var I, J: Integer; Found: Boolean; @@ -346,11 +402,18 @@ procedure MergeCodingBlocks(var Existing: TArray; Found := False; for J := 0 to High(Existing) do if Existing[J].DataIdentifier = Loaded[I].DataIdentifier then - begin Existing[J] := Loaded[I]; Found := True; Break; end; + begin + Existing[J] := Loaded[I]; + Found := True; + Break; + end; if not Found then Existing := Existing + [Loaded[I]]; end; end; +//------------------------------------------------------------------------------ +// MERGE ADAPTATIONS +//------------------------------------------------------------------------------ procedure MergeAdaptations(var Existing: TArray; const Loaded: TArray); var I, J: Integer; Found: Boolean; @@ -361,11 +424,18 @@ procedure MergeAdaptations(var Existing: TArray; for J := 0 to High(Existing) do if (Existing[J].Channel = Loaded[I].Channel) and (Existing[J].EcuAddress = Loaded[I].EcuAddress) then - begin Existing[J] := Loaded[I]; Found := True; Break; end; + begin + Existing[J] := Loaded[I]; + Found := True; + Break; + end; if not Found then Existing := Existing + [Loaded[I]]; end; end; +//------------------------------------------------------------------------------ +// MERGE ACTUATOR TESTS +//------------------------------------------------------------------------------ procedure MergeActuatorTests(var Existing: TArray; const Loaded: TArray); var I, J: Integer; Found: Boolean; @@ -376,11 +446,18 @@ procedure MergeActuatorTests(var Existing: TArray; for J := 0 to High(Existing) do if (Existing[J].Identifier = Loaded[I].Identifier) and (Existing[J].EcuAddress = Loaded[I].EcuAddress) then - begin Existing[J] := Loaded[I]; Found := True; Break; end; + begin + Existing[J] := Loaded[I]; + Found := True; + Break; + end; if not Found then Existing := Existing + [Loaded[I]]; end; end; +//------------------------------------------------------------------------------ +// MERGE LIVE PIDS +//------------------------------------------------------------------------------ procedure MergeLivePIDs(var Existing: TArray; const Loaded: TArray); var I, J: Integer; Found: Boolean; @@ -392,11 +469,18 @@ procedure MergeLivePIDs(var Existing: TArray; if (Existing[J].Mode = Loaded[I].Mode) and (Existing[J].PID = Loaded[I].PID) and (Existing[J].EcuAddress = Loaded[I].EcuAddress) then - begin Existing[J] := Loaded[I]; Found := True; Break; end; + begin + Existing[J] := Loaded[I]; + Found := True; + Break; + end; if not Found then Existing := Existing + [Loaded[I]]; end; end; +//------------------------------------------------------------------------------ +// MERGE DTC EXTENDED +//------------------------------------------------------------------------------ procedure MergeDtcExtended(var Existing: TArray; const Loaded: TArray); var I, J: Integer; Found: Boolean; @@ -407,17 +491,29 @@ procedure MergeDtcExtended(var Existing: TArray; for J := 0 to High(Existing) do if (Existing[J].DtcCode = Loaded[I].DtcCode) and (Existing[J].RecordNumber = Loaded[I].RecordNumber) then - begin Existing[J] := Loaded[I]; Found := True; Break; end; + begin + Existing[J] := Loaded[I]; + Found := True; + Break; + end; if not Found then Existing := Existing + [Loaded[I]]; end; end; +//------------------------------------------------------------------------------ +// MERGE EXTENDED CATALOG JSON +//------------------------------------------------------------------------------ procedure MergeExtendedCatalogJSON(const FileName: string; - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); var Path: string; Catalog: TOBDOEMJSONCatalog; @@ -465,6 +561,9 @@ procedure MergeExtendedCatalogJSON(const FileName: string; end; end; +//------------------------------------------------------------------------------ +// VINMATCHES CATALOG +//------------------------------------------------------------------------------ function VINMatchesCatalog(const FileName, VIN: string): Boolean; var Path, WMI, CatalogWMI: string; diff --git a/src/Services/OBD.OEM.Coding.AuditLog.pas b/src/Services/OBD.OEM.Coding.AuditLog.pas new file mode 100644 index 00000000..ae44c9be --- /dev/null +++ b/src/Services/OBD.OEM.Coding.AuditLog.pas @@ -0,0 +1,431 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.Coding.AuditLog.pas +// CONTENTS : Tamper-evident HMAC-chained coding audit log +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.Coding.AuditLog; + +interface + +uses + System.SysUtils, System.Classes, System.JSON, System.IOUtils, + System.DateUtils, System.Hash; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDCodingAuditLog = class(Exception); + + TOBDCodingAuditRecord = record + /// + /// Timestamp. + /// + Timestamp: TDateTime; + /// + /// Vin. + /// + VIN: string; + /// + /// Ecu. + /// + ECU: string; + /// + /// Block. + /// + Block: string; + BeforeHex: string; // hex-encoded current bytes + AfterHex: string; // hex-encoded target bytes + /// + /// Operator. + /// + Operator: string; + /// + /// Reason. + /// + Reason: string; + end; + + TOBDCodingAuditChainResult = record + /// + /// Total records. + /// + TotalRecords: Integer; + /// + /// Verified. + /// + Verified: Boolean; + FirstTamperLine: Integer; // 1-based; 0 if Verified + /// + /// Reason. + /// + Reason: string; + end; + + TOBDCodingAuditLog = class + private + FPath: string; + FKey: TBytes; + FPrevHmac: TBytes; + FInitialised: Boolean; + /// + /// Ensure initialised. + /// + procedure EnsureInitialised; + /// + /// Canonical body. + /// + function CanonicalBody(const Rec: TOBDCodingAuditRecord): string; + /// + /// Compute hmac. + /// + function ComputeHmac(const Prev: TBytes; const Body: string): TBytes; + /// + /// Hex encode. + /// + function HexEncode(const Bytes: TBytes): string; + /// + /// Hex decode. + /// + function HexDecode(const S: string): TBytes; + /// + /// Load last hmac. + /// + function LoadLastHmac: TBytes; + public + /// + /// Create. + /// + constructor Create(const APath: string; const AKey: TBytes); + /// + /// Destroy. + /// + destructor Destroy; override; + + /// + /// Append a record. The HMAC binds it to the previous + /// record's HMAC, forming a chain. + /// + procedure Append(const Rec: TOBDCodingAuditRecord); + + /// + /// Walk the file from the start; returns success only when + /// every record's HMAC matches the recomputed value. + /// + function Verify: TOBDCodingAuditChainResult; + + /// + /// Path. + /// + property Path: string read FPath; + end; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDCodingAuditLog.Create(const APath: string; const AKey: TBytes); +begin + // Initialize the inherited class + inherited Create; + if Length(AKey) = 0 then + raise EOBDCodingAuditLog.Create('Audit log requires a non-empty HMAC key'); + FPath := APath; + FKey := Copy(AKey); +end; + +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ +destructor TOBDCodingAuditLog.Destroy; +begin + // Call the inherited handler + inherited; +end; + +//------------------------------------------------------------------------------ +// ENSURE INITIALISED +//------------------------------------------------------------------------------ +procedure TOBDCodingAuditLog.EnsureInitialised; +begin + if FInitialised then Exit; + if TFile.Exists(FPath) then + FPrevHmac := LoadLastHmac + else + begin + // Allocate FPrevHmac + SetLength(FPrevHmac, 32); + FillChar(FPrevHmac[0], 32, 0); + end; + FInitialised := True; +end; + +//------------------------------------------------------------------------------ +// HEX ENCODE +//------------------------------------------------------------------------------ +function TOBDCodingAuditLog.HexEncode(const Bytes: TBytes): string; + +//------------------------------------------------------------------------------ +// CONSTANTS +//------------------------------------------------------------------------------ +const + HexChars: array[0..15] of Char = + ('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'); +var + I: Integer; +begin + // Allocate Result + SetLength(Result, Length(Bytes) * 2); + for I := 0 to High(Bytes) do + begin + Result[I * 2 + 1] := HexChars[Bytes[I] shr 4]; + Result[I * 2 + 2] := HexChars[Bytes[I] and $0F]; + end; +end; + +//------------------------------------------------------------------------------ +// HEX DECODE +//------------------------------------------------------------------------------ +function TOBDCodingAuditLog.HexDecode(const S: string): TBytes; + + function NibbleOf(C: Char): Byte; + begin + case UpCase(C) of + '0'..'9': Result := Ord(C) - Ord('0'); + 'A'..'F': Result := Ord(UpCase(C)) - Ord('A') + 10; + else + raise EOBDCodingAuditLog.CreateFmt('Bad hex character: %s', [C]); + end; + end; + +var + I: Integer; +begin + if Odd(Length(S)) then + raise EOBDCodingAuditLog.Create('Hex string has odd length'); + // Allocate Result + SetLength(Result, Length(S) div 2); + for I := 0 to High(Result) do + Result[I] := (NibbleOf(S[I * 2 + 1]) shl 4) or NibbleOf(S[I * 2 + 2]); +end; + +//------------------------------------------------------------------------------ +// CANONICAL BODY +//------------------------------------------------------------------------------ +function TOBDCodingAuditLog.CanonicalBody(const Rec: TOBDCodingAuditRecord): string; +var + Json: TJSONObject; +begin + // Field order is fixed by the source code so the canonicalisation is + // deterministic across builds. Adding new fields is a breaking change + // by design; rotate keys / start a new chain when extending. + Json := TJSONObject.Create; + try + Json.AddPair('ts', DateToISO8601(Rec.Timestamp, True)); + Json.AddPair('vin', Rec.VIN); + Json.AddPair('ecu', Rec.ECU); + Json.AddPair('block', Rec.Block); + Json.AddPair('before', Rec.BeforeHex); + Json.AddPair('after', Rec.AfterHex); + Json.AddPair('operator', Rec.Operator); + Json.AddPair('reason', Rec.Reason); + Result := Json.ToJSON; + finally + // Free Json + Json.Free; + end; +end; + +//------------------------------------------------------------------------------ +// COMPUTE HMAC +//------------------------------------------------------------------------------ +function TOBDCodingAuditLog.ComputeHmac(const Prev: TBytes; const Body: string): TBytes; +var + Input: TBytes; + BodyBytes: TBytes; + Hex: string; +begin + BodyBytes := TEncoding.UTF8.GetBytes(Body); + // Allocate Input + SetLength(Input, Length(Prev) + Length(BodyBytes)); + if Length(Prev) > 0 then + Move(Prev[0], Input[0], Length(Prev)); + if Length(BodyBytes) > 0 then + Move(BodyBytes[0], Input[Length(Prev)], Length(BodyBytes)); + Hex := THashSHA2.GetHMAC(TEncoding.UTF8.GetString(Input), + TEncoding.UTF8.GetString(FKey), + SHA256); + Result := HexDecode(Hex); +end; + +//------------------------------------------------------------------------------ +// LOAD LAST HMAC +//------------------------------------------------------------------------------ +function TOBDCodingAuditLog.LoadLastHmac: TBytes; +var + Reader: TStreamReader; + Last, Line: string; + Json: TJSONObject; + HmacStr: string; +begin + // Allocate Result + SetLength(Result, 32); + FillChar(Result[0], 32, 0); + Last := ''; + // Create Reader + Reader := TStreamReader.Create(FPath, TEncoding.UTF8); + try + while not Reader.EndOfStream do + begin + Line := Reader.ReadLine; + if Trim(Line) <> '' then Last := Line; + end; + finally + // Free Reader + Reader.Free; + end; + if Last = '' then Exit; + Json := TJSONObject.ParseJSONValue(Last) as TJSONObject; + if Json = nil then Exit; + try + if Json.TryGetValue('hmac', HmacStr) then + Result := HexDecode(HmacStr); + finally + // Free Json + Json.Free; + end; +end; + +//------------------------------------------------------------------------------ +// APPEND +//------------------------------------------------------------------------------ +procedure TOBDCodingAuditLog.Append(const Rec: TOBDCodingAuditRecord); +var + Body, Line: string; + Hmac: TBytes; + Json: TJSONObject; +begin + EnsureInitialised; + Body := CanonicalBody(Rec); + Hmac := ComputeHmac(FPrevHmac, Body); + // Create Json + Json := TJSONObject.Create; + try + // Embed the body inline so the file is one canonical document per + // line. Verify recomputes the body from the embedded fields. + Json.AddPair('ts', DateToISO8601(Rec.Timestamp, True)); + Json.AddPair('vin', Rec.VIN); + Json.AddPair('ecu', Rec.ECU); + Json.AddPair('block', Rec.Block); + Json.AddPair('before', Rec.BeforeHex); + Json.AddPair('after', Rec.AfterHex); + Json.AddPair('operator', Rec.Operator); + Json.AddPair('reason', Rec.Reason); + Json.AddPair('hmac', HexEncode(Hmac)); + Line := Json.ToJSON; + finally + // Free Json + Json.Free; + end; + TFile.AppendAllText(FPath, Line + sLineBreak, TEncoding.UTF8); + FPrevHmac := Hmac; +end; + +//------------------------------------------------------------------------------ +// VERIFY +//------------------------------------------------------------------------------ +function TOBDCodingAuditLog.Verify: TOBDCodingAuditChainResult; +var + Reader: TStreamReader; + Line: string; + LineNum: Integer; + Json: TJSONObject; + Rec: TOBDCodingAuditRecord; + Body: string; + Stored, Computed: TBytes; + Prev: TBytes; + HmacStr: string; + TS: string; +begin + Result := Default(TOBDCodingAuditChainResult); + Result.Verified := True; + + if not TFile.Exists(FPath) then + begin + Result.Reason := 'log file does not exist'; + Result.TotalRecords := 0; + Exit; + end; + + // Allocate Prev + SetLength(Prev, 32); + FillChar(Prev[0], 32, 0); + LineNum := 0; + // Create Reader + Reader := TStreamReader.Create(FPath, TEncoding.UTF8); + try + while not Reader.EndOfStream do + begin + Line := Reader.ReadLine; + Inc(LineNum); + if Trim(Line) = '' then Continue; + Json := TJSONObject.ParseJSONValue(Line) as TJSONObject; + if Json = nil then + begin + Result.Verified := False; + Result.FirstTamperLine := LineNum; + Result.Reason := 'malformed JSON on line ' + IntToStr(LineNum); + Exit; + end; + try + if not Json.TryGetValue('hmac', HmacStr) then + begin + Result.Verified := False; + Result.FirstTamperLine := LineNum; + Result.Reason := 'missing hmac on line ' + IntToStr(LineNum); + Exit; + end; + Json.TryGetValue('ts', TS); + Rec.Timestamp := ISO8601ToDate(TS, True); + Rec.VIN := Json.GetValue('vin', ''); + Rec.ECU := Json.GetValue('ecu', ''); + Rec.Block := Json.GetValue('block', ''); + Rec.BeforeHex := Json.GetValue('before', ''); + Rec.AfterHex := Json.GetValue('after', ''); + Rec.Operator := Json.GetValue('operator', ''); + Rec.Reason := Json.GetValue('reason', ''); + Body := CanonicalBody(Rec); + Stored := HexDecode(HmacStr); + Computed := ComputeHmac(Prev, Body); + if (Length(Stored) <> Length(Computed)) or + not CompareMem(@Stored[0], @Computed[0], Length(Stored)) then + begin + Result.Verified := False; + Result.FirstTamperLine := LineNum; + Result.Reason := 'hmac mismatch on line ' + IntToStr(LineNum); + Exit; + end; + Prev := Computed; + Inc(Result.TotalRecords); + finally + // Free Json + Json.Free; + end; + end; + finally + // Free Reader + Reader.Free; + end; +end; + +end. diff --git a/src/Services/OBD.OEM.Coding.BMW.pas b/src/Services/OBD.OEM.Coding.BMW.pas index c3b430c9..ca2d30e8 100644 --- a/src/Services/OBD.OEM.Coding.BMW.pas +++ b/src/Services/OBD.OEM.Coding.BMW.pas @@ -44,15 +44,21 @@ TOBDBMWFA = class procedure RemoveOption(const Code: string); procedure Clear; - /// Number of distinct options in the order. + /// + /// Number of distinct options in the order. + /// function Count: Integer; - /// Snapshot the option list (sorted ascending). + /// + /// Snapshot the option list (sorted ascending). + /// function Tokens: TArray; - /// Render in the canonical comma-separated form. Tokens - /// are sorted ascending so equal orders always produce equal - /// strings — easy to diff in audit logs. + /// + /// Render in the canonical comma-separated form. Tokens + /// are sorted ascending so equal orders always produce equal + /// strings — easy to diff in audit logs. + /// function ToString: string; reintroduce; end; @@ -69,21 +75,29 @@ TOBDBMWIStufe = record Month: Byte; // MM (1..12) Build: Word; // 1..9999 - /// Parse a wire-format I-Stufe; throws on malformed input. + /// + /// Parse a wire-format I-Stufe; throws on malformed input. + /// class function Parse(const S: string): TOBDBMWIStufe; static; - /// Render in canonical form. + /// + /// Render in canonical form. + /// function ToString: string; - /// Lexicographic comparison: returns -1 / 0 / 1 like - /// CompareStr. Project is the primary key; ties break on - /// Year, then Month, then Build. + /// + /// Lexicographic comparison: returns -1 / 0 / 1 like + /// CompareStr. Project is the primary key; ties break on + /// Year, then Month, then Build. + /// function CompareTo(const Other: TOBDBMWIStufe): Integer; - /// True if this I-Stufe is at least as new as Other - /// for the same project. Cross-project comparison is undefined and - /// returns False (the caller should never compare an F-series to - /// a G-series I-Stufe). + /// + /// True if this I-Stufe is at least as new as Other + /// for the same project. Cross-project comparison is undefined and + /// returns False (the caller should never compare an F-series to + /// a G-series I-Stufe). + /// function AtLeast(const Other: TOBDBMWIStufe): Boolean; end; @@ -95,12 +109,19 @@ implementation //============================================================================== // TOBDBMWFA //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDBMWFA.Create; begin inherited Create; FOptions := TList.Create; end; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDBMWFA.Create(const FAString: string); var Parts: TArray; @@ -115,12 +136,18 @@ constructor TOBDBMWFA.Create(const FAString: string); AddOption(Token); end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDBMWFA.Destroy; begin FOptions.Free; inherited; end; +//------------------------------------------------------------------------------ +// NORMALIZE CODE +//------------------------------------------------------------------------------ function TOBDBMWFA.NormalizeCode(const Code: string): string; begin Result := UpperCase(Trim(Code)); @@ -128,6 +155,9 @@ function TOBDBMWFA.NormalizeCode(const Code: string): string; raise EOBDCodingError.Create('FA option code cannot be empty'); end; +//------------------------------------------------------------------------------ +// HAS OPTION +//------------------------------------------------------------------------------ function TOBDBMWFA.HasOption(const Code: string): Boolean; var Norm, Existing: string; @@ -138,6 +168,9 @@ function TOBDBMWFA.HasOption(const Code: string): Boolean; Result := False; end; +//------------------------------------------------------------------------------ +// ADD OPTION +//------------------------------------------------------------------------------ procedure TOBDBMWFA.AddOption(const Code: string); var Norm: string; @@ -146,23 +179,42 @@ procedure TOBDBMWFA.AddOption(const Code: string); if not FOptions.Contains(Norm) then FOptions.Add(Norm); end; +//------------------------------------------------------------------------------ +// REMOVE OPTION +//------------------------------------------------------------------------------ procedure TOBDBMWFA.RemoveOption(const Code: string); begin FOptions.Remove(NormalizeCode(Code)); end; +//------------------------------------------------------------------------------ +// CLEAR +//------------------------------------------------------------------------------ procedure TOBDBMWFA.Clear; -begin FOptions.Clear; end; +begin + FOptions.Clear; +end; +//------------------------------------------------------------------------------ +// COUNT +//------------------------------------------------------------------------------ function TOBDBMWFA.Count: Integer; -begin Result := FOptions.Count; end; +begin + Result := FOptions.Count; +end; +//------------------------------------------------------------------------------ +// TOKENS +//------------------------------------------------------------------------------ function TOBDBMWFA.Tokens: TArray; begin Result := FOptions.ToArray; TArray.Sort(Result, TComparer.Default); end; +//------------------------------------------------------------------------------ +// TO STRING +//------------------------------------------------------------------------------ function TOBDBMWFA.ToString: string; begin Result := string.Join(',', Tokens); @@ -171,6 +223,10 @@ function TOBDBMWFA.ToString: string; //============================================================================== // TOBDBMWIStufe //============================================================================== + +//------------------------------------------------------------------------------ +// PARSE +//------------------------------------------------------------------------------ class function TOBDBMWIStufe.Parse(const S: string): TOBDBMWIStufe; var Parts: TArray; @@ -200,11 +256,17 @@ class function TOBDBMWIStufe.Parse(const S: string): TOBDBMWIStufe; 'I-Stufe build must be a positive integer: "%s"', [Parts[3]]); end; +//------------------------------------------------------------------------------ +// TO STRING +//------------------------------------------------------------------------------ function TOBDBMWIStufe.ToString: string; begin Result := Format('%s-%.2d-%.2d-%.3d', [Project, Year, Month, Build]); end; +//------------------------------------------------------------------------------ +// COMPARE TO +//------------------------------------------------------------------------------ function TOBDBMWIStufe.CompareTo(const Other: TOBDBMWIStufe): Integer; begin Result := CompareStr(Project, Other.Project); @@ -216,6 +278,9 @@ function TOBDBMWIStufe.CompareTo(const Other: TOBDBMWIStufe): Integer; Result := Integer(Build) - Integer(Other.Build); end; +//------------------------------------------------------------------------------ +// AT LEAST +//------------------------------------------------------------------------------ function TOBDBMWIStufe.AtLeast(const Other: TOBDBMWIStufe): Boolean; begin if not SameText(Project, Other.Project) then Exit(False); diff --git a/src/Services/OBD.OEM.Coding.Common.pas b/src/Services/OBD.OEM.Coding.Common.pas index 8bde6ca2..e9830a1b 100644 --- a/src/Services/OBD.OEM.Coding.Common.pas +++ b/src/Services/OBD.OEM.Coding.Common.pas @@ -23,12 +23,16 @@ interface OBD.OEM; const - /// UDS SID 0x2E — WriteDataByIdentifier. + /// + /// UDS SID 0x2E — WriteDataByIdentifier. + /// UDS_SID_WRITE_DATA_BY_IDENTIFIER = $2E; type - /// Canonical coding-function kinds. The set of writeable - /// DIDs a coding tool typically exposes as one-tap actions. + /// + /// Canonical coding-function kinds. The set of writeable + /// DIDs a coding tool typically exposes as one-tap actions. + /// TOBDCodingFunctionKind = ( cfUnknown, cfVehicleOrder, // BMW FA / Bentley commission / SALAPA option codes @@ -52,8 +56,10 @@ interface cfSofttopAuto // Convertible / soft-top automatic operation ); - /// Resolved coding function — one writeable DID the OEM - /// extension exposes that matches a canonical kind. + /// + /// Resolved coding function — one writeable DID the OEM + /// extension exposes that matches a canonical kind. + /// TOBDCodingFunction = record Kind: TOBDCodingFunctionKind; DataIdentifier: Word; @@ -62,7 +68,9 @@ TOBDCodingFunction = record EcuAddress: Word; end; - /// Registry of canonical kind → DID-name-token mappings. + /// + /// Registry of canonical kind → DID-name-token mappings. + /// TOBDCodingFunctionRegistry = class strict private class var FTokens: TObjectDictionary>; @@ -70,50 +78,69 @@ TOBDCodingFunctionRegistry = class class procedure RegisterTokens(const Kind: TOBDCodingFunctionKind; const Names: array of string); public - /// True if Name matches a known token for Kind - /// (case-insensitive substring match). + /// + /// True if Name matches a known token for Kind + /// (case-insensitive substring match). + /// class function NameMatchesKind(const Name: string; const Kind: TOBDCodingFunctionKind): Boolean; - /// Best-guess canonical kind for an arbitrary DID - /// name. Returns cfUnknown when no token matches. + /// + /// Best-guess canonical kind for an arbitrary DID + /// name. Returns cfUnknown when no token matches. + /// class function ClassifyName(const Name: string): TOBDCodingFunctionKind; end; -/// Find the first DID in the OEM extension's catalog that -/// maps to Kind. Returns True + populates Func; -/// returns False when no matching DID exists. +/// +/// Find the first DID in the OEM extension's catalog that +/// maps to Kind. Returns True + populates Func; +/// returns False when no matching DID exists. +/// function FindCodingFunction(const Ext: IOBDOEMExtension; const Kind: TOBDCodingFunctionKind; out Func: TOBDCodingFunction): Boolean; -/// Discover every coding-classifiable DID on the OEM -/// extension. Useful for populating a "Coding" menu without -/// hard-coding which OEMs support what. +/// +/// Discover every coding-classifiable DID on the OEM +/// extension. Useful for populating a "Coding" menu without +/// hard-coding which OEMs support what. +/// function ListCodingFunctions( const Ext: IOBDOEMExtension): TArray; -/// Build the WriteDataByIdentifier UDS frame -/// (2E DID-hi DID-lo data...). +/// +/// Build the WriteDataByIdentifier UDS frame +/// (2E DID-hi DID-lo data...). +/// function BuildWriteDataByIdentifier(const DID: Word; const Data: TBytes): TBytes; -/// Build a coding-write UDS frame for a resolved coding -/// function. +/// +/// Build a coding-write UDS frame for a resolved coding +/// function. +/// function BuildCodingFrame(const Func: TOBDCodingFunction; const Data: TBytes): TBytes; -/// Parse a positive WriteDataByIdentifier response -/// (6E DID-hi DID-lo). Returns True when the response -/// confirms the requested DID, False on negative response or -/// SID/DID mismatch. +/// +/// Parse a positive WriteDataByIdentifier response +/// (6E DID-hi DID-lo). Returns True when the response +/// confirms the requested DID, False on negative response or +/// SID/DID mismatch. +/// function ParseCodingResponse(const Response: TBytes; const ExpectedDID: Word): Boolean; -/// Display label for a coding-function kind (used by UI). +/// +/// Display label for a coding-function kind (used by UI). +/// function CodingFunctionKindName(const Kind: TOBDCodingFunctionKind): string; implementation +//------------------------------------------------------------------------------ +// ENSURE INITIALIZED +//------------------------------------------------------------------------------ class procedure TOBDCodingFunctionRegistry.EnsureInitialized; begin if FTokens = nil then @@ -163,6 +190,9 @@ class procedure TOBDCodingFunctionRegistry.EnsureInitialized; end; end; +//------------------------------------------------------------------------------ +// REGISTER TOKENS +//------------------------------------------------------------------------------ class procedure TOBDCodingFunctionRegistry.RegisterTokens( const Kind: TOBDCodingFunctionKind; const Names: array of string); var @@ -174,6 +204,9 @@ class procedure TOBDCodingFunctionRegistry.RegisterTokens( FTokens.AddOrSetValue(Kind, L); end; +//------------------------------------------------------------------------------ +// NAME MATCHES KIND +//------------------------------------------------------------------------------ class function TOBDCodingFunctionRegistry.NameMatchesKind( const Name: string; const Kind: TOBDCodingFunctionKind): Boolean; var @@ -188,6 +221,9 @@ class function TOBDCodingFunctionRegistry.NameMatchesKind( if Pos(Token, Lower) > 0 then Exit(True); end; +//------------------------------------------------------------------------------ +// CLASSIFY NAME +//------------------------------------------------------------------------------ class function TOBDCodingFunctionRegistry.ClassifyName( const Name: string): TOBDCodingFunctionKind; var @@ -199,6 +235,9 @@ class function TOBDCodingFunctionRegistry.ClassifyName( Result := cfUnknown; end; +//------------------------------------------------------------------------------ +// CODING FUNCTION KIND NAME +//------------------------------------------------------------------------------ function CodingFunctionKindName(const Kind: TOBDCodingFunctionKind): string; begin case Kind of @@ -226,6 +265,9 @@ function CodingFunctionKindName(const Kind: TOBDCodingFunctionKind): string; end; end; +//------------------------------------------------------------------------------ +// FIND CODING FUNCTION +//------------------------------------------------------------------------------ function FindCodingFunction(const Ext: IOBDOEMExtension; const Kind: TOBDCodingFunctionKind; out Func: TOBDCodingFunction): Boolean; @@ -247,6 +289,9 @@ function FindCodingFunction(const Ext: IOBDOEMExtension; end; end; +//------------------------------------------------------------------------------ +// LIST CODING FUNCTIONS +//------------------------------------------------------------------------------ function ListCodingFunctions( const Ext: IOBDOEMExtension): TArray; var @@ -276,6 +321,9 @@ function ListCodingFunctions( end; end; +//------------------------------------------------------------------------------ +// BUILD WRITE DATA BY IDENTIFIER +//------------------------------------------------------------------------------ function BuildWriteDataByIdentifier(const DID: Word; const Data: TBytes): TBytes; var @@ -290,12 +338,18 @@ function BuildWriteDataByIdentifier(const DID: Word; Result[HeaderLen + I] := Data[I]; end; +//------------------------------------------------------------------------------ +// BUILD CODING FRAME +//------------------------------------------------------------------------------ function BuildCodingFrame(const Func: TOBDCodingFunction; const Data: TBytes): TBytes; begin Result := BuildWriteDataByIdentifier(Func.DataIdentifier, Data); end; +//------------------------------------------------------------------------------ +// PARSE CODING RESPONSE +//------------------------------------------------------------------------------ function ParseCodingResponse(const Response: TBytes; const ExpectedDID: Word): Boolean; begin diff --git a/src/Services/OBD.OEM.Coding.Diff.pas b/src/Services/OBD.OEM.Coding.Diff.pas new file mode 100644 index 00000000..491cbd7a --- /dev/null +++ b/src/Services/OBD.OEM.Coding.Diff.pas @@ -0,0 +1,340 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.Coding.Diff.pas +// CONTENTS : Coding diff and dry-run with explicit confirm +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.Coding.Diff; + +interface + +uses + System.SysUtils, System.Classes, System.Generics.Collections, + + OBD.OEM.Coding; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDCodingDiffError = class(Exception); + + /// + /// Optional named-field schema. Each entry describes a bit- + /// or byte-range in the coding payload so the diff can render + /// human-readable field changes ("LongCoding[7].bit3: false -> true: + /// CornerLights") rather than just byte indices. + /// + TOBDCodingFieldKind = (cfkBit, cfkByte, cfkUInt16); + + TOBDCodingFieldSchema = record + /// + /// Name. + /// + Name: string; + /// + /// Description. + /// + Description: string; + /// + /// Kind. + /// + Kind: TOBDCodingFieldKind; + /// + /// Byte index. + /// + ByteIndex: Integer; + BitIndex: Integer; // valid only for cfkBit + end; + + TOBDCodingSchema = TArray; + + /// + /// One diff entry — either field-level (when Schema supplied) + /// or byte-level (no schema). + /// + TOBDCodingDiffEntry = record + FieldName: string; // empty when byte-level + Description: string; // empty when byte-level + /// + /// Byte index. + /// + ByteIndex: Integer; + BitIndex: Integer; // -1 for byte/uint16 entries + /// + /// Before value. + /// + BeforeValue: UInt32; + /// + /// After value. + /// + AfterValue: UInt32; + /// + /// As text. + /// + function AsText: string; + end; + + TOBDCodingDiff = TArray; + + /// + /// Callback invoked by Plan.Apply when the caller confirms + /// the write. Implementations typically wrap the OEM-specific + /// WriteDataByIdentifier (UDS 0x2E) call. Raise on failure; the plan + /// catches and reports through Last write outcome. + /// + TOBDCodingWriter = reference to procedure(const Bytes: TBytes); + + /// + /// Holds a snapshot pair + diff. Apply is a no-op unless the + /// caller passes Confirmed=True, encoding the human in the loop into + /// the type signature. + /// + TOBDCodingPlan = class + private + FCurrent: TBytes; + FTarget: TBytes; + FSchema: TOBDCodingSchema; + FDiff: TOBDCodingDiff; + FApplied: Boolean; + /// + /// Compute diff. + /// + procedure ComputeDiff; + public + /// + /// Create. + /// + constructor Create(const Current, Target: TBytes; + const Schema: TOBDCodingSchema = nil); + /// + /// Destroy. + /// + destructor Destroy; override; + + /// + /// Is no op. + /// + function IsNoOp: Boolean; + /// + /// As text. + /// + function AsText: string; + /// + /// Apply. + /// + procedure Apply(Confirmed: Boolean; const Writer: TOBDCodingWriter); + + /// + /// Current. + /// + property Current: TBytes read FCurrent; + /// + /// Target. + /// + property Target: TBytes read FTarget; + /// + /// Diff. + /// + property Diff: TOBDCodingDiff read FDiff; + /// + /// Applied. + /// + property Applied: Boolean read FApplied; + end; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +{ TOBDCodingDiffEntry } + +//------------------------------------------------------------------------------ +// AS TEXT +//------------------------------------------------------------------------------ +function TOBDCodingDiffEntry.AsText: string; +begin + if FieldName <> '' then + begin + if BitIndex >= 0 then + Result := Format('%s [byte %d bit %d]: %d -> %d', + [FieldName, ByteIndex, BitIndex, BeforeValue, AfterValue]) + else + Result := Format('%s [byte %d]: 0x%.2x -> 0x%.2x', + [FieldName, ByteIndex, BeforeValue, AfterValue]); + if Description <> '' then + Result := Result + ' (' + Description + ')'; + end + else + Result := Format('byte %d: 0x%.2x -> 0x%.2x', + [ByteIndex, BeforeValue, AfterValue]); +end; + +{ TOBDCodingPlan } + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDCodingPlan.Create(const Current, Target: TBytes; + const Schema: TOBDCodingSchema); +begin + // Initialize the inherited class + inherited Create; + if Length(Current) <> Length(Target) then + raise EOBDCodingDiffError.CreateFmt( + 'Coding plan mismatch: current=%d bytes, target=%d bytes', + [Length(Current), Length(Target)]); + FCurrent := Copy(Current); + FTarget := Copy(Target); + FSchema := Schema; + ComputeDiff; +end; + +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ +destructor TOBDCodingPlan.Destroy; +begin + // Call the inherited handler + inherited; +end; + +//------------------------------------------------------------------------------ +// COMPUTE DIFF +//------------------------------------------------------------------------------ +procedure TOBDCodingPlan.ComputeDiff; +var + I: Integer; + Field: TOBDCodingFieldSchema; + Entry: TOBDCodingDiffEntry; + EntryList: TList; + BeforeBit, AfterBit: Boolean; +begin + // Create EntryList + EntryList := TList.Create; + try + if Length(FSchema) > 0 then + begin + // Field-level diff: walk the schema. + for Field in FSchema do + begin + Entry := Default(TOBDCodingDiffEntry); + Entry.FieldName := Field.Name; + Entry.Description := Field.Description; + Entry.ByteIndex := Field.ByteIndex; + Entry.BitIndex := -1; + case Field.Kind of + cfkBit: + begin + if (Field.ByteIndex < 0) or (Field.ByteIndex > High(FCurrent)) then + Continue; + BeforeBit := GetBit(FCurrent, Field.ByteIndex, Field.BitIndex); + AfterBit := GetBit(FTarget, Field.ByteIndex, Field.BitIndex); + if BeforeBit = AfterBit then Continue; + Entry.BitIndex := Field.BitIndex; + Entry.BeforeValue := UInt32(Ord(BeforeBit)); + Entry.AfterValue := UInt32(Ord(AfterBit)); + end; + cfkByte: + begin + if (Field.ByteIndex < 0) or (Field.ByteIndex > High(FCurrent)) then + Continue; + if FCurrent[Field.ByteIndex] = FTarget[Field.ByteIndex] then Continue; + Entry.BeforeValue := FCurrent[Field.ByteIndex]; + Entry.AfterValue := FTarget[Field.ByteIndex]; + end; + cfkUInt16: + begin + if (Field.ByteIndex < 0) or (Field.ByteIndex + 1 > High(FCurrent)) then + Continue; + Entry.BeforeValue := (UInt32(FCurrent[Field.ByteIndex]) shl 8) + or FCurrent[Field.ByteIndex + 1]; + Entry.AfterValue := (UInt32(FTarget[Field.ByteIndex]) shl 8) + or FTarget[Field.ByteIndex + 1]; + if Entry.BeforeValue = Entry.AfterValue then Continue; + end; + end; + EntryList.Add(Entry); + end; + end + else + begin + // Byte-level fallback when no schema is supplied. + for I := 0 to High(FCurrent) do + if FCurrent[I] <> FTarget[I] then + begin + Entry := Default(TOBDCodingDiffEntry); + Entry.ByteIndex := I; + Entry.BitIndex := -1; + Entry.BeforeValue := FCurrent[I]; + Entry.AfterValue := FTarget[I]; + EntryList.Add(Entry); + end; + end; + FDiff := EntryList.ToArray; + finally + // Free EntryList + EntryList.Free; + end; +end; + +//------------------------------------------------------------------------------ +// IS NO OP +//------------------------------------------------------------------------------ +function TOBDCodingPlan.IsNoOp: Boolean; +begin + Result := Length(FDiff) = 0; +end; + +//------------------------------------------------------------------------------ +// AS TEXT +//------------------------------------------------------------------------------ +function TOBDCodingPlan.AsText: string; +var + Entry: TOBDCodingDiffEntry; + Buf: TStringBuilder; +begin + if IsNoOp then Exit('Coding plan is a no-op (no fields differ).'); + // Create Buf + Buf := TStringBuilder.Create; + try + Buf.AppendLine(Format('Coding plan: %d field(s) change', + [Length(FDiff)])); + // Loop over FDiff + for Entry in FDiff do + Buf.AppendLine(' ' + Entry.AsText); + Result := Buf.ToString; + finally + // Free Buf + Buf.Free; + end; +end; + +//------------------------------------------------------------------------------ +// APPLY +//------------------------------------------------------------------------------ +procedure TOBDCodingPlan.Apply(Confirmed: Boolean; + const Writer: TOBDCodingWriter); +begin + if not Confirmed then + raise EOBDCodingDiffError.Create( + 'Apply called with Confirmed=False; coding write skipped'); + if not Assigned(Writer) then + raise EOBDCodingDiffError.Create( + 'Apply requires a non-nil writer callback'); + if IsNoOp then + begin + FApplied := True; + Exit; + end; + Writer(FTarget); + FApplied := True; +end; + +end. diff --git a/src/Services/OBD.OEM.Coding.Ford.pas b/src/Services/OBD.OEM.Coding.Ford.pas index 1c254116..8ff215ec 100644 --- a/src/Services/OBD.OEM.Coding.Ford.pas +++ b/src/Services/OBD.OEM.Coding.Ford.pas @@ -25,27 +25,39 @@ TOBDFordAsBuiltBlock = record Data: array[0..2] of Byte; Checksum: Byte; - /// Compute the expected checksum for the current DID - /// + data bytes (sum of all 5 bytes mod 256). Use to validate - /// a parsed block (compare against Checksum) or to - /// generate the trailing checksum byte after editing. + /// + /// Compute the expected checksum for the current DID + /// + data bytes (sum of all 5 bytes mod 256). Use to validate + /// a parsed block (compare against Checksum) or to + /// generate the trailing checksum byte after editing. + /// function ComputeChecksum: Byte; - /// True if Checksum matches the computed value. + /// + /// True if Checksum matches the computed value. + /// function IsValid: Boolean; - /// Recompute and store the checksum. + /// + /// Recompute and store the checksum. + /// procedure Reseal; - /// Render in FORScan's DDDD-XX XX XX CC form. + /// + /// Render in FORScan's DDDD-XX XX XX CC form. + /// function ToString: string; end; - /// Parse one AsBuilt line. Throws on malformed input. + /// + /// Parse one AsBuilt line. Throws on malformed input. + /// function ParseFordAsBuiltLine(const Line: string): TOBDFordAsBuiltBlock; - /// Parse a multi-line AsBuilt export. Blank lines and - /// lines starting with ; or # are skipped. + /// + /// Parse a multi-line AsBuilt export. Blank lines and + /// lines starting with ; or # are skipped. + /// function ParseFordAsBuiltText(const Text: string): TArray; implementation @@ -53,6 +65,9 @@ implementation uses System.Classes; +//------------------------------------------------------------------------------ +// COMPUTE CHECKSUM +//------------------------------------------------------------------------------ function TOBDFordAsBuiltBlock.ComputeChecksum: Byte; var Sum: Cardinal; @@ -61,22 +76,34 @@ function TOBDFordAsBuiltBlock.ComputeChecksum: Byte; Result := Byte(Sum and $FF); end; +//------------------------------------------------------------------------------ +// IS VALID +//------------------------------------------------------------------------------ function TOBDFordAsBuiltBlock.IsValid: Boolean; begin Result := Checksum = ComputeChecksum; end; +//------------------------------------------------------------------------------ +// RESEAL +//------------------------------------------------------------------------------ procedure TOBDFordAsBuiltBlock.Reseal; begin Checksum := ComputeChecksum; end; +//------------------------------------------------------------------------------ +// TO STRING +//------------------------------------------------------------------------------ function TOBDFordAsBuiltBlock.ToString: string; begin Result := Format('%.4X-%.2X %.2X %.2X %.2X', [DID, Data[0], Data[1], Data[2], Checksum]); end; +//------------------------------------------------------------------------------ +// PARSE FORD AS BUILT LINE +//------------------------------------------------------------------------------ function ParseFordAsBuiltLine(const Line: string): TOBDFordAsBuiltBlock; var Cleaned: string; @@ -115,6 +142,9 @@ function ParseFordAsBuiltLine(const Line: string): TOBDFordAsBuiltBlock; Result.Checksum := Bytes[3]; end; +//------------------------------------------------------------------------------ +// PARSE FORD AS BUILT TEXT +//------------------------------------------------------------------------------ function ParseFordAsBuiltText(const Text: string): TArray; var Lines: TStringList; diff --git a/src/Services/OBD.OEM.Coding.HMG.pas b/src/Services/OBD.OEM.Coding.HMG.pas new file mode 100644 index 00000000..0df7535d --- /dev/null +++ b/src/Services/OBD.OEM.Coding.HMG.pas @@ -0,0 +1,171 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.Coding.HMG.pas +// CONTENTS : Hyundai/Kia/Genesis GDS variant-coding wrapper +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.Coding.HMG; + +interface + +uses + System.SysUtils, OBD.OEM.Coding; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + TOBDHMGVariantCoding = class + strict private + FBytes: TBytes; + public + /// + /// Create. + /// + constructor Create(const Length: Integer); overload; + /// + /// Create. + /// + constructor Create(const Bytes: TBytes); overload; + /// + /// Create from hex. + /// + constructor CreateFromHex(const HexString: string); + /// + /// Byte count. + /// + function ByteCount: Integer; + /// + /// Get byte. + /// + function GetByte(const Index: Integer): Byte; + /// + /// Set byte. + /// + procedure SetByte(const Index: Integer; const Value: Byte); + /// + /// Get bit. + /// + function GetBit(const ByteIndex, BitIndex: Integer): Boolean; + /// + /// Set bit. + /// + procedure SetBit(const ByteIndex, BitIndex: Integer; const Value: Boolean); + /// + /// To bytes. + /// + function ToBytes: TBytes; + /// + /// To hex. + /// + function ToHex: string; + end; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDHMGVariantCoding.Create(const Length: Integer); +begin + // Initialize the inherited class + inherited Create; + if Length < 1 then + raise EOBDCodingError.CreateFmt( + 'HMG variant-coding length must be >= 1, got %d', [Length]); + // Allocate FBytes + SetLength(FBytes, Length); +end; + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDHMGVariantCoding.Create(const Bytes: TBytes); +begin + // Initialize the inherited class + inherited Create; + FBytes := Copy(Bytes); +end; + +//------------------------------------------------------------------------------ +// CREATE FROM HEX +//------------------------------------------------------------------------------ +constructor TOBDHMGVariantCoding.CreateFromHex(const HexString: string); +begin + // Initialize the inherited class + inherited Create; + FBytes := HexStringToBytes(HexString); +end; + +//------------------------------------------------------------------------------ +// BYTE COUNT +//------------------------------------------------------------------------------ +function TOBDHMGVariantCoding.ByteCount: Integer; +begin + Result := Length(FBytes); +end; + +//------------------------------------------------------------------------------ +// GET BYTE +//------------------------------------------------------------------------------ +function TOBDHMGVariantCoding.GetByte(const Index: Integer): Byte; +begin + if (Index < 0) or (Index > High(FBytes)) then + raise EOBDCodingError.CreateFmt( + 'Byte index %d out of range (0..%d)', [Index, High(FBytes)]); + Result := FBytes[Index]; +end; + +//------------------------------------------------------------------------------ +// SET BYTE +//------------------------------------------------------------------------------ +procedure TOBDHMGVariantCoding.SetByte(const Index: Integer; const Value: Byte); +begin + if (Index < 0) or (Index > High(FBytes)) then + raise EOBDCodingError.CreateFmt( + 'Byte index %d out of range (0..%d)', [Index, High(FBytes)]); + FBytes[Index] := Value; +end; + +//------------------------------------------------------------------------------ +// GET BIT +//------------------------------------------------------------------------------ +function TOBDHMGVariantCoding.GetBit(const ByteIndex, BitIndex: Integer): Boolean; +begin + Result := OBD.OEM.Coding.GetBit(FBytes, ByteIndex, BitIndex); +end; + +//------------------------------------------------------------------------------ +// SET BIT +//------------------------------------------------------------------------------ +procedure TOBDHMGVariantCoding.SetBit(const ByteIndex, BitIndex: Integer; + const Value: Boolean); +begin + OBD.OEM.Coding.SetBit(FBytes, ByteIndex, BitIndex, Value); +end; + +//------------------------------------------------------------------------------ +// TO BYTES +//------------------------------------------------------------------------------ +function TOBDHMGVariantCoding.ToBytes: TBytes; +begin + Result := Copy(FBytes); +end; + +//------------------------------------------------------------------------------ +// TO HEX +//------------------------------------------------------------------------------ +function TOBDHMGVariantCoding.ToHex: string; +begin + Result := BytesToHexString(FBytes); +end; + +end. diff --git a/src/Services/OBD.OEM.Coding.Honda.pas b/src/Services/OBD.OEM.Coding.Honda.pas new file mode 100644 index 00000000..5ab51188 --- /dev/null +++ b/src/Services/OBD.OEM.Coding.Honda.pas @@ -0,0 +1,171 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.Coding.Honda.pas +// CONTENTS : Honda HDS option-byte coding wrapper +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.Coding.Honda; + +interface + +uses + System.SysUtils, OBD.OEM.Coding; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + TOBDHondaOptionByte = class + strict private + FBytes: TBytes; + public + /// + /// Create. + /// + constructor Create(const Length: Integer); overload; + /// + /// Create. + /// + constructor Create(const Bytes: TBytes); overload; + /// + /// Create from hex. + /// + constructor CreateFromHex(const HexString: string); + /// + /// Byte count. + /// + function ByteCount: Integer; + /// + /// Get byte. + /// + function GetByte(const Index: Integer): Byte; + /// + /// Set byte. + /// + procedure SetByte(const Index: Integer; const Value: Byte); + /// + /// Get bit. + /// + function GetBit(const ByteIndex, BitIndex: Integer): Boolean; + /// + /// Set bit. + /// + procedure SetBit(const ByteIndex, BitIndex: Integer; const Value: Boolean); + /// + /// To bytes. + /// + function ToBytes: TBytes; + /// + /// To hex. + /// + function ToHex: string; + end; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDHondaOptionByte.Create(const Length: Integer); +begin + // Initialize the inherited class + inherited Create; + if Length < 1 then + raise EOBDCodingError.CreateFmt( + 'Honda option-byte length must be >= 1, got %d', [Length]); + // Allocate FBytes + SetLength(FBytes, Length); +end; + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDHondaOptionByte.Create(const Bytes: TBytes); +begin + // Initialize the inherited class + inherited Create; + FBytes := Copy(Bytes); +end; + +//------------------------------------------------------------------------------ +// CREATE FROM HEX +//------------------------------------------------------------------------------ +constructor TOBDHondaOptionByte.CreateFromHex(const HexString: string); +begin + // Initialize the inherited class + inherited Create; + FBytes := HexStringToBytes(HexString); +end; + +//------------------------------------------------------------------------------ +// BYTE COUNT +//------------------------------------------------------------------------------ +function TOBDHondaOptionByte.ByteCount: Integer; +begin + Result := Length(FBytes); +end; + +//------------------------------------------------------------------------------ +// GET BYTE +//------------------------------------------------------------------------------ +function TOBDHondaOptionByte.GetByte(const Index: Integer): Byte; +begin + if (Index < 0) or (Index > High(FBytes)) then + raise EOBDCodingError.CreateFmt( + 'Byte index %d out of range (0..%d)', [Index, High(FBytes)]); + Result := FBytes[Index]; +end; + +//------------------------------------------------------------------------------ +// SET BYTE +//------------------------------------------------------------------------------ +procedure TOBDHondaOptionByte.SetByte(const Index: Integer; const Value: Byte); +begin + if (Index < 0) or (Index > High(FBytes)) then + raise EOBDCodingError.CreateFmt( + 'Byte index %d out of range (0..%d)', [Index, High(FBytes)]); + FBytes[Index] := Value; +end; + +//------------------------------------------------------------------------------ +// GET BIT +//------------------------------------------------------------------------------ +function TOBDHondaOptionByte.GetBit(const ByteIndex, BitIndex: Integer): Boolean; +begin + Result := OBD.OEM.Coding.GetBit(FBytes, ByteIndex, BitIndex); +end; + +//------------------------------------------------------------------------------ +// SET BIT +//------------------------------------------------------------------------------ +procedure TOBDHondaOptionByte.SetBit(const ByteIndex, BitIndex: Integer; + const Value: Boolean); +begin + OBD.OEM.Coding.SetBit(FBytes, ByteIndex, BitIndex, Value); +end; + +//------------------------------------------------------------------------------ +// TO BYTES +//------------------------------------------------------------------------------ +function TOBDHondaOptionByte.ToBytes: TBytes; +begin + Result := Copy(FBytes); +end; + +//------------------------------------------------------------------------------ +// TO HEX +//------------------------------------------------------------------------------ +function TOBDHondaOptionByte.ToHex: string; +begin + Result := BytesToHexString(FBytes); +end; + +end. diff --git a/src/Services/OBD.OEM.Coding.Mercedes.pas b/src/Services/OBD.OEM.Coding.Mercedes.pas index 1f230fa3..9e822a70 100644 --- a/src/Services/OBD.OEM.Coding.Mercedes.pas +++ b/src/Services/OBD.OEM.Coding.Mercedes.pas @@ -30,23 +30,35 @@ interface /// per-segment semantics from their own data files. /// TOBDMercedesSCN = record - /// Hardware/dataset segment — typically a 10-digit - /// numeric prefix that identifies the ECU dataset. + /// + /// Hardware/dataset segment — typically a 10-digit + /// numeric prefix that identifies the ECU dataset. + /// HardwareSegment: string; - /// Project / variant marker — 3-4 alphanumerics, e.g. - /// "212" for W212, "204" for W204. + /// + /// Project / variant marker — 3-4 alphanumerics, e.g. + /// "212" for W212, "204" for W204. + /// ProjectSegment: string; - /// Build / patch marker — 5-6 alphanumerics for the - /// specific calibration revision. + /// + /// Build / patch marker — 5-6 alphanumerics for the + /// specific calibration revision. + /// BuildSegment: string; - /// Parse a wire-format SCN; raises on a malformed input. + /// + /// Parse a wire-format SCN; raises on a malformed input. + /// class function Parse(const S: string): TOBDMercedesSCN; static; - /// True if any segment is non-empty. + /// + /// True if any segment is non-empty. + /// function IsValid: Boolean; - /// Render in the canonical hyphenated form. + /// + /// Render in the canonical hyphenated form. + /// function ToString: string; end; @@ -55,6 +67,9 @@ implementation const ALLOWED_CHARS = ['0'..'9', 'A'..'Z']; +//------------------------------------------------------------------------------ +// NORMALIZE SEGMENT +//------------------------------------------------------------------------------ function NormalizeSegment(const S: string): string; var C: Char; @@ -68,6 +83,9 @@ function NormalizeSegment(const S: string): string; 'SCN segment contains illegal character "%s" in "%s"', [C, S]); end; +//------------------------------------------------------------------------------ +// PARSE +//------------------------------------------------------------------------------ class function TOBDMercedesSCN.Parse(const S: string): TOBDMercedesSCN; var Parts: TArray; @@ -83,6 +101,9 @@ class function TOBDMercedesSCN.Parse(const S: string): TOBDMercedesSCN; Result.BuildSegment := NormalizeSegment(Parts[2]); end; +//------------------------------------------------------------------------------ +// IS VALID +//------------------------------------------------------------------------------ function TOBDMercedesSCN.IsValid: Boolean; begin Result := (HardwareSegment <> '') and @@ -90,6 +111,9 @@ function TOBDMercedesSCN.IsValid: Boolean; (BuildSegment <> ''); end; +//------------------------------------------------------------------------------ +// TO STRING +//------------------------------------------------------------------------------ function TOBDMercedesSCN.ToString: string; begin Result := Format('%s-%s-%s', diff --git a/src/Services/OBD.OEM.Coding.Stellantis.pas b/src/Services/OBD.OEM.Coding.Stellantis.pas new file mode 100644 index 00000000..31ed08e2 --- /dev/null +++ b/src/Services/OBD.OEM.Coding.Stellantis.pas @@ -0,0 +1,211 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.Coding.Stellantis.pas +// CONTENTS : Stellantis FCA Proxi configuration wrapper +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.Coding.Stellantis; + +interface + +uses + System.SysUtils, OBD.OEM.Coding; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDStellantisProxi = class(EOBDCodingError); + + TOBDStellantisProxi = class + strict private + FBytes: TBytes; + public + /// + /// Create. + /// + constructor Create(const Length: Integer); overload; + /// + /// Create. + /// + constructor Create(const Bytes: TBytes); overload; + /// + /// Create from hex. + /// + constructor CreateFromHex(const HexString: string); + + /// + /// Byte count. + /// + function ByteCount: Integer; + /// + /// Get byte. + /// + function GetByte(const Index: Integer): Byte; + /// + /// Set byte. + /// + procedure SetByte(const Index: Integer; const Value: Byte); + /// + /// Get bit. + /// + function GetBit(const ByteIndex, BitIndex: Integer): Boolean; + /// + /// Set bit. + /// + procedure SetBit(const ByteIndex, BitIndex: Integer; const Value: Boolean); + /// + /// To bytes. + /// + function ToBytes: TBytes; + /// + /// To hex. + /// + function ToHex: string; + + /// + /// Compute the Proxi-CRC over the current bytes. The + /// polynomial used by FCA / Stellantis for Proxi is not publicly + /// documented; this method raises EOBDStellantisProxi until the + /// algorithm is supplied (see docs/DATA_GAPS.md). + /// + function ComputeChecksum: Word; + + /// + /// Set the explicit CRC bytes (for callers that have an + /// independent verified value, e.g. captured from a wiTECH log). + /// Leaves the rest of the payload untouched. + /// + procedure SetChecksum(const Crc: Word; const Offset: Integer); + end; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDStellantisProxi.Create(const Length: Integer); +begin + // Initialize the inherited class + inherited Create; + if Length < 1 then + raise EOBDStellantisProxi.CreateFmt( + 'Proxi length must be >= 1, got %d', [Length]); + // Allocate FBytes + SetLength(FBytes, Length); +end; + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDStellantisProxi.Create(const Bytes: TBytes); +begin + // Initialize the inherited class + inherited Create; + FBytes := Copy(Bytes); +end; + +//------------------------------------------------------------------------------ +// CREATE FROM HEX +//------------------------------------------------------------------------------ +constructor TOBDStellantisProxi.CreateFromHex(const HexString: string); +begin + // Initialize the inherited class + inherited Create; + FBytes := HexStringToBytes(HexString); +end; + +//------------------------------------------------------------------------------ +// BYTE COUNT +//------------------------------------------------------------------------------ +function TOBDStellantisProxi.ByteCount: Integer; +begin + Result := Length(FBytes); +end; + +//------------------------------------------------------------------------------ +// GET BYTE +//------------------------------------------------------------------------------ +function TOBDStellantisProxi.GetByte(const Index: Integer): Byte; +begin + if (Index < 0) or (Index > High(FBytes)) then + raise EOBDStellantisProxi.CreateFmt( + 'Byte index %d out of range (0..%d)', [Index, High(FBytes)]); + Result := FBytes[Index]; +end; + +//------------------------------------------------------------------------------ +// SET BYTE +//------------------------------------------------------------------------------ +procedure TOBDStellantisProxi.SetByte(const Index: Integer; const Value: Byte); +begin + if (Index < 0) or (Index > High(FBytes)) then + raise EOBDStellantisProxi.CreateFmt( + 'Byte index %d out of range (0..%d)', [Index, High(FBytes)]); + FBytes[Index] := Value; +end; + +//------------------------------------------------------------------------------ +// GET BIT +//------------------------------------------------------------------------------ +function TOBDStellantisProxi.GetBit(const ByteIndex, BitIndex: Integer): Boolean; +begin + Result := OBD.OEM.Coding.GetBit(FBytes, ByteIndex, BitIndex); +end; + +//------------------------------------------------------------------------------ +// SET BIT +//------------------------------------------------------------------------------ +procedure TOBDStellantisProxi.SetBit(const ByteIndex, BitIndex: Integer; + const Value: Boolean); +begin + OBD.OEM.Coding.SetBit(FBytes, ByteIndex, BitIndex, Value); +end; + +//------------------------------------------------------------------------------ +// TO BYTES +//------------------------------------------------------------------------------ +function TOBDStellantisProxi.ToBytes: TBytes; +begin + Result := Copy(FBytes); +end; + +//------------------------------------------------------------------------------ +// TO HEX +//------------------------------------------------------------------------------ +function TOBDStellantisProxi.ToHex: string; +begin + Result := BytesToHexString(FBytes); +end; + +//------------------------------------------------------------------------------ +// COMPUTE CHECKSUM +//------------------------------------------------------------------------------ +function TOBDStellantisProxi.ComputeChecksum: Word; +begin + raise EOBDStellantisProxi.Create( + 'Stellantis Proxi CRC polynomial not available in this build; ' + + 'see docs/DATA_GAPS.md (4.4.stellantis_proxi_crc).'); +end; + +//------------------------------------------------------------------------------ +// SET CHECKSUM +//------------------------------------------------------------------------------ +procedure TOBDStellantisProxi.SetChecksum(const Crc: Word; const Offset: Integer); +begin + if (Offset < 0) or (Offset + 1 > High(FBytes)) then + raise EOBDStellantisProxi.CreateFmt( + 'Checksum offset %d out of range', [Offset]); + FBytes[Offset] := Byte(Crc shr 8); + FBytes[Offset + 1] := Byte(Crc and $FF); +end; + +end. diff --git a/src/Services/OBD.OEM.Coding.Toyota.pas b/src/Services/OBD.OEM.Coding.Toyota.pas new file mode 100644 index 00000000..dce19548 --- /dev/null +++ b/src/Services/OBD.OEM.Coding.Toyota.pas @@ -0,0 +1,177 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.Coding.Toyota.pas +// CONTENTS : Toyota CUW Customize coding wrapper +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.Coding.Toyota; + +interface + +uses + System.SysUtils, OBD.OEM.Coding; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + /// + /// Mutable Toyota Customize byte block. Constructed from + /// the Techstream "Customize Read" payload, round-trips back via + /// ToHex. Length is per-controller and fixed at construction. + /// + TOBDToyotaCustomize = class + strict private + FBytes: TBytes; + public + /// + /// Create. + /// + constructor Create(const Length: Integer); overload; + /// + /// Create. + /// + constructor Create(const Bytes: TBytes); overload; + /// + /// Create from hex. + /// + constructor CreateFromHex(const HexString: string); + + /// + /// Byte count. + /// + function ByteCount: Integer; + /// + /// Get byte. + /// + function GetByte(const Index: Integer): Byte; + /// + /// Set byte. + /// + procedure SetByte(const Index: Integer; const Value: Byte); + /// + /// Get bit. + /// + function GetBit(const ByteIndex, BitIndex: Integer): Boolean; + /// + /// Set bit. + /// + procedure SetBit(const ByteIndex, BitIndex: Integer; const Value: Boolean); + /// + /// To bytes. + /// + function ToBytes: TBytes; + /// + /// To hex. + /// + function ToHex: string; + end; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDToyotaCustomize.Create(const Length: Integer); +begin + // Initialize the inherited class + inherited Create; + if Length < 1 then + raise EOBDCodingError.CreateFmt( + 'Toyota Customize length must be >= 1, got %d', [Length]); + // Allocate FBytes + SetLength(FBytes, Length); +end; + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDToyotaCustomize.Create(const Bytes: TBytes); +begin + // Initialize the inherited class + inherited Create; + FBytes := Copy(Bytes); +end; + +//------------------------------------------------------------------------------ +// CREATE FROM HEX +//------------------------------------------------------------------------------ +constructor TOBDToyotaCustomize.CreateFromHex(const HexString: string); +begin + // Initialize the inherited class + inherited Create; + FBytes := HexStringToBytes(HexString); +end; + +//------------------------------------------------------------------------------ +// BYTE COUNT +//------------------------------------------------------------------------------ +function TOBDToyotaCustomize.ByteCount: Integer; +begin + Result := Length(FBytes); +end; + +//------------------------------------------------------------------------------ +// GET BYTE +//------------------------------------------------------------------------------ +function TOBDToyotaCustomize.GetByte(const Index: Integer): Byte; +begin + if (Index < 0) or (Index > High(FBytes)) then + raise EOBDCodingError.CreateFmt( + 'Byte index %d out of range (0..%d)', [Index, High(FBytes)]); + Result := FBytes[Index]; +end; + +//------------------------------------------------------------------------------ +// SET BYTE +//------------------------------------------------------------------------------ +procedure TOBDToyotaCustomize.SetByte(const Index: Integer; const Value: Byte); +begin + if (Index < 0) or (Index > High(FBytes)) then + raise EOBDCodingError.CreateFmt( + 'Byte index %d out of range (0..%d)', [Index, High(FBytes)]); + FBytes[Index] := Value; +end; + +//------------------------------------------------------------------------------ +// GET BIT +//------------------------------------------------------------------------------ +function TOBDToyotaCustomize.GetBit(const ByteIndex, BitIndex: Integer): Boolean; +begin + Result := OBD.OEM.Coding.GetBit(FBytes, ByteIndex, BitIndex); +end; + +//------------------------------------------------------------------------------ +// SET BIT +//------------------------------------------------------------------------------ +procedure TOBDToyotaCustomize.SetBit(const ByteIndex, BitIndex: Integer; + const Value: Boolean); +begin + OBD.OEM.Coding.SetBit(FBytes, ByteIndex, BitIndex, Value); +end; + +//------------------------------------------------------------------------------ +// TO BYTES +//------------------------------------------------------------------------------ +function TOBDToyotaCustomize.ToBytes: TBytes; +begin + Result := Copy(FBytes); +end; + +//------------------------------------------------------------------------------ +// TO HEX +//------------------------------------------------------------------------------ +function TOBDToyotaCustomize.ToHex: string; +begin + Result := BytesToHexString(FBytes); +end; + +end. diff --git a/src/Services/OBD.OEM.Coding.VW.pas b/src/Services/OBD.OEM.Coding.VW.pas index aaafedb5..4e0fa416 100644 --- a/src/Services/OBD.OEM.Coding.VW.pas +++ b/src/Services/OBD.OEM.Coding.VW.pas @@ -44,21 +44,29 @@ TOBDVWLongCoding = class function GetBit(const ByteIndex, BitIndex: Integer): Boolean; procedure SetBit(const ByteIndex, BitIndex: Integer; const Value: Boolean); - /// True if any byte is non-zero — useful for the "is - /// this a fresh (zeroed) coding string?" check the dealer tools - /// run before offering a copy-from-vehicle workflow. + /// + /// True if any byte is non-zero — useful for the "is + /// this a fresh (zeroed) coding string?" check the dealer tools + /// run before offering a copy-from-vehicle workflow. + /// function HasNonZeroByte: Boolean; - /// Snapshot the bytes (callers get a copy; mutating it - /// doesn't affect this object). + /// + /// Snapshot the bytes (callers get a copy; mutating it + /// doesn't affect this object). + /// function ToBytes: TBytes; - /// Render as the upper-case continuous hex form VAG - /// service tools display (e.g. 0204110030480500). + /// + /// Render as the upper-case continuous hex form VAG + /// service tools display (e.g. 0204110030480500). + /// function ToHex: string; - /// Render with a separator after every byte — useful - /// for human review or CSV export. + /// + /// Render with a separator after every byte — useful + /// for human review or CSV export. + /// function ToHexWithSeparator(const Separator: string): string; property Bytes[const Index: Integer]: Byte @@ -67,6 +75,9 @@ TOBDVWLongCoding = class implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDVWLongCoding.Create(const Length: Integer); begin inherited Create; @@ -75,6 +86,9 @@ constructor TOBDVWLongCoding.Create(const Length: Integer); SetLength(FBytes, Length); end; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDVWLongCoding.Create(const Bytes: TBytes); begin inherited Create; @@ -83,14 +97,25 @@ constructor TOBDVWLongCoding.Create(const Bytes: TBytes); FBytes := Copy(Bytes, 0, System.Length(Bytes)); end; +//------------------------------------------------------------------------------ +// CREATE FROM HEX +//------------------------------------------------------------------------------ constructor TOBDVWLongCoding.CreateFromHex(const HexString: string); begin Create(HexStringToBytes(HexString)); end; +//------------------------------------------------------------------------------ +// BYTE COUNT +//------------------------------------------------------------------------------ function TOBDVWLongCoding.ByteCount: Integer; -begin Result := Length(FBytes); end; +begin + Result := Length(FBytes); +end; +//------------------------------------------------------------------------------ +// GET BYTE +//------------------------------------------------------------------------------ function TOBDVWLongCoding.GetByte(const Index: Integer): Byte; begin if (Index < 0) or (Index > High(FBytes)) then @@ -99,6 +124,9 @@ function TOBDVWLongCoding.GetByte(const Index: Integer): Byte; Result := FBytes[Index]; end; +//------------------------------------------------------------------------------ +// SET BYTE +//------------------------------------------------------------------------------ procedure TOBDVWLongCoding.SetByte(const Index: Integer; const Value: Byte); begin if (Index < 0) or (Index > High(FBytes)) then @@ -107,17 +135,26 @@ procedure TOBDVWLongCoding.SetByte(const Index: Integer; const Value: Byte); FBytes[Index] := Value; end; +//------------------------------------------------------------------------------ +// GET BIT +//------------------------------------------------------------------------------ function TOBDVWLongCoding.GetBit(const ByteIndex, BitIndex: Integer): Boolean; begin Result := OBD.OEM.Coding.GetBit(FBytes, ByteIndex, BitIndex); end; +//------------------------------------------------------------------------------ +// SET BIT +//------------------------------------------------------------------------------ procedure TOBDVWLongCoding.SetBit(const ByteIndex, BitIndex: Integer; const Value: Boolean); begin OBD.OEM.Coding.SetBit(FBytes, ByteIndex, BitIndex, Value); end; +//------------------------------------------------------------------------------ +// HAS NON ZERO BYTE +//------------------------------------------------------------------------------ function TOBDVWLongCoding.HasNonZeroByte: Boolean; var B: Byte; @@ -127,15 +164,28 @@ function TOBDVWLongCoding.HasNonZeroByte: Boolean; Result := False; end; +//------------------------------------------------------------------------------ +// TO BYTES +//------------------------------------------------------------------------------ function TOBDVWLongCoding.ToBytes: TBytes; begin Result := Copy(FBytes, 0, Length(FBytes)); end; +//------------------------------------------------------------------------------ +// TO HEX +//------------------------------------------------------------------------------ function TOBDVWLongCoding.ToHex: string; -begin Result := BytesToHexString(FBytes); end; +begin + Result := BytesToHexString(FBytes); +end; +//------------------------------------------------------------------------------ +// TO HEX WITH SEPARATOR +//------------------------------------------------------------------------------ function TOBDVWLongCoding.ToHexWithSeparator(const Separator: string): string; -begin Result := BytesToHexString(FBytes, Separator); end; +begin + Result := BytesToHexString(FBytes, Separator); +end; end. diff --git a/src/Services/OBD.OEM.Coding.pas b/src/Services/OBD.OEM.Coding.pas index 8024a5a4..c1d6010b 100644 --- a/src/Services/OBD.OEM.Coding.pas +++ b/src/Services/OBD.OEM.Coding.pas @@ -22,19 +22,25 @@ interface type EOBDCodingError = class(Exception); -/// Strip whitespace + non-hex separators and decode the -/// remaining hex pairs into bytes. Throws on a malformed input -/// (odd character count, non-hex characters). +/// +/// Strip whitespace + non-hex separators and decode the +/// remaining hex pairs into bytes. Throws on a malformed input +/// (odd character count, non-hex characters). +/// function HexStringToBytes(const Hex: string): TBytes; -/// Format Bytes as upper-case hex. Separator -/// inserts after every byte except the last (default empty for the -/// continuous form VAG / BMW use; pass ' ' for human-readable). +/// +/// Format Bytes as upper-case hex. Separator +/// inserts after every byte except the last (default empty for the +/// continuous form VAG / BMW use; pass ' ' for human-readable). +/// function BytesToHexString(const Bytes: TBytes; const Separator: string = ''): string; -/// Helpers for bit-fields inside a TBytes — used by VW long -/// coding and BMW FA-byte mode. +/// +/// Helpers for bit-fields inside a TBytes — used by VW long +/// coding and BMW FA-byte mode. +/// function GetBit(const Bytes: TBytes; const ByteIndex, BitIndex: Integer): Boolean; procedure SetBit(var Bytes: TBytes; @@ -42,11 +48,17 @@ procedure SetBit(var Bytes: TBytes; implementation +//------------------------------------------------------------------------------ +// IS HEX CHAR +//------------------------------------------------------------------------------ function IsHexChar(C: Char): Boolean; begin Result := CharInSet(C, ['0'..'9', 'a'..'f', 'A'..'F']); end; +//------------------------------------------------------------------------------ +// HEX CHAR TO NIBBLE +//------------------------------------------------------------------------------ function HexCharToNibble(C: Char): Byte; begin case UpCase(C) of @@ -58,6 +70,9 @@ function HexCharToNibble(C: Char): Byte; end; end; +//------------------------------------------------------------------------------ +// HEX STRING TO BYTES +//------------------------------------------------------------------------------ function HexStringToBytes(const Hex: string): TBytes; var Filtered: string; @@ -89,6 +104,9 @@ function HexStringToBytes(const Hex: string): TBytes; HexCharToNibble(Filtered[I * 2 + 2]); end; +//------------------------------------------------------------------------------ +// BYTES TO HEX STRING +//------------------------------------------------------------------------------ function BytesToHexString(const Bytes: TBytes; const Separator: string): string; var @@ -108,6 +126,9 @@ function BytesToHexString(const Bytes: TBytes; end; end; +//------------------------------------------------------------------------------ +// GET BIT +//------------------------------------------------------------------------------ function GetBit(const Bytes: TBytes; const ByteIndex, BitIndex: Integer): Boolean; begin @@ -120,6 +141,9 @@ function GetBit(const Bytes: TBytes; Result := (Bytes[ByteIndex] and (Byte(1) shl BitIndex)) <> 0; end; +//------------------------------------------------------------------------------ +// SET BIT +//------------------------------------------------------------------------------ procedure SetBit(var Bytes: TBytes; const ByteIndex, BitIndex: Integer; const Value: Boolean); var diff --git a/src/Services/OBD.OEM.ComponentProtection.VAG.pas b/src/Services/OBD.OEM.ComponentProtection.VAG.pas new file mode 100644 index 00000000..fde14569 --- /dev/null +++ b/src/Services/OBD.OEM.ComponentProtection.VAG.pas @@ -0,0 +1,249 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.ComponentProtection.VAG.pas +// CONTENTS : VAG Component Protection request/response framing +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.ComponentProtection.VAG; + +interface + +uses + System.SysUtils; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDVAGCP = class(Exception); + EOBDVAGCPNoSolver = class(EOBDVAGCP); + + TVAGCPRequest = record + /// + /// Ecu type. + /// + ECUType: Word; + /// + /// Component serial. + /// + ComponentSerial: TBytes; + VIN: string; // 17 ASCII chars, validated + /// + /// Nonce. + /// + Nonce: TBytes; + end; + + TVAGCPResponse = record + /// + /// Response. + /// + Response: TBytes; + /// + /// Signature. + /// + Signature: TBytes; + end; + + IVAGCPSolver = interface + ['{72D7E9F1-6A8D-4D6A-9FBE-9A5B2B8E4C10}'] + /// + /// Forward the challenge envelope to the SVM portal and + /// return the activation envelope. Production hosts plug in their + /// dealer-portal client here. + /// + function Solve(const Request: TVAGCPRequest): TVAGCPResponse; + end; + + /// + /// Default solver that fails closed. + /// + TVAGCPSolverNotAvailable = class(TInterfacedObject, IVAGCPSolver) + public + /// + /// Solve. + /// + function Solve(const Request: TVAGCPRequest): TVAGCPResponse; + end; + +function EncodeVAGCPRequest(const Request: TVAGCPRequest): TBytes; +function DecodeVAGCPRequest(const Bytes: TBytes): TVAGCPRequest; +function EncodeVAGCPResponse(const Response: TVAGCPResponse): TBytes; +function DecodeVAGCPResponse(const Bytes: TBytes): TVAGCPResponse; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// PUT WORD +//------------------------------------------------------------------------------ +function PutWord(var Out_: TBytes; Cursor: Integer; W: Word): Integer; +begin + Out_[Cursor] := Byte(W shr 8); + Out_[Cursor + 1] := Byte(W and $FF); + Result := Cursor + 2; +end; + +//------------------------------------------------------------------------------ +// GET WORD +//------------------------------------------------------------------------------ +function GetWord(const B: TBytes; Off: Integer): Word; +begin + Result := (UInt16(B[Off]) shl 8) or B[Off + 1]; +end; + +//------------------------------------------------------------------------------ +// ENCODE VAGCPREQUEST +//------------------------------------------------------------------------------ +function EncodeVAGCPRequest(const Request: TVAGCPRequest): TBytes; +var + Total, Cursor, I: Integer; +begin + if Length(Request.VIN) <> 17 then + raise EOBDVAGCP.CreateFmt( + 'VIN must be 17 chars (got %d)', [Length(Request.VIN)]); + if Length(Request.ComponentSerial) > $FFFF then + raise EOBDVAGCP.Create('ComponentSerial exceeds 65535 bytes'); + if Length(Request.Nonce) > $FFFF then + raise EOBDVAGCP.Create('Nonce exceeds 65535 bytes'); + + Total := 2 // ECUType + + 2 + Length(Request.ComponentSerial) // serial-len + serial + + 1 + 17 // VIN length + VIN + + 2 + Length(Request.Nonce); // nonce-len + nonce + // Allocate Result + SetLength(Result, Total); + + Cursor := 0; + Cursor := PutWord(Result, Cursor, Request.ECUType); + Cursor := PutWord(Result, Cursor, Word(Length(Request.ComponentSerial))); + if Length(Request.ComponentSerial) > 0 then + begin + Move(Request.ComponentSerial[0], Result[Cursor], Length(Request.ComponentSerial)); + Inc(Cursor, Length(Request.ComponentSerial)); + end; + Result[Cursor] := 17; + Inc(Cursor); + for I := 0 to 16 do + Result[Cursor + I] := Byte(Ord(Request.VIN[I + 1])); + Inc(Cursor, 17); + PutWord(Result, Cursor, Word(Length(Request.Nonce))); + Inc(Cursor, 2); + if Length(Request.Nonce) > 0 then + Move(Request.Nonce[0], Result[Cursor], Length(Request.Nonce)); +end; + +//------------------------------------------------------------------------------ +// DECODE VAGCPREQUEST +//------------------------------------------------------------------------------ +function DecodeVAGCPRequest(const Bytes: TBytes): TVAGCPRequest; +var + Cursor, Len, I: Integer; +begin + if Length(Bytes) < 5 then + raise EOBDVAGCP.Create('CP request too short'); + Cursor := 0; + Result.ECUType := GetWord(Bytes, Cursor); Inc(Cursor, 2); + Len := GetWord(Bytes, Cursor); Inc(Cursor, 2); + if Cursor + Len > Length(Bytes) then + raise EOBDVAGCP.Create('ComponentSerial truncated'); + // Allocate Result.ComponentSerial + SetLength(Result.ComponentSerial, Len); + if Len > 0 then Move(Bytes[Cursor], Result.ComponentSerial[0], Len); + Inc(Cursor, Len); + if Cursor + 1 > Length(Bytes) then + raise EOBDVAGCP.Create('VIN length byte missing'); + if Bytes[Cursor] <> 17 then + raise EOBDVAGCP.CreateFmt( + 'VIN length must be 17 (got %d)', [Bytes[Cursor]]); + Inc(Cursor); + if Cursor + 17 > Length(Bytes) then + raise EOBDVAGCP.Create('VIN bytes truncated'); + // Allocate Result.VIN + SetLength(Result.VIN, 17); + for I := 0 to 16 do Result.VIN[I + 1] := Char(Bytes[Cursor + I]); + Inc(Cursor, 17); + if Cursor + 2 > Length(Bytes) then + raise EOBDVAGCP.Create('Nonce length missing'); + Len := GetWord(Bytes, Cursor); Inc(Cursor, 2); + if Cursor + Len > Length(Bytes) then + raise EOBDVAGCP.Create('Nonce truncated'); + // Allocate Result.Nonce + SetLength(Result.Nonce, Len); + if Len > 0 then Move(Bytes[Cursor], Result.Nonce[0], Len); +end; + +//------------------------------------------------------------------------------ +// ENCODE VAGCPRESPONSE +//------------------------------------------------------------------------------ +function EncodeVAGCPResponse(const Response: TVAGCPResponse): TBytes; +var + Cursor: Integer; +begin + if Length(Response.Response) > $FFFF then + raise EOBDVAGCP.Create('Response exceeds 65535 bytes'); + if Length(Response.Signature) > $FFFF then + raise EOBDVAGCP.Create('Signature exceeds 65535 bytes'); + SetLength(Result, 2 + Length(Response.Response) + + 2 + Length(Response.Signature)); + Cursor := 0; + Cursor := PutWord(Result, Cursor, Word(Length(Response.Response))); + if Length(Response.Response) > 0 then + begin + Move(Response.Response[0], Result[Cursor], Length(Response.Response)); + Inc(Cursor, Length(Response.Response)); + end; + PutWord(Result, Cursor, Word(Length(Response.Signature))); + Inc(Cursor, 2); + if Length(Response.Signature) > 0 then + Move(Response.Signature[0], Result[Cursor], Length(Response.Signature)); +end; + +//------------------------------------------------------------------------------ +// DECODE VAGCPRESPONSE +//------------------------------------------------------------------------------ +function DecodeVAGCPResponse(const Bytes: TBytes): TVAGCPResponse; +var + Cursor, Len: Integer; +begin + if Length(Bytes) < 4 then + raise EOBDVAGCP.Create('CP response too short'); + Cursor := 0; + Len := GetWord(Bytes, Cursor); Inc(Cursor, 2); + if Cursor + Len > Length(Bytes) then + raise EOBDVAGCP.Create('Response payload truncated'); + // Allocate Result.Response + SetLength(Result.Response, Len); + if Len > 0 then Move(Bytes[Cursor], Result.Response[0], Len); + Inc(Cursor, Len); + if Cursor + 2 > Length(Bytes) then + raise EOBDVAGCP.Create('Signature length missing'); + Len := GetWord(Bytes, Cursor); Inc(Cursor, 2); + if Cursor + Len > Length(Bytes) then + raise EOBDVAGCP.Create('Signature payload truncated'); + // Allocate Result.Signature + SetLength(Result.Signature, Len); + if Len > 0 then Move(Bytes[Cursor], Result.Signature[0], Len); +end; + +{ TVAGCPSolverNotAvailable } + +//------------------------------------------------------------------------------ +// SOLVE +//------------------------------------------------------------------------------ +function TVAGCPSolverNotAvailable.Solve(const Request: TVAGCPRequest): TVAGCPResponse; +begin + raise EOBDVAGCPNoSolver.Create( + 'VAG Component Protection solver not available in this build. ' + + 'Plug in a dealer-portal SVM client to authorise component ' + + 'replacements (see docs/DATA_GAPS.md).'); +end; + +end. diff --git a/src/Services/OBD.OEM.Cummins.pas b/src/Services/OBD.OEM.Cummins.pas index 1e1353f9..94c870aa 100644 --- a/src/Services/OBD.OEM.Cummins.pas +++ b/src/Services/OBD.OEM.Cummins.pas @@ -28,13 +28,18 @@ interface TOBDOEMExtensionCummins = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -53,17 +58,34 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionCummins.ManufacturerKey: string; -begin Result := 'CUMMINS'; end; +begin + Result := 'CUMMINS'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionCummins.DisplayName: string; -begin Result := 'Cummins Inc. (engine OEM)'; end; +begin + Result := 'Cummins Inc. (engine OEM)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionCummins.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in cummins.json. Result := VINMatchesCatalog('cummins.json', VIN); end; + +//------------------------------------------------------------------------------ +// APPLICABLE TO ECUSUPPLIER +//------------------------------------------------------------------------------ function TOBDOEMExtensionCummins.ApplicableToECUSupplier( const SupplierID: string): Boolean; var @@ -76,10 +98,16 @@ function TOBDOEMExtensionCummins.ApplicableToECUSupplier( Result := (Norm = 'CUMMINS') or (Norm = 'CMI'); end; +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionCummins.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are cummins.json // + uds-standard.json. Hardcoded entries removed. @@ -90,19 +118,36 @@ procedure TOBDOEMExtensionCummins.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionCummins.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('cummins.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionCummins.CreateSessionNegotiator: IOBDSessionNegotiator; -begin Result := TOBDHDSessionNegotiator.Create; end; +begin + Result := TOBDHDSessionNegotiator.Create; +end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionCummins.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin @@ -113,6 +158,9 @@ procedure TOBDOEMExtensionCummins.SeedDefaultSeedKeyAlgorithms( Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionCummins.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -120,9 +168,17 @@ procedure TOBDOEMExtensionCummins.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionCummins.DtcCatalogFileName: string; -begin Result := 'dtc-cummins.json'; end; +begin + Result := 'dtc-cummins.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionCummins.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.DTC.Loader.pas b/src/Services/OBD.OEM.DTC.Loader.pas index 0c1e0404..3e3d3718 100644 --- a/src/Services/OBD.OEM.DTC.Loader.pas +++ b/src/Services/OBD.OEM.DTC.Loader.pas @@ -13,14 +13,19 @@ interface System.SysUtils, OBD.OEM.DTC, OBD.OEM.Catalog.Loader; -/// Load FileName through the standard catalog search -/// path and merge its entries into Cat. Silently no-ops when -/// the file isn't found so deployments without the catalog folder -/// still work. +/// +/// Load FileName through the standard catalog search +/// path and merge its entries into Cat. Silently no-ops when +/// the file isn't found so deployments without the catalog folder +/// still work. +/// procedure MergeDtcCatalog(const FileName: string; Cat: TOBDDtcCatalog); implementation +//------------------------------------------------------------------------------ +// MERGE DTC CATALOG +//------------------------------------------------------------------------------ procedure MergeDtcCatalog(const FileName: string; Cat: TOBDDtcCatalog); var Path: string; diff --git a/src/Services/OBD.OEM.DTC.pas b/src/Services/OBD.OEM.DTC.pas index 51e461c6..fcab2a21 100644 --- a/src/Services/OBD.OEM.DTC.pas +++ b/src/Services/OBD.OEM.DTC.pas @@ -27,8 +27,10 @@ interface type EOBDDtcError = class(Exception); - /// Coarse severity hint for UI tinting / triage. The wire - /// protocol doesn't carry severity — these are catalog metadata. + /// + /// Coarse severity hint for UI tinting / triage. The wire + /// protocol doesn't carry severity — these are catalog metadata. + /// TOBDDtcSeverity = ( dtcSeverityUnknown, dtcSeverityInfo, @@ -36,7 +38,9 @@ EOBDDtcError = class(Exception); dtcSeverityCritical ); - /// SAE J2012 system letter — the first character of a DTC. + /// + /// SAE J2012 system letter — the first character of a DTC. + /// TOBDDtcSystem = ( dtcPowertrain, // P dtcChassis, // C @@ -44,11 +48,13 @@ EOBDDtcError = class(Exception); dtcNetwork // U ); - /// OBD-II monitor classification (SAE J1979 §6). - /// dmtContinuous covers misfire / fuel system / comprehensive - /// component monitors that run continuously while the engine is on. - /// dmtNonContinuous covers catalyst / EVAP / O2 sensor / - /// EGR monitors that run during specific drive cycles. + /// + /// OBD-II monitor classification (SAE J1979 §6). + /// dmtContinuous covers misfire / fuel system / comprehensive + /// component monitors that run continuously while the engine is on. + /// dmtNonContinuous covers catalyst / EVAP / O2 sensor / + /// EGR monitors that run during specific drive cycles. + /// TOBDDtcMonitorType = ( dmtUnknown, dmtContinuous, @@ -57,46 +63,72 @@ EOBDDtcError = class(Exception); ); TOBDDtcCatalogEntry = record - /// Five-character code: P0301, B22A8, etc. + /// + /// Five-character code: P0301, B22A8, etc. + /// Code: string; Severity: TOBDDtcSeverity; - /// Short, single-line description ("Cylinder 1 misfire detected"). + /// + /// Short, single-line description ("Cylinder 1 misfire detected"). + /// Description: string; - /// Free-form list of plausible causes shown to the user. + /// + /// Free-form list of plausible causes shown to the user. + /// PossibleCauses: TArray; - /// Repair-hint paragraph (often borrowed from service manuals). + /// + /// Repair-hint paragraph (often borrowed from service manuals). + /// RepairHints: string; - /// Provenance — same vocabulary as the DID catalog - /// (iso-15031-6, sae-j2012, ross-tech-wiki, - /// esys-community, community-pr, …). + /// + /// Provenance — same vocabulary as the DID catalog + /// (iso-15031-6, sae-j2012, ross-tech-wiki, + /// esys-community, community-pr, …). + /// Source: string; - /// True only when matched against an authoritative spec - /// (SAE J2012, OEM service manual) or capture fixture. + /// + /// True only when matched against an authoritative spec + /// (SAE J2012, OEM service manual) or capture fixture. + /// Verified: Boolean; //--------- v3.77 schema extensions --------- - /// Driver-observable symptoms ("rough idle", "MIL on", - /// "lurching upshift"). Optional. UI shows alongside causes. + /// + /// Driver-observable symptoms ("rough idle", "MIL on", + /// "lurching upshift"). Optional. UI shows alongside causes. + /// Symptoms: TArray; - /// Stepped repair guidance (numbered steps, often - /// "1) Check connector. 2) Measure resistance ..."). Distinct - /// from the older free-form RepairHints which is a - /// paragraph blurb. + /// + /// Stepped repair guidance (numbered steps, often + /// "1) Check connector. 2) Measure resistance ..."). Distinct + /// from the older free-form RepairHints which is a + /// paragraph blurb. + /// RepairGuidance: TArray; - /// SAE J1979 monitor category — drives readiness UI. + /// + /// SAE J1979 monitor category — drives readiness UI. + /// MonitorType: TOBDDtcMonitorType; - /// Whether this code triggers a freeze-frame snapshot. - /// MIL-on codes typically do; pending codes typically don't. + /// + /// Whether this code triggers a freeze-frame snapshot. + /// MIL-on codes typically do; pending codes typically don't. + /// FreezeFrameRelevant: Boolean; - /// DID names from the same OEM catalog that are useful - /// during diagnosis (e.g. ecm_misfire, ecm_lambda_b1 - /// for P0301). Lets a tool offer "read related data" buttons - /// next to the DTC entry. + /// + /// DID names from the same OEM catalog that are useful + /// during diagnosis (e.g. ecm_misfire, ecm_lambda_b1 + /// for P0301). Lets a tool offer "read related data" buttons + /// next to the DTC entry. + /// RelatedDIDs: TArray; - /// Routine names that are likely the corrective action - /// (e.g. ecm_dpf_regen_force for a P244A DPF fault). + /// + /// Routine names that are likely the corrective action + /// (e.g. ecm_dpf_regen_force for a P244A DPF fault). + /// RelatedRoutines: TArray; - /// OEM service-bulletin reference (TSB number / - /// recall ID / dealer fix code). Free text. + /// + /// OEM service-bulletin reference (TSB number / + /// recall ID / dealer fix code). Free text. + /// OemBulletin: string; end; @@ -114,49 +146,69 @@ TOBDDtcCatalog = class constructor Create; destructor Destroy; override; - /// Add or replace an entry. Index is updated in lock-step. + /// + /// Add or replace an entry. Index is updated in lock-step. + /// procedure Add(const Entry: TOBDDtcCatalogEntry); - /// Bulk-load from a JSON array (entries are appended; - /// existing codes are replaced). + /// + /// Bulk-load from a JSON array (entries are appended; + /// existing codes are replaced). + /// procedure LoadFromJSON(Arr: TJSONArray; const DefaultSource: string = ''); procedure LoadFromFile(const FilePath: string); procedure LoadFromText(const JsonText: string); - /// Find an entry by its 5-character code (case-insensitive). + /// + /// Find an entry by its 5-character code (case-insensitive). + /// function FindByCode(const Code: string; out Entry: TOBDDtcCatalogEntry): Boolean; - /// Number of catalogued entries. + /// + /// Number of catalogued entries. + /// function Count: Integer; function Item(Index: Integer): TOBDDtcCatalogEntry; function ToArray: TArray; procedure Clear; end; -/// Decode a two-byte ISO 15031-5 DTC into its 5-character form. +/// +/// Decode a two-byte ISO 15031-5 DTC into its 5-character form. +/// function FormatDtc(const High, Low: Byte): string; overload; function FormatDtc(const Bytes: TBytes): string; overload; -/// Encode a 5-character DTC (e.g. P0301) back into the -/// two ISO 15031-5 bytes. Throws EOBDDtcError on a malformed -/// input. +/// +/// Encode a 5-character DTC (e.g. P0301) back into the +/// two ISO 15031-5 bytes. Throws EOBDDtcError on a malformed +/// input. +/// function EncodeDtc(const Code: string): TBytes; -/// Letter (P/C/B/U) parser. +/// +/// Letter (P/C/B/U) parser. +/// function ParseDtcSystem(const C: Char): TOBDDtcSystem; function FormatDtcSystem(const Sys: TOBDDtcSystem): Char; -/// True if the DTC is in the manufacturer-specific range -/// (P1xxx, P3xxx, B1xxx, B3xxx, C1xxx, C3xxx, U1xxx, U3xxx). +/// +/// True if the DTC is in the manufacturer-specific range +/// (P1xxx, P3xxx, B1xxx, B3xxx, C1xxx, C3xxx, U1xxx, U3xxx). +/// function IsManufacturerDtc(const Code: string): Boolean; -/// Map the catalog's text severity tags to the enum. +/// +/// Map the catalog's text severity tags to the enum. +/// function ParseSeverity(const S: string): TOBDDtcSeverity; function FormatSeverity(const Severity: TOBDDtcSeverity): string; -/// Map the catalog's monitor_type strings (continuous / -/// non_continuous / comprehensive_component / "") to the enum. +/// +/// Map the catalog's monitor_type strings (continuous / +/// non_continuous / comprehensive_component / "") to the enum. +/// function ParseMonitorType(const S: string): TOBDDtcMonitorType; function FormatMonitorType(const Mt: TOBDDtcMonitorType): string; @@ -168,6 +220,10 @@ implementation //============================================================================== // Encoding //============================================================================== + +//------------------------------------------------------------------------------ +// PARSE DTC SYSTEM +//------------------------------------------------------------------------------ function ParseDtcSystem(const C: Char): TOBDDtcSystem; begin case UpCase(C) of @@ -180,6 +236,9 @@ function ParseDtcSystem(const C: Char): TOBDDtcSystem; end; end; +//------------------------------------------------------------------------------ +// FORMAT DTC SYSTEM +//------------------------------------------------------------------------------ function FormatDtcSystem(const Sys: TOBDDtcSystem): Char; begin case Sys of @@ -192,6 +251,9 @@ function FormatDtcSystem(const Sys: TOBDDtcSystem): Char; end; end; +//------------------------------------------------------------------------------ +// FORMAT DTC +//------------------------------------------------------------------------------ function FormatDtc(const High, Low: Byte): string; const SYSTEMS: array[0..3] of Char = ('P', 'C', 'B', 'U'); @@ -213,6 +275,9 @@ function FormatDtc(const High, Low: Byte): string; ]); end; +//------------------------------------------------------------------------------ +// FORMAT DTC +//------------------------------------------------------------------------------ function FormatDtc(const Bytes: TBytes): string; begin if Length(Bytes) < 2 then @@ -220,6 +285,9 @@ function FormatDtc(const Bytes: TBytes): string; Result := FormatDtc(Bytes[0], Bytes[1]); end; +//------------------------------------------------------------------------------ +// HEX CHAR TO NIBBLE +//------------------------------------------------------------------------------ function HexCharToNibble(C: Char): Byte; begin case UpCase(C) of @@ -230,6 +298,9 @@ function HexCharToNibble(C: Char): Byte; end; end; +//------------------------------------------------------------------------------ +// ENCODE DTC +//------------------------------------------------------------------------------ function EncodeDtc(const Code: string): TBytes; var Norm: string; @@ -261,6 +332,9 @@ function EncodeDtc(const Code: string): TBytes; Result[1] := (D4 shl 4) or D5; end; +//------------------------------------------------------------------------------ +// IS MANUFACTURER DTC +//------------------------------------------------------------------------------ function IsManufacturerDtc(const Code: string): Boolean; begin if Length(Code) < 2 then Exit(False); @@ -270,8 +344,13 @@ function IsManufacturerDtc(const Code: string): Boolean; //============================================================================== // Severity //============================================================================== + +//------------------------------------------------------------------------------ +// PARSE SEVERITY +//------------------------------------------------------------------------------ function ParseSeverity(const S: string): TOBDDtcSeverity; -var L: string; +var + L: string; begin L := LowerCase(Trim(S)); if (L = 'info') or (L = 'information') then Exit(dtcSeverityInfo); @@ -280,6 +359,9 @@ function ParseSeverity(const S: string): TOBDDtcSeverity; Result := dtcSeverityUnknown; end; +//------------------------------------------------------------------------------ +// FORMAT SEVERITY +//------------------------------------------------------------------------------ function FormatSeverity(const Severity: TOBDDtcSeverity): string; begin case Severity of @@ -291,8 +373,12 @@ function FormatSeverity(const Severity: TOBDDtcSeverity): string; end; end; +//------------------------------------------------------------------------------ +// PARSE MONITOR TYPE +//------------------------------------------------------------------------------ function ParseMonitorType(const S: string): TOBDDtcMonitorType; -var L: string; +var + L: string; begin L := LowerCase(Trim(S)); if (L = 'continuous') then Exit(dmtContinuous); @@ -303,6 +389,9 @@ function ParseMonitorType(const S: string): TOBDDtcMonitorType; Result := dmtUnknown; end; +//------------------------------------------------------------------------------ +// FORMAT MONITOR TYPE +//------------------------------------------------------------------------------ function FormatMonitorType(const Mt: TOBDDtcMonitorType): string; begin case Mt of @@ -317,6 +406,10 @@ function FormatMonitorType(const Mt: TOBDDtcMonitorType): string; //============================================================================== // TOBDDtcCatalog //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDDtcCatalog.Create; begin inherited Create; @@ -324,6 +417,9 @@ constructor TOBDDtcCatalog.Create; FByCode := TDictionary.Create; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDDtcCatalog.Destroy; begin FByCode.Free; @@ -331,6 +427,9 @@ destructor TOBDDtcCatalog.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// REBUILD INDEX +//------------------------------------------------------------------------------ procedure TOBDDtcCatalog.RebuildIndex; var I: Integer; @@ -340,6 +439,9 @@ procedure TOBDDtcCatalog.RebuildIndex; FByCode.AddOrSetValue(UpperCase(FEntries[I].Code), I); end; +//------------------------------------------------------------------------------ +// ADD +//------------------------------------------------------------------------------ procedure TOBDDtcCatalog.Add(const Entry: TOBDDtcCatalogEntry); var ExistingIdx: Integer; @@ -355,6 +457,9 @@ procedure TOBDDtcCatalog.Add(const Entry: TOBDDtcCatalogEntry); end; end; +//------------------------------------------------------------------------------ +// PARSE CAUSES ARRAY +//------------------------------------------------------------------------------ function ParseCausesArray(Arr: TJSONArray): TArray; var I: Integer; @@ -365,6 +470,9 @@ function ParseCausesArray(Arr: TJSONArray): TArray; Result[I] := Arr.Items[I].Value; end; +//------------------------------------------------------------------------------ +// LOAD FROM JSON +//------------------------------------------------------------------------------ procedure TOBDDtcCatalog.LoadFromJSON(Arr: TJSONArray; const DefaultSource: string); var @@ -405,6 +513,9 @@ procedure TOBDDtcCatalog.LoadFromJSON(Arr: TJSONArray; end; end; +//------------------------------------------------------------------------------ +// LOAD FROM FILE +//------------------------------------------------------------------------------ procedure TOBDDtcCatalog.LoadFromFile(const FilePath: string); begin if not TFile.Exists(FilePath) then @@ -412,6 +523,9 @@ procedure TOBDDtcCatalog.LoadFromFile(const FilePath: string); LoadFromText(TFile.ReadAllText(FilePath, TEncoding.UTF8)); end; +//------------------------------------------------------------------------------ +// LOAD FROM TEXT +//------------------------------------------------------------------------------ procedure TOBDDtcCatalog.LoadFromText(const JsonText: string); var Root: TJSONValue; @@ -439,6 +553,9 @@ procedure TOBDDtcCatalog.LoadFromText(const JsonText: string); end; end; +//------------------------------------------------------------------------------ +// FIND BY CODE +//------------------------------------------------------------------------------ function TOBDDtcCatalog.FindByCode(const Code: string; out Entry: TOBDDtcCatalogEntry): Boolean; var @@ -448,15 +565,33 @@ function TOBDDtcCatalog.FindByCode(const Code: string; if Result then Entry := FEntries[Idx]; end; +//------------------------------------------------------------------------------ +// COUNT +//------------------------------------------------------------------------------ function TOBDDtcCatalog.Count: Integer; -begin Result := FEntries.Count; end; +begin + Result := FEntries.Count; +end; +//------------------------------------------------------------------------------ +// ITEM +//------------------------------------------------------------------------------ function TOBDDtcCatalog.Item(Index: Integer): TOBDDtcCatalogEntry; -begin Result := FEntries[Index]; end; +begin + Result := FEntries[Index]; +end; +//------------------------------------------------------------------------------ +// TO ARRAY +//------------------------------------------------------------------------------ function TOBDDtcCatalog.ToArray: TArray; -begin Result := FEntries.ToArray; end; +begin + Result := FEntries.ToArray; +end; +//------------------------------------------------------------------------------ +// CLEAR +//------------------------------------------------------------------------------ procedure TOBDDtcCatalog.Clear; begin FEntries.Clear; diff --git a/src/Services/OBD.OEM.Dacia.pas b/src/Services/OBD.OEM.Dacia.pas index bb841224..20c0d64b 100644 --- a/src/Services/OBD.OEM.Dacia.pas +++ b/src/Services/OBD.OEM.Dacia.pas @@ -27,13 +27,18 @@ interface TOBDOEMExtensionDacia = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; function DtcCatalogFileName: string; override; @@ -49,21 +54,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionDacia.ManufacturerKey: string; -begin Result := 'DACIA'; end; +begin + Result := 'DACIA'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionDacia.DisplayName: string; -begin Result := 'Automobile Dacia SA (Renault Group)'; end; +begin + Result := 'Automobile Dacia SA (Renault Group)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionDacia.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in dacia.json. Result := VINMatchesCatalog('dacia.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionDacia.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are dacia.json // + uds-standard.json. Hardcoded entries removed. @@ -74,16 +99,28 @@ procedure TOBDOEMExtensionDacia.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionDacia.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('dacia.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionDacia.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -91,9 +128,17 @@ procedure TOBDOEMExtensionDacia.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionDacia.DtcCatalogFileName: string; -begin Result := 'dtc-dacia.json'; end; +begin + Result := 'dtc-dacia.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionDacia.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.DetroitDiesel.pas b/src/Services/OBD.OEM.DetroitDiesel.pas index c7c1794f..4b3c07ce 100644 --- a/src/Services/OBD.OEM.DetroitDiesel.pas +++ b/src/Services/OBD.OEM.DetroitDiesel.pas @@ -25,13 +25,18 @@ interface TOBDOEMExtensionDetroitDiesel = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -50,17 +55,34 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionDetroitDiesel.ManufacturerKey: string; -begin Result := 'DDC'; end; +begin + Result := 'DDC'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionDetroitDiesel.DisplayName: string; -begin Result := 'Detroit Diesel Corp. (engine OEM)'; end; +begin + Result := 'Detroit Diesel Corp. (engine OEM)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionDetroitDiesel.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in detroit.json. Result := VINMatchesCatalog('detroit.json', VIN); end; + +//------------------------------------------------------------------------------ +// APPLICABLE TO ECUSUPPLIER +//------------------------------------------------------------------------------ function TOBDOEMExtensionDetroitDiesel.ApplicableToECUSupplier( const SupplierID: string): Boolean; var @@ -73,10 +95,16 @@ function TOBDOEMExtensionDetroitDiesel.ApplicableToECUSupplier( Result := (Norm = 'DETROIT') or (Norm = 'DDC') or (Norm = 'DETROITDDC'); end; +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionDetroitDiesel.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are detroit.json // + uds-standard.json. Hardcoded entries removed. @@ -87,25 +115,45 @@ procedure TOBDOEMExtensionDetroitDiesel.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionDetroitDiesel.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('detroit.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionDetroitDiesel.CreateSessionNegotiator: IOBDSessionNegotiator; -begin Result := TOBDHDSessionNegotiator.Create; end; +begin + Result := TOBDHDSessionNegotiator.Create; +end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionDetroitDiesel.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionDetroitDiesel.SeedDefaultDtcCatalog( Cat: TOBDDtcCatalog); begin @@ -114,9 +162,17 @@ procedure TOBDOEMExtensionDetroitDiesel.SeedDefaultDtcCatalog( MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionDetroitDiesel.DtcCatalogFileName: string; -begin Result := 'dtc-detroit.json'; end; +begin + Result := 'dtc-detroit.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionDetroitDiesel.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.DiagSession.pas b/src/Services/OBD.OEM.DiagSession.pas index c43925e0..7abba932 100644 --- a/src/Services/OBD.OEM.DiagSession.pas +++ b/src/Services/OBD.OEM.DiagSession.pas @@ -56,54 +56,76 @@ TOBDDiagSession = class const Ext: IOBDOEMExtension); destructor Destroy; override; - /// Run the OEM negotiator's begin-session plan. Starts - /// the tester-present heartbeat on success. Idempotent for the - /// same session type — calling twice with the same args is a - /// no-op. + /// + /// Run the OEM negotiator's begin-session plan. Starts + /// the tester-present heartbeat on success. Idempotent for the + /// same session type — calling twice with the same args is a + /// no-op. + /// function BeginSession(const SessionType: TOBDSessionType; const ECUAddress: Word = 0): Boolean; - /// Run the OEM negotiator's end-session plan. Stops the - /// heartbeat, returns to dssIdle. Safe to call from any - /// state. + /// + /// Run the OEM negotiator's end-session plan. Stops the + /// heartbeat, returns to dssIdle. Safe to call from any + /// state. + /// function EndSession: Boolean; - /// UDS SecurityAccess: 27 LL → 67 LL SEED → 27 LL+1 KEY. - /// Uses the OEM extension's seed-key registry by default; the - /// optional Algorithm parameter overrides it (production - /// users plug their NDA-protected algorithm in here). + /// + /// UDS SecurityAccess: 27 LL → 67 LL SEED → 27 LL+1 KEY. + /// Uses the OEM extension's seed-key registry by default; the + /// optional Algorithm parameter overrides it (production + /// users plug their NDA-protected algorithm in here). + /// function UnlockSecurityAccess(const Level: Byte; const Algorithm: IOBDSeedKeyAlgorithm = nil): Boolean; - /// UDS ReadDataByIdentifier (22 HiDID LoDID). Returns - /// the raw payload (everything past 62 HiDID LoDID). - /// Negative replies set LastError and return False. + /// + /// UDS ReadDataByIdentifier (22 HiDID LoDID). Returns + /// the raw payload (everything past 62 HiDID LoDID). + /// Negative replies set LastError and return False. + /// function ReadDID(const DID: Word; out Payload: TBytes): Boolean; overload; - /// Read + decode a DID via the OEM's DecodeDID. - /// Useful for tool UIs that just want the human-readable string. + /// + /// Read + decode a DID via the OEM's DecodeDID. + /// Useful for tool UIs that just want the human-readable string. + /// function ReadDID(const DID: Word; out Decoded: string): Boolean; overload; - /// Run RoutineControl 31 01 with optional input data. - /// Returns the status payload (everything past 71 01 RID). + /// + /// Run RoutineControl 31 01 with optional input data. + /// Returns the status payload (everything past 71 01 RID). + /// function StartRoutine(const RID: Word; const InputData: TBytes; out Status: TBytes): Boolean; - /// RoutineControl 31 02 — stop the named routine. + /// + /// RoutineControl 31 02 — stop the named routine. + /// function StopRoutine(const RID: Word): Boolean; - /// RoutineControl 31 03 — request the routine's results. + /// + /// RoutineControl 31 03 — request the routine's results. + /// function RequestRoutineResults(const RID: Word; out Status: TBytes): Boolean; - /// State accessor. + /// + /// State accessor. + /// function State: TOBDDiagSessionState; - /// Most recent failure detail; cleared on each successful - /// high-level call. + /// + /// Most recent failure detail; cleared on each successful + /// high-level call. + /// property LastError: string read FLastError; - /// The OEM extension this session was bound to. + /// + /// The OEM extension this session was bound to. + /// property OEM: IOBDOEMExtension read FOEM; end; @@ -112,6 +134,9 @@ implementation uses OBD.OEM.Coding, OBD.Async; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDDiagSession.Create(const Conn: TOBDConnectionAsync; const Ext: IOBDOEMExtension); begin @@ -128,6 +153,9 @@ constructor TOBDDiagSession.Create(const Conn: TOBDConnectionAsync; FLock := TCriticalSection.Create; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDDiagSession.Destroy; begin StopHeartbeat; @@ -141,6 +169,9 @@ destructor TOBDDiagSession.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// STOP HEARTBEAT +//------------------------------------------------------------------------------ procedure TOBDDiagSession.StopHeartbeat; begin if FHeartbeat = nil then Exit; @@ -151,6 +182,9 @@ procedure TOBDDiagSession.StopHeartbeat; end; end; +//------------------------------------------------------------------------------ +// RESOLVE SEED KEY +//------------------------------------------------------------------------------ function TOBDDiagSession.ResolveSeedKey(const Level: Byte; const Override_: IOBDSeedKeyAlgorithm): IOBDSeedKeyAlgorithm; begin @@ -158,6 +192,9 @@ function TOBDDiagSession.ResolveSeedKey(const Level: Byte; Result := FOEM.SeedKeyRegistry.Find(Level); end; +//------------------------------------------------------------------------------ +// AWAIT OBD +//------------------------------------------------------------------------------ function TOBDDiagSession.AwaitOBD(const HexCommand: string; const TimeoutMs: Cardinal): TBytes; var @@ -169,6 +206,9 @@ function TOBDDiagSession.AwaitOBD(const HexCommand: string; Result := HexStringToBytes(Reply); end; +//------------------------------------------------------------------------------ +// FORMAT BYTES +//------------------------------------------------------------------------------ function FormatBytes(const Bytes: TBytes): string; var I: Integer; @@ -184,6 +224,10 @@ function FormatBytes(const Bytes: TBytes): string; //============================================================================== // BeginSession / EndSession //============================================================================== + +//------------------------------------------------------------------------------ +// BEGIN SESSION +//------------------------------------------------------------------------------ function TOBDDiagSession.BeginSession(const SessionType: TOBDSessionType; const ECUAddress: Word): Boolean; var @@ -223,6 +267,9 @@ function TOBDDiagSession.BeginSession(const SessionType: TOBDSessionType; end; end; +//------------------------------------------------------------------------------ +// END SESSION +//------------------------------------------------------------------------------ function TOBDDiagSession.EndSession: Boolean; var Plan: TOBDSessionPlan; @@ -254,6 +301,10 @@ function TOBDDiagSession.EndSession: Boolean; //============================================================================== // SecurityAccess //============================================================================== + +//------------------------------------------------------------------------------ +// UNLOCK SECURITY ACCESS +//------------------------------------------------------------------------------ function TOBDDiagSession.UnlockSecurityAccess(const Level: Byte; const Algorithm: IOBDSeedKeyAlgorithm): Boolean; var @@ -317,6 +368,10 @@ function TOBDDiagSession.UnlockSecurityAccess(const Level: Byte; //============================================================================== // ReadDataByIdentifier //============================================================================== + +//------------------------------------------------------------------------------ +// READ DID +//------------------------------------------------------------------------------ function TOBDDiagSession.ReadDID(const DID: Word; out Payload: TBytes): Boolean; var Reply: TBytes; @@ -349,6 +404,9 @@ function TOBDDiagSession.ReadDID(const DID: Word; out Payload: TBytes): Boolean; end; end; +//------------------------------------------------------------------------------ +// READ DID +//------------------------------------------------------------------------------ function TOBDDiagSession.ReadDID(const DID: Word; out Decoded: string): Boolean; var @@ -363,6 +421,10 @@ function TOBDDiagSession.ReadDID(const DID: Word; //============================================================================== // RoutineControl //============================================================================== + +//------------------------------------------------------------------------------ +// START ROUTINE +//------------------------------------------------------------------------------ function TOBDDiagSession.StartRoutine(const RID: Word; const InputData: TBytes; out Status: TBytes): Boolean; var @@ -389,6 +451,9 @@ function TOBDDiagSession.StartRoutine(const RID: Word; end; end; +//------------------------------------------------------------------------------ +// STOP ROUTINE +//------------------------------------------------------------------------------ function TOBDDiagSession.StopRoutine(const RID: Word): Boolean; var Request, Reply, Discard: TBytes; @@ -414,6 +479,9 @@ function TOBDDiagSession.StopRoutine(const RID: Word): Boolean; end; end; +//------------------------------------------------------------------------------ +// REQUEST ROUTINE RESULTS +//------------------------------------------------------------------------------ function TOBDDiagSession.RequestRoutineResults(const RID: Word; out Status: TBytes): Boolean; var @@ -440,6 +508,9 @@ function TOBDDiagSession.RequestRoutineResults(const RID: Word; end; end; +//------------------------------------------------------------------------------ +// STATE +//------------------------------------------------------------------------------ function TOBDDiagSession.State: TOBDDiagSessionState; begin FLock.Enter; diff --git a/src/Services/OBD.OEM.DoIP.pas b/src/Services/OBD.OEM.DoIP.pas index 45d0e203..aaee8196 100644 --- a/src/Services/OBD.OEM.DoIP.pas +++ b/src/Services/OBD.OEM.DoIP.pas @@ -29,14 +29,18 @@ interface type EOBDDoIPError = class(Exception); - /// ISO 13400-2 protocol versions used in the wild. + /// + /// ISO 13400-2 protocol versions used in the wild. + /// TOBDDoIPVersion = ( dpv2010 = $01, // 2010 edition dpv2012 = $02, // 2012 edition (most current vehicles) dpvDefault = $02 ); - /// Common DoIP payload type codes (ISO 13400-2 §5.4). + /// + /// Common DoIP payload type codes (ISO 13400-2 §5.4). + /// TOBDDoIPPayloadType = ( dptGenericNack = $0000, dptVehicleIdentRequest = $0001, @@ -56,7 +60,9 @@ EOBDDoIPError = class(Exception); dptDiagnosticMessageNack = $8003 ); - /// RoutingActivation request types (ISO 13400-2 §7.4.4.2). + /// + /// RoutingActivation request types (ISO 13400-2 §7.4.4.2). + /// TOBDDoIPActivationType = ( datDefault = $00, datWWHOBD = $01, @@ -64,7 +70,9 @@ EOBDDoIPError = class(Exception); datOEMSpecific = $E0 ); - /// RoutingActivation response codes (ISO 13400-2 §7.4.5.4). + /// + /// RoutingActivation response codes (ISO 13400-2 §7.4.5.4). + /// TOBDDoIPRoutingResponse = ( drrUnknownSourceAddress = $00, drrAllSocketsRegistered = $01, @@ -84,7 +92,9 @@ TOBDDoIPHeader = record PayloadLength: Cardinal; end; - /// Decoded RoutingActivationResponse. + /// + /// Decoded RoutingActivationResponse. + /// TOBDDoIPRoutingActivation = record TesterLogicalAddress: Word; EntityLogicalAddress: Word; @@ -93,7 +103,9 @@ TOBDDoIPRoutingActivation = record OEMSpecific: Cardinal; end; - /// Decoded VehicleAnnouncement / VehicleIdentResponse. + /// + /// Decoded VehicleAnnouncement / VehicleIdentResponse. + /// TOBDDoIPVehicleAnnouncement = record VIN: string; // 17 ASCII LogicalAddress: Word; @@ -103,8 +115,10 @@ TOBDDoIPVehicleAnnouncement = record SyncStatus: Byte; // optional in v2012 end; - /// Decoded DiagnosticMessage payload (just the wrapper — - /// the inner UDS bytes go to the runner / DiagSession). + /// + /// Decoded DiagnosticMessage payload (just the wrapper — + /// the inner UDS bytes go to the runner / DiagSession). + /// TOBDDoIPDiagnosticMessage = record SourceAddress: Word; TargetAddress: Word; @@ -118,8 +132,10 @@ function BuildDoIPHeader(const Version: TOBDDoIPVersion; const PayloadType: TOBDDoIPPayloadType; const PayloadLength: Cardinal): TBytes; -/// Parse the 8-byte header. Throws on the protocol-version -/// inversion check (Version XOR InvVersion must equal 0xFF). +/// +/// Parse the 8-byte header. Throws on the protocol-version +/// inversion check (Version XOR InvVersion must equal 0xFF). +/// function ParseDoIPHeader(const Bytes: TBytes; out Header: TOBDDoIPHeader): Integer; //============================================================================== @@ -175,12 +191,19 @@ implementation //============================================================================== // Endianness + header helpers (ISO 13400 is big-endian) //============================================================================== + +//------------------------------------------------------------------------------ +// WRITE UINT16 BE +//------------------------------------------------------------------------------ procedure WriteUInt16BE(var Buf: TBytes; const Offset: Integer; const Value: Word); begin Buf[Offset] := Byte(Value shr 8); Buf[Offset + 1] := Byte(Value and $FF); end; +//------------------------------------------------------------------------------ +// WRITE UINT32 BE +//------------------------------------------------------------------------------ procedure WriteUInt32BE(var Buf: TBytes; const Offset: Integer; const Value: Cardinal); begin Buf[Offset] := Byte(Value shr 24); @@ -189,11 +212,17 @@ procedure WriteUInt32BE(var Buf: TBytes; const Offset: Integer; const Value: Car Buf[Offset + 3] := Byte(Value and $FF); end; +//------------------------------------------------------------------------------ +// READ UINT16 BE +//------------------------------------------------------------------------------ function ReadUInt16BE(const Buf: TBytes; const Offset: Integer): Word; begin Result := (Word(Buf[Offset]) shl 8) or Buf[Offset + 1]; end; +//------------------------------------------------------------------------------ +// READ UINT32 BE +//------------------------------------------------------------------------------ function ReadUInt32BE(const Buf: TBytes; const Offset: Integer): Cardinal; begin Result := (Cardinal(Buf[Offset]) shl 24) or @@ -202,6 +231,9 @@ function ReadUInt32BE(const Buf: TBytes; const Offset: Integer): Cardinal; Cardinal(Buf[Offset + 3]); end; +//------------------------------------------------------------------------------ +// BUILD DO IPHEADER +//------------------------------------------------------------------------------ function BuildDoIPHeader(const Version: TOBDDoIPVersion; const PayloadType: TOBDDoIPPayloadType; const PayloadLength: Cardinal): TBytes; @@ -213,6 +245,9 @@ function BuildDoIPHeader(const Version: TOBDDoIPVersion; WriteUInt32BE(Result, 4, PayloadLength); end; +//------------------------------------------------------------------------------ +// PARSE DO IPHEADER +//------------------------------------------------------------------------------ function ParseDoIPHeader(const Bytes: TBytes; out Header: TOBDDoIPHeader): Integer; begin @@ -231,6 +266,9 @@ function ParseDoIPHeader(const Bytes: TBytes; Result := DOIP_HEADER_SIZE; end; +//------------------------------------------------------------------------------ +// BYTES APPEND +//------------------------------------------------------------------------------ function BytesAppend(const A, B: TBytes): TBytes; begin SetLength(Result, Length(A) + Length(B)); @@ -241,6 +279,10 @@ function BytesAppend(const A, B: TBytes): TBytes; //============================================================================== // Routing activation //============================================================================== + +//------------------------------------------------------------------------------ +// BUILD ROUTING ACTIVATION REQUEST +//------------------------------------------------------------------------------ function BuildRoutingActivationRequest( const TesterAddress: Word; const ActivationType: TOBDDoIPActivationType; @@ -260,6 +302,9 @@ function BuildRoutingActivationRequest( Result := BytesAppend(Header, Payload); end; +//------------------------------------------------------------------------------ +// PARSE ROUTING ACTIVATION RESPONSE +//------------------------------------------------------------------------------ function ParseRoutingActivationResponse(const Bytes: TBytes; out Activation: TOBDDoIPRoutingActivation): Boolean; var @@ -289,12 +334,19 @@ function ParseRoutingActivationResponse(const Bytes: TBytes; //============================================================================== // Vehicle announcement / ident //============================================================================== + +//------------------------------------------------------------------------------ +// BUILD VEHICLE IDENT REQUEST +//------------------------------------------------------------------------------ function BuildVehicleIdentRequest(const Version: TOBDDoIPVersion): TBytes; begin // No payload — broadcast on UDP/13400. Result := BuildDoIPHeader(Version, dptVehicleIdentRequest, 0); end; +//------------------------------------------------------------------------------ +// BUILD VEHICLE IDENT REQUEST BY VIN +//------------------------------------------------------------------------------ function BuildVehicleIdentRequestByVIN(const VIN: string; const Version: TOBDDoIPVersion): TBytes; var @@ -313,6 +365,9 @@ function BuildVehicleIdentRequestByVIN(const VIN: string; Result := BytesAppend(Header, Payload); end; +//------------------------------------------------------------------------------ +// PARSE VEHICLE ANNOUNCEMENT +//------------------------------------------------------------------------------ function ParseVehicleAnnouncement(const Bytes: TBytes; out Announcement: TOBDDoIPVehicleAnnouncement): Boolean; var @@ -348,11 +403,18 @@ function ParseVehicleAnnouncement(const Bytes: TBytes; //============================================================================== // Alive check //============================================================================== + +//------------------------------------------------------------------------------ +// BUILD ALIVE CHECK REQUEST +//------------------------------------------------------------------------------ function BuildAliveCheckRequest(const Version: TOBDDoIPVersion): TBytes; begin Result := BuildDoIPHeader(Version, dptAliveCheckRequest, 0); end; +//------------------------------------------------------------------------------ +// BUILD ALIVE CHECK RESPONSE +//------------------------------------------------------------------------------ function BuildAliveCheckResponse(const TesterAddress: Word; const Version: TOBDDoIPVersion): TBytes; var @@ -367,6 +429,10 @@ function BuildAliveCheckResponse(const TesterAddress: Word; //============================================================================== // Diagnostic message //============================================================================== + +//------------------------------------------------------------------------------ +// BUILD DIAGNOSTIC MESSAGE +//------------------------------------------------------------------------------ function BuildDiagnosticMessage(const SourceAddress, TargetAddress: Word; const UserData: TBytes; const Version: TOBDDoIPVersion): TBytes; var @@ -383,6 +449,9 @@ function BuildDiagnosticMessage(const SourceAddress, TargetAddress: Word; Result := BytesAppend(Header, Payload); end; +//------------------------------------------------------------------------------ +// PARSE DIAGNOSTIC MESSAGE +//------------------------------------------------------------------------------ function ParseDiagnosticMessage(const Bytes: TBytes; out Msg: TOBDDoIPDiagnosticMessage): Boolean; var diff --git a/src/Services/OBD.OEM.Ferrari.pas b/src/Services/OBD.OEM.Ferrari.pas index b2adf553..a9ff0417 100644 --- a/src/Services/OBD.OEM.Ferrari.pas +++ b/src/Services/OBD.OEM.Ferrari.pas @@ -24,13 +24,18 @@ interface TOBDOEMExtensionFerrari = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -47,21 +52,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionFerrari.ManufacturerKey: string; -begin Result := 'FERRARI'; end; +begin + Result := 'FERRARI'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionFerrari.DisplayName: string; -begin Result := 'Ferrari N.V.'; end; +begin + Result := 'Ferrari N.V.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionFerrari.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in ferrari.json. Result := VINMatchesCatalog('ferrari.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionFerrari.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are ferrari.json // + uds-standard.json. Hardcoded entries removed. @@ -72,16 +97,28 @@ procedure TOBDOEMExtensionFerrari.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionFerrari.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('ferrari.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionFerrari.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin @@ -91,6 +128,9 @@ procedure TOBDOEMExtensionFerrari.SeedDefaultSeedKeyAlgorithms( Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionFerrari.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -98,9 +138,17 @@ procedure TOBDOEMExtensionFerrari.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionFerrari.DtcCatalogFileName: string; -begin Result := 'dtc-ferrari.json'; end; +begin + Result := 'dtc-ferrari.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionFerrari.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Ford.pas b/src/Services/OBD.OEM.Ford.pas index e277b60a..a589f529 100644 --- a/src/Services/OBD.OEM.Ford.pas +++ b/src/Services/OBD.OEM.Ford.pas @@ -32,13 +32,18 @@ TOBDFordSessionNegotiator = class(TOBDStandardSessionNegotiator) TOBDOEMExtensionFord = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -56,6 +61,9 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// BEGIN SESSION PLAN +//------------------------------------------------------------------------------ function TOBDFordSessionNegotiator.BeginSessionPlan( SessionType: TOBDSessionType; const ECUAddress: Word): TOBDSessionPlan; @@ -72,16 +80,25 @@ function TOBDFordSessionNegotiator.BeginSessionPlan( end; end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDFordSessionNegotiator.DisplayName: string; begin Result := 'Ford IDS / FDRS'; end; +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionFord.CreateSessionNegotiator: IOBDSessionNegotiator; begin Result := TOBDFordSessionNegotiator.Create; end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionFord.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); const @@ -99,6 +116,9 @@ procedure TOBDOEMExtensionFord.SeedDefaultSeedKeyAlgorithms( 'forscan-community', False)); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionFord.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -106,23 +126,49 @@ procedure TOBDOEMExtensionFord.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionFord.DtcCatalogFileName: string; -begin Result := 'dtc-ford.json'; end; +begin + Result := 'dtc-ford.json'; +end; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionFord.ManufacturerKey: string; -begin Result := 'FORD'; end; +begin + Result := 'FORD'; +end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionFord.DisplayName: string; -begin Result := 'Ford Motor Company'; end; +begin + Result := 'Ford Motor Company'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionFord.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in ford.json. Result := VINMatchesCatalog('ford.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionFord.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are ford.json // + uds-standard.json. Hardcoded entries removed. @@ -133,16 +179,28 @@ procedure TOBDOEMExtensionFord.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionFord.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('ford.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionFord.DecodeDID(const DID: Word; const Payload: TBytes): string; var diff --git a/src/Services/OBD.OEM.GM.pas b/src/Services/OBD.OEM.GM.pas index e365e065..712db4d5 100644 --- a/src/Services/OBD.OEM.GM.pas +++ b/src/Services/OBD.OEM.GM.pas @@ -37,13 +37,18 @@ TOBDGMSessionNegotiator = class(TOBDStandardSessionNegotiator) TOBDOEMExtensionGM = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -61,6 +66,9 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// BEGIN SESSION PLAN +//------------------------------------------------------------------------------ function TOBDGMSessionNegotiator.BeginSessionPlan( SessionType: TOBDSessionType; const ECUAddress: Word): TOBDSessionPlan; @@ -72,16 +80,25 @@ function TOBDGMSessionNegotiator.BeginSessionPlan( ] + Result.Steps; end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDGMSessionNegotiator.DisplayName: string; begin Result := 'GM Tech 2 / GDS-2'; end; +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionGM.CreateSessionNegotiator: IOBDSessionNegotiator; begin Result := TOBDGMSessionNegotiator.Create; end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionGM.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); const @@ -98,6 +115,9 @@ procedure TOBDOEMExtensionGM.SeedDefaultSeedKeyAlgorithms( 'GMLAN Class B trial-mode constant key', 'gmlan-public', False)); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionGM.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -105,23 +125,49 @@ procedure TOBDOEMExtensionGM.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionGM.DtcCatalogFileName: string; -begin Result := 'dtc-gm.json'; end; +begin + Result := 'dtc-gm.json'; +end; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionGM.ManufacturerKey: string; -begin Result := 'GM'; end; +begin + Result := 'GM'; +end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionGM.DisplayName: string; -begin Result := 'General Motors'; end; +begin + Result := 'General Motors'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionGM.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in gm.json. Result := VINMatchesCatalog('gm.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionGM.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are gm.json // + uds-standard.json. Hardcoded entries removed. @@ -132,16 +178,28 @@ procedure TOBDOEMExtensionGM.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionGM.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('gm.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionGM.DecodeDID(const DID: Word; const Payload: TBytes): string; var diff --git a/src/Services/OBD.OEM.Geely.pas b/src/Services/OBD.OEM.Geely.pas index 448deb87..13af404d 100644 --- a/src/Services/OBD.OEM.Geely.pas +++ b/src/Services/OBD.OEM.Geely.pas @@ -24,13 +24,18 @@ interface TOBDOEMExtensionGeely = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -47,21 +52,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionGeely.ManufacturerKey: string; -begin Result := 'GEELY'; end; +begin + Result := 'GEELY'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionGeely.DisplayName: string; -begin Result := 'Geely Auto / Lynk & Co'; end; +begin + Result := 'Geely Auto / Lynk & Co'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionGeely.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in geely.json. Result := VINMatchesCatalog('geely.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionGeely.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are geely.json // + uds-standard.json. Hardcoded entries removed. @@ -72,22 +97,37 @@ procedure TOBDOEMExtensionGeely.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionGeely.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('geely.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionGeely.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionGeely.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -95,9 +135,17 @@ procedure TOBDOEMExtensionGeely.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionGeely.DtcCatalogFileName: string; -begin Result := 'dtc-geely.json'; end; +begin + Result := 'dtc-geely.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionGeely.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.GoldenCheck.pas b/src/Services/OBD.OEM.GoldenCheck.pas index cc0c56d5..ed90fbb4 100644 --- a/src/Services/OBD.OEM.GoldenCheck.pas +++ b/src/Services/OBD.OEM.GoldenCheck.pas @@ -24,10 +24,14 @@ interface TOBDGoldenVector = record DID: Word; Payload: TBytes; - /// Substring the decoder's output must contain. Empty - /// means "any non-empty output passes". + /// + /// Substring the decoder's output must contain. Empty + /// means "any non-empty output passes". + /// ExpectedSubstring: string; - /// Free-text label for failure messages. + /// + /// Free-text label for failure messages. + /// Description: string; end; @@ -37,12 +41,16 @@ TOBDGoldenFailure = record Reason: string; end; -/// Build a vector inline. +/// +/// Build a vector inline. +/// function GoldenVector(const DID: Word; const Payload: TBytes; const ExpectedSubstring, Description: string): TOBDGoldenVector; -/// Run every vector through Ext.DecodeDID. Returns -/// the failures list — empty when every vector passed. +/// +/// Run every vector through Ext.DecodeDID. Returns +/// the failures list — empty when every vector passed. +/// function CheckGoldenVectors(const Ext: IOBDOEMExtension; const Vectors: TArray): TArray; @@ -51,6 +59,9 @@ implementation uses System.Generics.Collections; +//------------------------------------------------------------------------------ +// GOLDEN VECTOR +//------------------------------------------------------------------------------ function GoldenVector(const DID: Word; const Payload: TBytes; const ExpectedSubstring, Description: string): TOBDGoldenVector; begin @@ -60,6 +71,9 @@ function GoldenVector(const DID: Word; const Payload: TBytes; Result.Description := Description; end; +//------------------------------------------------------------------------------ +// CHECK GOLDEN VECTORS +//------------------------------------------------------------------------------ function CheckGoldenVectors(const Ext: IOBDOEMExtension; const Vectors: TArray): TArray; var diff --git a/src/Services/OBD.OEM.GreatWall.pas b/src/Services/OBD.OEM.GreatWall.pas index 12288f4f..d33f8ad9 100644 --- a/src/Services/OBD.OEM.GreatWall.pas +++ b/src/Services/OBD.OEM.GreatWall.pas @@ -24,13 +24,18 @@ interface TOBDOEMExtensionGreatWall = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -47,21 +52,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionGreatWall.ManufacturerKey: string; -begin Result := 'GWM'; end; +begin + Result := 'GWM'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionGreatWall.DisplayName: string; -begin Result := 'Great Wall Motor (Haval / WEY / ORA / Tank / Poer)'; end; +begin + Result := 'Great Wall Motor (Haval / WEY / ORA / Tank / Poer)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionGreatWall.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in gwm.json. Result := VINMatchesCatalog('gwm.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionGreatWall.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are gwm.json // + uds-standard.json. Hardcoded entries removed. @@ -72,22 +97,37 @@ procedure TOBDOEMExtensionGreatWall.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionGreatWall.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('gwm.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionGreatWall.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionGreatWall.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -95,9 +135,17 @@ procedure TOBDOEMExtensionGreatWall.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionGreatWall.DtcCatalogFileName: string; -begin Result := 'dtc-gwm.json'; end; +begin + Result := 'dtc-gwm.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionGreatWall.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.HD.pas b/src/Services/OBD.OEM.HD.pas index 67a81399..4836166b 100644 --- a/src/Services/OBD.OEM.HD.pas +++ b/src/Services/OBD.OEM.HD.pas @@ -33,9 +33,11 @@ interface System.SysUtils, OBD.OEM.Session; const - /// SAE J1939-71 source-address allocations the framework - /// references. The full table lives in J1939-71 Annex F; these - /// are the most-queried for diagnostics. + /// + /// SAE J1939-71 source-address allocations the framework + /// references. The full table lives in J1939-71 Annex F; these + /// are the most-queried for diagnostics. + /// J1939_ADDR_ENGINE_1 = $00; // 0 — Engine #1 (primary) J1939_ADDR_ENGINE_2 = $01; // 1 — Engine #2 J1939_ADDR_TRANSMISSION_1 = $03; // 3 — Transmission @@ -72,30 +74,50 @@ TOBDHDSessionNegotiator = class(TOBDStandardSessionNegotiator) function DisplayName: string; override; end; -/// Build an SPN-FMI DTC code in the canonical -/// "SPN0094-FMI4" form used by the catalog. Used by per-OEM -/// extensions to populate TOBDDtcCatalogEntry.Code. +/// +/// Build an SPN-FMI DTC code in the canonical +/// "SPN0094-FMI4" form used by the catalog. Used by per-OEM +/// extensions to populate TOBDDtcCatalogEntry.Code. +/// function FormatSPNFMI(const SPN: Cardinal; const FMI: Byte): string; -/// Decode a J1939-73 DM1 packed DTC payload (4 bytes per -/// active DTC: SPN[16:0] + FMI[4:0] + occurrence count[6:0] + -/// conversion-method bit) into the canonical string form. Returns -/// an empty string on malformed input. +/// +/// Decode a J1939-73 DM1 packed DTC payload (4 bytes per +/// active DTC: SPN[16:0] + FMI[4:0] + occurrence count[6:0] + +/// conversion-method bit) into the canonical string form. Returns +/// an empty string on malformed input. +/// function ParseDM1DTC(const Bytes: TBytes; const Offset: Integer): string; implementation +//------------------------------------------------------------------------------ +// DEFAULT TESTER PRESENT MS +//------------------------------------------------------------------------------ function TOBDHDSessionNegotiator.DefaultTesterPresentMs: Cardinal; -begin Result := 3000; end; +begin + Result := 3000; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDHDSessionNegotiator.DisplayName: string; -begin Result := 'J1939 / ISO 14229 over J1939-21'; end; +begin + Result := 'J1939 / ISO 14229 over J1939-21'; +end; +//------------------------------------------------------------------------------ +// FORMAT SPNFMI +//------------------------------------------------------------------------------ function FormatSPNFMI(const SPN: Cardinal; const FMI: Byte): string; begin Result := Format('SPN%.4d-FMI%d', [SPN, FMI]); end; +//------------------------------------------------------------------------------ +// PARSE DM1 DTC +//------------------------------------------------------------------------------ function ParseDM1DTC(const Bytes: TBytes; const Offset: Integer): string; var B0, B1, B2, B3: Byte; diff --git a/src/Services/OBD.OEM.Helpers.pas b/src/Services/OBD.OEM.Helpers.pas index 4651a20e..343ba7c4 100644 --- a/src/Services/OBD.OEM.Helpers.pas +++ b/src/Services/OBD.OEM.Helpers.pas @@ -30,6 +30,9 @@ function ECU(const AAddress: Word; implementation +//------------------------------------------------------------------------------ +// DID +//------------------------------------------------------------------------------ function DID(const ADID: Word; const AName, ADescription: string): TOBDOEMDataIdentifier; begin @@ -39,6 +42,9 @@ function DID(const ADID: Word; Result.Description := ADescription; end; +//------------------------------------------------------------------------------ +// DID +//------------------------------------------------------------------------------ function DID(const ADID: Word; const AName, ADescription: string; const AEcuAddress: Word): TOBDOEMDataIdentifier; @@ -47,6 +53,9 @@ function DID(const ADID: Word; Result.EcuAddress := AEcuAddress; end; +//------------------------------------------------------------------------------ +// ROUTINE +//------------------------------------------------------------------------------ function Routine(const AIdentifier: Word; const AName, ADescription: string): TOBDOEMRoutine; begin @@ -56,6 +65,9 @@ function Routine(const AIdentifier: Word; Result.Description := ADescription; end; +//------------------------------------------------------------------------------ +// ROUTINE +//------------------------------------------------------------------------------ function Routine(const AIdentifier: Word; const AName, ADescription: string; const AEcuAddress: Word): TOBDOEMRoutine; @@ -64,6 +76,9 @@ function Routine(const AIdentifier: Word; Result.EcuAddress := AEcuAddress; end; +//------------------------------------------------------------------------------ +// ECU +//------------------------------------------------------------------------------ function ECU(const AAddress: Word; const AName, ACommonName: string): TOBDOEMECU; begin diff --git a/src/Services/OBD.OEM.Honda.pas b/src/Services/OBD.OEM.Honda.pas index 1c3f3fda..67859a5b 100644 --- a/src/Services/OBD.OEM.Honda.pas +++ b/src/Services/OBD.OEM.Honda.pas @@ -21,13 +21,18 @@ interface TOBDOEMExtensionHonda = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -44,21 +49,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionHonda.ManufacturerKey: string; -begin Result := 'HONDA'; end; +begin + Result := 'HONDA'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionHonda.DisplayName: string; -begin Result := 'Honda Motor Co. (incl. Acura)'; end; +begin + Result := 'Honda Motor Co. (incl. Acura)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionHonda.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in honda.json. Result := VINMatchesCatalog('honda.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionHonda.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are honda.json // + uds-standard.json. Hardcoded entries removed. @@ -69,16 +94,28 @@ procedure TOBDOEMExtensionHonda.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionHonda.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('honda.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionHonda.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); const @@ -95,6 +132,9 @@ procedure TOBDOEMExtensionHonda.SeedDefaultSeedKeyAlgorithms( 'Honda HDS community XOR-mask placeholder', 'community-pr', False)); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionHonda.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -102,9 +142,17 @@ procedure TOBDOEMExtensionHonda.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionHonda.DtcCatalogFileName: string; -begin Result := 'dtc-honda.json'; end; +begin + Result := 'dtc-honda.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionHonda.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.HyundaiKia.pas b/src/Services/OBD.OEM.HyundaiKia.pas index b452ae94..7cfbc365 100644 --- a/src/Services/OBD.OEM.HyundaiKia.pas +++ b/src/Services/OBD.OEM.HyundaiKia.pas @@ -19,9 +19,11 @@ interface System.SysUtils, OBD.OEM, OBD.OEM.Session, OBD.OEM.SeedKey, OBD.OEM.DTC; type - /// HMG (Hyundai Motor Group) session negotiator. GDS uses - /// a 1500 ms tester-present interval which most pre-2018 ECUs - /// require to keep the extended session alive. + /// + /// HMG (Hyundai Motor Group) session negotiator. GDS uses + /// a 1500 ms tester-present interval which most pre-2018 ECUs + /// require to keep the extended session alive. + /// TOBDHyundaiKiaSessionNegotiator = class(TOBDStandardSessionNegotiator) public function DefaultTesterPresentMs: Cardinal; override; @@ -31,13 +33,18 @@ TOBDHyundaiKiaSessionNegotiator = class(TOBDStandardSessionNegotiator) TOBDOEMExtensionHyundaiKia = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -55,27 +62,57 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// DEFAULT TESTER PRESENT MS +//------------------------------------------------------------------------------ function TOBDHyundaiKiaSessionNegotiator.DefaultTesterPresentMs: Cardinal; -begin Result := 1500; end; +begin + Result := 1500; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDHyundaiKiaSessionNegotiator.DisplayName: string; -begin Result := 'Hyundai/Kia GDS / KDS'; end; +begin + Result := 'Hyundai/Kia GDS / KDS'; +end; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionHyundaiKia.ManufacturerKey: string; -begin Result := 'HMG'; end; +begin + Result := 'HMG'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionHyundaiKia.DisplayName: string; -begin Result := 'Hyundai Motor Group (Hyundai / Kia / Genesis)'; end; +begin + Result := 'Hyundai Motor Group (Hyundai / Kia / Genesis)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionHyundaiKia.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in hmg.json. Result := VINMatchesCatalog('hmg.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionHyundaiKia.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are hmg.json // + uds-standard.json. Hardcoded entries removed. @@ -86,19 +123,36 @@ procedure TOBDOEMExtensionHyundaiKia.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionHyundaiKia.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('hmg.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionHyundaiKia.CreateSessionNegotiator: IOBDSessionNegotiator; -begin Result := TOBDHyundaiKiaSessionNegotiator.Create; end; +begin + Result := TOBDHyundaiKiaSessionNegotiator.Create; +end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionHyundaiKia.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); const @@ -112,6 +166,9 @@ procedure TOBDOEMExtensionHyundaiKia.SeedDefaultSeedKeyAlgorithms( 'HMG community XOR-mask placeholder', 'community-pr', False)); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionHyundaiKia.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -119,9 +176,17 @@ procedure TOBDOEMExtensionHyundaiKia.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionHyundaiKia.DtcCatalogFileName: string; -begin Result := 'dtc-hmg.json'; end; +begin + Result := 'dtc-hmg.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionHyundaiKia.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Isuzu.pas b/src/Services/OBD.OEM.Isuzu.pas index 8bb43ac0..f2731fd0 100644 --- a/src/Services/OBD.OEM.Isuzu.pas +++ b/src/Services/OBD.OEM.Isuzu.pas @@ -23,13 +23,18 @@ interface TOBDOEMExtensionIsuzu = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -47,21 +52,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionIsuzu.ManufacturerKey: string; -begin Result := 'ISUZU'; end; +begin + Result := 'ISUZU'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionIsuzu.DisplayName: string; -begin Result := 'Isuzu Motors Ltd.'; end; +begin + Result := 'Isuzu Motors Ltd.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionIsuzu.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in isuzu.json. Result := VINMatchesCatalog('isuzu.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionIsuzu.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are isuzu.json // + uds-standard.json. Hardcoded entries removed. @@ -72,25 +97,45 @@ procedure TOBDOEMExtensionIsuzu.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionIsuzu.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('isuzu.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionIsuzu.CreateSessionNegotiator: IOBDSessionNegotiator; -begin Result := TOBDHDSessionNegotiator.Create; end; +begin + Result := TOBDHDSessionNegotiator.Create; +end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionIsuzu.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionIsuzu.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -98,9 +143,17 @@ procedure TOBDOEMExtensionIsuzu.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionIsuzu.DtcCatalogFileName: string; -begin Result := 'dtc-isuzu.json'; end; +begin + Result := 'dtc-isuzu.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionIsuzu.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Iveco.pas b/src/Services/OBD.OEM.Iveco.pas index 59286ba1..aa0a3b48 100644 --- a/src/Services/OBD.OEM.Iveco.pas +++ b/src/Services/OBD.OEM.Iveco.pas @@ -24,13 +24,18 @@ interface TOBDOEMExtensionIveco = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -48,21 +53,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionIveco.ManufacturerKey: string; -begin Result := 'IVECO'; end; +begin + Result := 'IVECO'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionIveco.DisplayName: string; -begin Result := 'Iveco S.p.A. (Iveco Group)'; end; +begin + Result := 'Iveco S.p.A. (Iveco Group)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionIveco.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in iveco.json. Result := VINMatchesCatalog('iveco.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionIveco.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are iveco.json // + uds-standard.json. Hardcoded entries removed. @@ -73,25 +98,45 @@ procedure TOBDOEMExtensionIveco.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionIveco.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('iveco.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionIveco.CreateSessionNegotiator: IOBDSessionNegotiator; -begin Result := TOBDHDSessionNegotiator.Create; end; +begin + Result := TOBDHDSessionNegotiator.Create; +end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionIveco.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionIveco.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -99,9 +144,17 @@ procedure TOBDOEMExtensionIveco.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionIveco.DtcCatalogFileName: string; -begin Result := 'dtc-iveco.json'; end; +begin + Result := 'dtc-iveco.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionIveco.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.JLR.pas b/src/Services/OBD.OEM.JLR.pas index d518bbc3..5775d217 100644 --- a/src/Services/OBD.OEM.JLR.pas +++ b/src/Services/OBD.OEM.JLR.pas @@ -23,13 +23,18 @@ interface TOBDOEMExtensionJLR = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -46,21 +51,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionJLR.ManufacturerKey: string; -begin Result := 'JLR'; end; +begin + Result := 'JLR'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionJLR.DisplayName: string; -begin Result := 'Jaguar Land Rover Limited (Tata)'; end; +begin + Result := 'Jaguar Land Rover Limited (Tata)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionJLR.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in jlr.json. Result := VINMatchesCatalog('jlr.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionJLR.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are jlr.json // + uds-standard.json. Hardcoded entries removed. @@ -71,22 +96,37 @@ procedure TOBDOEMExtensionJLR.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionJLR.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('jlr.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionJLR.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionJLR.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -94,9 +134,17 @@ procedure TOBDOEMExtensionJLR.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionJLR.DtcCatalogFileName: string; -begin Result := 'dtc-jlr.json'; end; +begin + Result := 'dtc-jlr.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionJLR.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.KeyAdaptation.BMW.pas b/src/Services/OBD.OEM.KeyAdaptation.BMW.pas new file mode 100644 index 00000000..ff1c1223 --- /dev/null +++ b/src/Services/OBD.OEM.KeyAdaptation.BMW.pas @@ -0,0 +1,315 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.KeyAdaptation.BMW.pas +// CONTENTS : BMW key adaptation framing (EWS/CAS/FEM-BDC) +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.KeyAdaptation.BMW; + +interface + +uses + System.SysUtils; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDBMWKey = class(Exception); + EBMWKeyChallengeNotAvailable = class(EOBDBMWKey); + + TBMWImmoGeneration = (bmwgEWS, bmwgCAS, bmwgFEMBDC); + + /// + /// EWS key slot — 16 bytes per spec. Slot 0..9. + /// + TBMWKeyDataE = record + SlotIndex: Byte; // 0..9 + KeyEnabled: Boolean; // bit set in status flags + KeyCutCode: TBytes; // 4 bytes — mechanical cut, OEM-defined + UsageCounter: UInt16; // counts ignition cycles using this key + Reserved: TBytes; // padding to 16 bytes + end; + + /// + /// CAS key slot — 16 bytes per spec. Slot 0..9. + /// + TBMWKeyDataCas = record + /// + /// Slot index. + /// + SlotIndex: Byte; + /// + /// Key enabled. + /// + KeyEnabled: Boolean; + KeyCutCode: TBytes; // 4 bytes + RemoteId: UInt32; // remote-control identifier + KMReadingThousands: UInt16; // odometer captured by this key + /// + /// Reserved. + /// + Reserved: TBytes; + end; + + /// + /// FEM-BDC key slot — 32 bytes (F/G-series). Slot 0..7. + /// + TBMWKeyDataFem = record + /// + /// Slot index. + /// + SlotIndex: Byte; + /// + /// Key enabled. + /// + KeyEnabled: Boolean; + PersonalSettingsBank: Byte; // 1..4 (driver profile binding) + KeyCutCode: TBytes; // 4 bytes + DigitalKeySerial: TBytes; // 7 bytes (CD UWB key id, 0..) or zero + /// + /// Usage counter. + /// + UsageCounter: UInt32; + /// + /// Last km reading. + /// + LastKMReading: UInt32; + Reserved: TBytes; // padding to 32 bytes + end; + + /// + /// Pluggable solver for the proprietary parts: + /// ISN derivation per ECU and the EWS/CAS challenge-response + /// encryption. Production code wires a dealer-portal client here. + /// + IBMWKeyChallengeSolver = interface + ['{F2DE8AB1-7DBA-4F1E-A5C0-0F9A2D0D3C50}'] + function ComputeISN(Generation: TBMWImmoGeneration; + const ECUSerial: TBytes; const VIN: string): TBytes; + /// + /// Solve challenge. + /// + function SolveChallenge(Generation: TBMWImmoGeneration; + const Challenge: TBytes): TBytes; + end; + +function EncodeKeyDataE(const Key: TBMWKeyDataE): TBytes; +function DecodeKeyDataE(const Bytes: TBytes): TBMWKeyDataE; +function EncodeKeyDataCas(const Key: TBMWKeyDataCas): TBytes; +function DecodeKeyDataCas(const Bytes: TBytes): TBMWKeyDataCas; +function EncodeKeyDataFem(const Key: TBMWKeyDataFem): TBytes; +function DecodeKeyDataFem(const Bytes: TBytes): TBMWKeyDataFem; + +/// +/// Validate the slot index for a given immobiliser generation. +/// +function ValidateSlotIndex(Gen: TBMWImmoGeneration; Slot: Byte): Boolean; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// CONSTANTS +//------------------------------------------------------------------------------ +const + EWS_SLOT_BYTES = 16; + CAS_SLOT_BYTES = 16; + FEM_SLOT_BYTES = 32; + +//------------------------------------------------------------------------------ +// VALIDATE SLOT INDEX +//------------------------------------------------------------------------------ +function ValidateSlotIndex(Gen: TBMWImmoGeneration; Slot: Byte): Boolean; +begin + case Gen of + bmwgEWS, bmwgCAS: Result := Slot <= 9; + bmwgFEMBDC: Result := Slot <= 7; + else + // Initialize result + Result := False; + end; +end; + +//------------------------------------------------------------------------------ +// WRITE BYTES PAD +//------------------------------------------------------------------------------ +procedure WriteBytesPad(var Out_: TBytes; Cursor: Integer; const Src: TBytes; + Width: Integer); +var + N: Integer; +begin + N := Length(Src); + if N > Width then + raise EOBDBMWKey.CreateFmt( + 'Source bytes (%d) exceed field width (%d)', [N, Width]); + if N > 0 then Move(Src[0], Out_[Cursor], N); + // remainder stays zero +end; + +//------------------------------------------------------------------------------ +// ENCODE KEY DATA E +//------------------------------------------------------------------------------ +function EncodeKeyDataE(const Key: TBMWKeyDataE): TBytes; +var + Status: Byte; +begin + if not ValidateSlotIndex(bmwgEWS, Key.SlotIndex) then + raise EOBDBMWKey.CreateFmt('EWS slot %d out of range', [Key.SlotIndex]); + if Length(Key.KeyCutCode) <> 4 then + raise EOBDBMWKey.Create('KeyCutCode must be 4 bytes'); + // Allocate Result + SetLength(Result, EWS_SLOT_BYTES); + Result[0] := Key.SlotIndex; + Status := 0; + if Key.KeyEnabled then Status := Status or $01; + Result[1] := Status; + Move(Key.KeyCutCode[0], Result[2], 4); + Result[6] := Byte(Key.UsageCounter shr 8); + Result[7] := Byte(Key.UsageCounter and $FF); + if Length(Key.Reserved) > 0 then + WriteBytesPad(Result, 8, Key.Reserved, EWS_SLOT_BYTES - 8); +end; + +//------------------------------------------------------------------------------ +// DECODE KEY DATA E +//------------------------------------------------------------------------------ +function DecodeKeyDataE(const Bytes: TBytes): TBMWKeyDataE; +begin + if Length(Bytes) <> EWS_SLOT_BYTES then + raise EOBDBMWKey.CreateFmt( + 'EWS slot must be %d bytes (got %d)', [EWS_SLOT_BYTES, Length(Bytes)]); + Result := Default(TBMWKeyDataE); + Result.SlotIndex := Bytes[0]; + Result.KeyEnabled := (Bytes[1] and $01) <> 0; + // Allocate Result.KeyCutCode + SetLength(Result.KeyCutCode, 4); + Move(Bytes[2], Result.KeyCutCode[0], 4); + Result.UsageCounter := (UInt16(Bytes[6]) shl 8) or Bytes[7]; + // Allocate Result.Reserved + SetLength(Result.Reserved, EWS_SLOT_BYTES - 8); + Move(Bytes[8], Result.Reserved[0], EWS_SLOT_BYTES - 8); +end; + +//------------------------------------------------------------------------------ +// ENCODE KEY DATA CAS +//------------------------------------------------------------------------------ +function EncodeKeyDataCas(const Key: TBMWKeyDataCas): TBytes; +var + Status: Byte; +begin + if not ValidateSlotIndex(bmwgCAS, Key.SlotIndex) then + raise EOBDBMWKey.CreateFmt('CAS slot %d out of range', [Key.SlotIndex]); + if Length(Key.KeyCutCode) <> 4 then + raise EOBDBMWKey.Create('KeyCutCode must be 4 bytes'); + // Allocate Result + SetLength(Result, CAS_SLOT_BYTES); + Result[0] := Key.SlotIndex; + Status := 0; + if Key.KeyEnabled then Status := Status or $01; + Result[1] := Status; + Move(Key.KeyCutCode[0], Result[2], 4); + Result[6] := Byte(Key.RemoteId shr 24); + Result[7] := Byte(Key.RemoteId shr 16); + Result[8] := Byte(Key.RemoteId shr 8); + Result[9] := Byte(Key.RemoteId and $FF); + Result[10] := Byte(Key.KMReadingThousands shr 8); + Result[11] := Byte(Key.KMReadingThousands and $FF); + if Length(Key.Reserved) > 0 then + WriteBytesPad(Result, 12, Key.Reserved, CAS_SLOT_BYTES - 12); +end; + +//------------------------------------------------------------------------------ +// DECODE KEY DATA CAS +//------------------------------------------------------------------------------ +function DecodeKeyDataCas(const Bytes: TBytes): TBMWKeyDataCas; +begin + if Length(Bytes) <> CAS_SLOT_BYTES then + raise EOBDBMWKey.CreateFmt( + 'CAS slot must be %d bytes (got %d)', [CAS_SLOT_BYTES, Length(Bytes)]); + Result := Default(TBMWKeyDataCas); + Result.SlotIndex := Bytes[0]; + Result.KeyEnabled := (Bytes[1] and $01) <> 0; + // Allocate Result.KeyCutCode + SetLength(Result.KeyCutCode, 4); + Move(Bytes[2], Result.KeyCutCode[0], 4); + Result.RemoteId := (UInt32(Bytes[6]) shl 24) or (UInt32(Bytes[7]) shl 16) + or (UInt32(Bytes[8]) shl 8) or UInt32(Bytes[9]); + Result.KMReadingThousands := (UInt16(Bytes[10]) shl 8) or Bytes[11]; + // Allocate Result.Reserved + SetLength(Result.Reserved, CAS_SLOT_BYTES - 12); + Move(Bytes[12], Result.Reserved[0], CAS_SLOT_BYTES - 12); +end; + +//------------------------------------------------------------------------------ +// ENCODE KEY DATA FEM +//------------------------------------------------------------------------------ +function EncodeKeyDataFem(const Key: TBMWKeyDataFem): TBytes; +var + Status: Byte; +begin + if not ValidateSlotIndex(bmwgFEMBDC, Key.SlotIndex) then + raise EOBDBMWKey.CreateFmt('FEM-BDC slot %d out of range', [Key.SlotIndex]); + if (Key.PersonalSettingsBank < 1) or (Key.PersonalSettingsBank > 4) then + raise EOBDBMWKey.CreateFmt( + 'PersonalSettingsBank must be 1..4 (got %d)', [Key.PersonalSettingsBank]); + if Length(Key.KeyCutCode) <> 4 then + raise EOBDBMWKey.Create('KeyCutCode must be 4 bytes'); + if Length(Key.DigitalKeySerial) <> 7 then + raise EOBDBMWKey.Create('DigitalKeySerial must be 7 bytes (zero if none)'); + // Allocate Result + SetLength(Result, FEM_SLOT_BYTES); + Result[0] := Key.SlotIndex; + Status := 0; + if Key.KeyEnabled then Status := Status or $01; + Result[1] := Status; + Result[2] := Key.PersonalSettingsBank; + Move(Key.KeyCutCode[0], Result[3], 4); + Move(Key.DigitalKeySerial[0], Result[7], 7); + Result[14] := Byte(Key.UsageCounter shr 24); + Result[15] := Byte(Key.UsageCounter shr 16); + Result[16] := Byte(Key.UsageCounter shr 8); + Result[17] := Byte(Key.UsageCounter and $FF); + Result[18] := Byte(Key.LastKMReading shr 24); + Result[19] := Byte(Key.LastKMReading shr 16); + Result[20] := Byte(Key.LastKMReading shr 8); + Result[21] := Byte(Key.LastKMReading and $FF); + if Length(Key.Reserved) > 0 then + WriteBytesPad(Result, 22, Key.Reserved, FEM_SLOT_BYTES - 22); +end; + +//------------------------------------------------------------------------------ +// DECODE KEY DATA FEM +//------------------------------------------------------------------------------ +function DecodeKeyDataFem(const Bytes: TBytes): TBMWKeyDataFem; +begin + if Length(Bytes) <> FEM_SLOT_BYTES then + raise EOBDBMWKey.CreateFmt( + 'FEM-BDC slot must be %d bytes (got %d)', [FEM_SLOT_BYTES, Length(Bytes)]); + Result := Default(TBMWKeyDataFem); + Result.SlotIndex := Bytes[0]; + Result.KeyEnabled := (Bytes[1] and $01) <> 0; + Result.PersonalSettingsBank := Bytes[2]; + // Allocate Result.KeyCutCode + SetLength(Result.KeyCutCode, 4); + Move(Bytes[3], Result.KeyCutCode[0], 4); + // Allocate Result.DigitalKeySerial + SetLength(Result.DigitalKeySerial, 7); + Move(Bytes[7], Result.DigitalKeySerial[0], 7); + Result.UsageCounter := (UInt32(Bytes[14]) shl 24) or (UInt32(Bytes[15]) shl 16) + or (UInt32(Bytes[16]) shl 8) or UInt32(Bytes[17]); + Result.LastKMReading := (UInt32(Bytes[18]) shl 24) or (UInt32(Bytes[19]) shl 16) + or (UInt32(Bytes[20]) shl 8) or UInt32(Bytes[21]); + // Allocate Result.Reserved + SetLength(Result.Reserved, FEM_SLOT_BYTES - 22); + Move(Bytes[22], Result.Reserved[0], FEM_SLOT_BYTES - 22); +end; + +end. diff --git a/src/Services/OBD.OEM.KeyAdaptation.Ford.pas b/src/Services/OBD.OEM.KeyAdaptation.Ford.pas new file mode 100644 index 00000000..8adf3495 --- /dev/null +++ b/src/Services/OBD.OEM.KeyAdaptation.Ford.pas @@ -0,0 +1,249 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.KeyAdaptation.Ford.pas +// CONTENTS : Ford PATS framing +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.KeyAdaptation.Ford; + +interface + +uses + System.SysUtils, System.Generics.Collections; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDFordPATS = class(Exception); + + TFordPATSOperation = (fpoInitialise, fpoAddKey, fpoStatus); + + TFordPATSRequest = record + VIN: string; // 17 ASCII chars + /// + /// Operation. + /// + Operation: TFordPATSOperation; + /// + /// Programmer present byte; some platforms require a + /// captured value from a dealer programmer to authorise destructive + /// operations. + /// + ProgrammerPresentByte: Byte; + end; + + TFordPATSStatus = record + /// + /// Key count. + /// + KeyCount: Byte; + /// + /// Lockout active. + /// + LockoutActive: Boolean; + SecondsRemaining: UInt16; // when locked out + /// + /// Pin code present. + /// + PinCodePresent: Boolean; + end; + + TFordPlatformAccess = (fpaOpen, fpaPinRequired, fpaGatewayLocked); + + TFordPlatformInfo = record + /// + /// Key. + /// + Key: string; + /// + /// Display name. + /// + DisplayName: string; + /// + /// Access. + /// + Access: TFordPlatformAccess; + /// + /// Notes. + /// + Notes: string; + end; + +function EncodeFordPATSRequest(const Req: TFordPATSRequest): TBytes; +function DecodeFordPATSRequest(const Bytes: TBytes): TFordPATSRequest; +function EncodeFordPATSStatus(const Status: TFordPATSStatus): TBytes; +function DecodeFordPATSStatus(const Bytes: TBytes): TFordPATSStatus; + +/// +/// Per-platform applicability lookup (chassis code keys). +/// +function FindFordPlatform(const ChassisKey: string): TFordPlatformInfo; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +uses + System.Classes, System.JSON, + OBD.Catalog.Path; + +var + GFordPlatforms: TDictionary = nil; + +//------------------------------------------------------------------------------ +// FORD ACCESS FROM STRING +//------------------------------------------------------------------------------ +function FordAccessFromString(const S: string): TFordPlatformAccess; +begin + if SameText(S, 'open') then Exit(fpaOpen); + if SameText(S, 'pin_required') then Exit(fpaPinRequired); + Result := fpaGatewayLocked; +end; + +//------------------------------------------------------------------------------ +// LOAD FORD CATALOG +//------------------------------------------------------------------------------ +procedure LoadFordCatalog; +var + Path, Raw: string; + Stream: TStringStream; + Doc: TJSONValue; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + Info: TFordPlatformInfo; +begin + // Resolve catalog path + Path := ResolveCatalogPath('key-platforms-ford.json'); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + Info.Key := LowerCase(Obj.GetValue('chassis_key', '')); + if Info.Key = '' then Continue; + Info.DisplayName := Obj.GetValue('display_name', ''); + Info.Access := FordAccessFromString(Obj.GetValue('access', '')); + Info.Notes := Obj.GetValue('notes', ''); + GFordPlatforms.AddOrSetValue(Info.Key, Info); + end; + finally + // Free the document + Doc.Free; + end; +end; + +//------------------------------------------------------------------------------ +// ENCODE FORD PATSREQUEST +//------------------------------------------------------------------------------ +function EncodeFordPATSRequest(const Req: TFordPATSRequest): TBytes; +var + I: Integer; +begin + if Length(Req.VIN) <> 17 then + raise EOBDFordPATS.CreateFmt('VIN must be 17 chars (got %d)', [Length(Req.VIN)]); + // Layout: 17 VIN + 1 Op + 1 ProgrammerPresent + SetLength(Result, 19); + for I := 0 to 16 do Result[I] := Byte(Ord(Req.VIN[I + 1])); + Result[17] := Byte(Req.Operation); + Result[18] := Req.ProgrammerPresentByte; +end; + +//------------------------------------------------------------------------------ +// DECODE FORD PATSREQUEST +//------------------------------------------------------------------------------ +function DecodeFordPATSRequest(const Bytes: TBytes): TFordPATSRequest; +var + I: Integer; +begin + if Length(Bytes) <> 19 then + raise EOBDFordPATS.CreateFmt('Ford PATS request must be 19 bytes (got %d)', + [Length(Bytes)]); + // Allocate Result.VIN + SetLength(Result.VIN, 17); + for I := 0 to 16 do Result.VIN[I + 1] := Char(Bytes[I]); + Result.Operation := TFordPATSOperation(Bytes[17]); + Result.ProgrammerPresentByte := Bytes[18]; +end; + +//------------------------------------------------------------------------------ +// ENCODE FORD PATSSTATUS +//------------------------------------------------------------------------------ +function EncodeFordPATSStatus(const Status: TFordPATSStatus): TBytes; +begin + // Allocate Result + SetLength(Result, 5); + Result[0] := Status.KeyCount; + if Status.LockoutActive then Result[1] := $01 else Result[1] := $00; + Result[2] := Byte(Status.SecondsRemaining shr 8); + Result[3] := Byte(Status.SecondsRemaining and $FF); + if Status.PinCodePresent then Result[4] := $01 else Result[4] := $00; +end; + +//------------------------------------------------------------------------------ +// DECODE FORD PATSSTATUS +//------------------------------------------------------------------------------ +function DecodeFordPATSStatus(const Bytes: TBytes): TFordPATSStatus; +begin + if Length(Bytes) <> 5 then + raise EOBDFordPATS.CreateFmt('Ford PATS status must be 5 bytes (got %d)', + [Length(Bytes)]); + Result.KeyCount := Bytes[0]; + Result.LockoutActive := Bytes[1] <> 0; + Result.SecondsRemaining := (UInt16(Bytes[2]) shl 8) or Bytes[3]; + Result.PinCodePresent := Bytes[4] <> 0; +end; + +//------------------------------------------------------------------------------ +// FIND FORD PLATFORM +//------------------------------------------------------------------------------ +function FindFordPlatform(const ChassisKey: string): TFordPlatformInfo; +var + Lookup: string; +begin + Lookup := LowerCase(ChassisKey); + if (GFordPlatforms <> nil) and GFordPlatforms.TryGetValue(Lookup, Result) then Exit; + Result.Key := Lookup; + Result.DisplayName := ChassisKey; + Result.Access := fpaGatewayLocked; + Result.Notes := 'Unknown platform; assume gateway-locked.'; +end; + +initialization + GFordPlatforms := TDictionary.Create; + LoadFordCatalog; + +finalization + GFordPlatforms.Free; + +end. diff --git a/src/Services/OBD.OEM.KeyAdaptation.HMG.pas b/src/Services/OBD.OEM.KeyAdaptation.HMG.pas new file mode 100644 index 00000000..5e1a5e55 --- /dev/null +++ b/src/Services/OBD.OEM.KeyAdaptation.HMG.pas @@ -0,0 +1,258 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.KeyAdaptation.HMG.pas +// CONTENTS : Hyundai/Kia/Genesis smart-key registration framing +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.KeyAdaptation.HMG; + +interface + +uses + System.SysUtils, System.Generics.Collections; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDHMGKey = class(Exception); + + THMGKeyMode = (hkmAddKey, hkmEraseAll, hkmReadCount); + + THMGKeyRegisterRequest = record + VIN: string; // 17 ASCII chars + /// + /// Mode. + /// + Mode: THMGKeyMode; + PIN: string; // 4..6 ASCII digits, dealer-supplied + KeyIndex: Byte; // 0..7; ignored for EraseAll/ReadCount + end; + + THMGKeyRegisterResponse = record + /// + /// Mode. + /// + Mode: THMGKeyMode; + /// + /// Success. + /// + Success: Boolean; + KeyCount: Byte; // populated for ReadCount or after AddKey + StatusCode: Byte; // OEM-defined + end; + + THMGPlatformAccess = (hpaOpenWithPIN, hpaGatewayLockedPostMY2020, + hpaCertificateRequired); + + THMGPlatformInfo = record + /// + /// Key. + /// + Key: string; + /// + /// Display name. + /// + DisplayName: string; + /// + /// Access. + /// + Access: THMGPlatformAccess; + /// + /// Notes. + /// + Notes: string; + end; + +function EncodeHMGKeyRegisterRequest(const Req: THMGKeyRegisterRequest): TBytes; +function DecodeHMGKeyRegisterRequest(const Bytes: TBytes): THMGKeyRegisterRequest; +function EncodeHMGKeyRegisterResponse(const Resp: THMGKeyRegisterResponse): TBytes; +function DecodeHMGKeyRegisterResponse(const Bytes: TBytes): THMGKeyRegisterResponse; + +/// +/// Per-platform applicability. Returns hpaCertificateRequired +/// for unknown platforms (fail-safe default). +/// +function FindHMGPlatform(const PlatformKey: string): THMGPlatformInfo; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +uses + System.Classes, System.JSON, + OBD.Catalog.Path; + +//------------------------------------------------------------------------------ +// ENCODE HMGKEY REGISTER REQUEST +//------------------------------------------------------------------------------ +function EncodeHMGKeyRegisterRequest(const Req: THMGKeyRegisterRequest): TBytes; +var + PINLen, I: Integer; +begin + if Length(Req.VIN) <> 17 then + raise EOBDHMGKey.CreateFmt('VIN must be 17 chars (got %d)', [Length(Req.VIN)]); + PINLen := Length(Req.PIN); + if not (PINLen in [4..6]) then + raise EOBDHMGKey.CreateFmt('PIN must be 4..6 chars (got %d)', [PINLen]); + if Req.KeyIndex > 7 then + raise EOBDHMGKey.CreateFmt('KeyIndex must be 0..7 (got %d)', [Req.KeyIndex]); + // Layout: 17 VIN + 1 Mode + 1 PIN-len + PIN + 1 KeyIndex + SetLength(Result, 17 + 1 + 1 + PINLen + 1); + for I := 0 to 16 do Result[I] := Byte(Ord(Req.VIN[I + 1])); + Result[17] := Byte(Req.Mode); + Result[18] := Byte(PINLen); + for I := 0 to PINLen - 1 do + Result[19 + I] := Byte(Ord(Req.PIN[I + 1])); + Result[19 + PINLen] := Req.KeyIndex; +end; + +//------------------------------------------------------------------------------ +// DECODE HMGKEY REGISTER REQUEST +//------------------------------------------------------------------------------ +function DecodeHMGKeyRegisterRequest(const Bytes: TBytes): THMGKeyRegisterRequest; +var + PINLen, I: Integer; +begin + if Length(Bytes) < 17 + 1 + 1 + 4 + 1 then + raise EOBDHMGKey.Create('HMG key register request too short'); + // Allocate Result.VIN + SetLength(Result.VIN, 17); + for I := 0 to 16 do Result.VIN[I + 1] := Char(Bytes[I]); + Result.Mode := THMGKeyMode(Bytes[17]); + PINLen := Bytes[18]; + if not (PINLen in [4..6]) then + raise EOBDHMGKey.CreateFmt('PIN length out of range: %d', [PINLen]); + if 19 + PINLen + 1 > Length(Bytes) then + raise EOBDHMGKey.Create('HMG key request truncated at PIN'); + // Allocate Result.PIN + SetLength(Result.PIN, PINLen); + for I := 0 to PINLen - 1 do + Result.PIN[I + 1] := Char(Bytes[19 + I]); + Result.KeyIndex := Bytes[19 + PINLen]; +end; + +//------------------------------------------------------------------------------ +// ENCODE HMGKEY REGISTER RESPONSE +//------------------------------------------------------------------------------ +function EncodeHMGKeyRegisterResponse(const Resp: THMGKeyRegisterResponse): TBytes; +begin + // Allocate Result + SetLength(Result, 4); + Result[0] := Byte(Resp.Mode); + if Resp.Success then Result[1] := $01 else Result[1] := $00; + Result[2] := Resp.KeyCount; + Result[3] := Resp.StatusCode; +end; + +//------------------------------------------------------------------------------ +// DECODE HMGKEY REGISTER RESPONSE +//------------------------------------------------------------------------------ +function DecodeHMGKeyRegisterResponse(const Bytes: TBytes): THMGKeyRegisterResponse; +begin + if Length(Bytes) <> 4 then + raise EOBDHMGKey.CreateFmt('HMG response must be 4 bytes (got %d)', [Length(Bytes)]); + Result.Mode := THMGKeyMode(Bytes[0]); + Result.Success := Bytes[1] <> 0; + Result.KeyCount := Bytes[2]; + Result.StatusCode := Bytes[3]; +end; + +var + GHMGPlatforms: TDictionary = nil; + +//------------------------------------------------------------------------------ +// HMGACCESS FROM STRING +//------------------------------------------------------------------------------ +function HMGAccessFromString(const S: string): THMGPlatformAccess; +begin + if SameText(S, 'open_with_pin') then Exit(hpaOpenWithPIN); + if SameText(S, 'gateway_locked_post_my2020') then Exit(hpaGatewayLockedPostMY2020); + Result := hpaCertificateRequired; +end; + +//------------------------------------------------------------------------------ +// LOAD HMGCATALOG +//------------------------------------------------------------------------------ +procedure LoadHMGCatalog; +var + Path, Raw: string; + Stream: TStringStream; + Doc: TJSONValue; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + Info: THMGPlatformInfo; +begin + // Resolve catalog path + Path := ResolveCatalogPath('key-platforms-hmg.json'); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + Info.Key := LowerCase(Obj.GetValue('platform_key', '')); + if Info.Key = '' then Continue; + Info.DisplayName := Obj.GetValue('display_name', ''); + Info.Access := HMGAccessFromString(Obj.GetValue('access', '')); + Info.Notes := Obj.GetValue('notes', ''); + GHMGPlatforms.AddOrSetValue(Info.Key, Info); + end; + finally + // Free the document + Doc.Free; + end; +end; + +//------------------------------------------------------------------------------ +// FIND HMGPLATFORM +//------------------------------------------------------------------------------ +function FindHMGPlatform(const PlatformKey: string): THMGPlatformInfo; +var + Lookup: string; +begin + Lookup := LowerCase(PlatformKey); + if (GHMGPlatforms <> nil) and GHMGPlatforms.TryGetValue(Lookup, Result) then Exit; + Result.Key := Lookup; + Result.DisplayName := PlatformKey; + Result.Access := hpaCertificateRequired; + Result.Notes := 'Unknown platform; assume gateway-locked.'; +end; + +initialization + GHMGPlatforms := TDictionary.Create; + LoadHMGCatalog; + +finalization + GHMGPlatforms.Free; + +end. diff --git a/src/Services/OBD.OEM.KeyAdaptation.Toyota.pas b/src/Services/OBD.OEM.KeyAdaptation.Toyota.pas new file mode 100644 index 00000000..28a8d0d8 --- /dev/null +++ b/src/Services/OBD.OEM.KeyAdaptation.Toyota.pas @@ -0,0 +1,277 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.KeyAdaptation.Toyota.pas +// CONTENTS : Toyota/Lexus smart-key learning framing +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.KeyAdaptation.Toyota; + +interface + +uses + System.SysUtils, System.Generics.Collections; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDToyotaKey = class(Exception); + + TToyotaKeyMode = (tkmAddKey, tkmEraseAll, tkmReadCount); + + TToyotaKeyRegisterRequest = record + /// + /// Vin. + /// + VIN: string; + /// + /// Mode. + /// + Mode: TToyotaKeyMode; + /// + /// True if a master (black-shell) key is in the slot — + /// most pre-2015 platforms require this; smart-key-only cars + /// from 2015+ replace the master-key requirement with a PIN. + /// + MasterKeyPresent: Boolean; + PIN: string; // empty when MasterKeyPresent = True + end; + + TToyotaKeyRegisterResponse = record + /// + /// Mode. + /// + Mode: TToyotaKeyMode; + /// + /// Success. + /// + Success: Boolean; + /// + /// Key count. + /// + KeyCount: Byte; + AddedKeyId: TBytes; // 4-byte transponder id of the new key + end; + + TToyotaPlatformAccess = (tpaMasterKey, tpaPin, tpaCertificateRequired); + + TToyotaPlatformInfo = record + /// + /// Key. + /// + Key: string; + /// + /// Display name. + /// + DisplayName: string; + /// + /// Access. + /// + Access: TToyotaPlatformAccess; + /// + /// Notes. + /// + Notes: string; + end; + +function EncodeToyotaKeyRegisterRequest(const Req: TToyotaKeyRegisterRequest): TBytes; +function DecodeToyotaKeyRegisterRequest(const Bytes: TBytes): TToyotaKeyRegisterRequest; +function EncodeToyotaKeyRegisterResponse(const Resp: TToyotaKeyRegisterResponse): TBytes; +function DecodeToyotaKeyRegisterResponse(const Bytes: TBytes): TToyotaKeyRegisterResponse; + +function FindToyotaPlatform(const ChassisKey: string): TToyotaPlatformInfo; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +uses + System.Classes, System.JSON, + OBD.Catalog.Path; + +var + GToyotaPlatforms: TDictionary = nil; + +//------------------------------------------------------------------------------ +// TOYOTA ACCESS FROM STRING +//------------------------------------------------------------------------------ +function ToyotaAccessFromString(const S: string): TToyotaPlatformAccess; +begin + if SameText(S, 'master_key') then Exit(tpaMasterKey); + if SameText(S, 'pin') then Exit(tpaPin); + Result := tpaCertificateRequired; +end; + +//------------------------------------------------------------------------------ +// LOAD TOYOTA CATALOG +//------------------------------------------------------------------------------ +procedure LoadToyotaCatalog; +var + Path, Raw: string; + Stream: TStringStream; + Doc: TJSONValue; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + Info: TToyotaPlatformInfo; +begin + // Resolve catalog path + Path := ResolveCatalogPath('key-platforms-toyota.json'); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + Info.Key := LowerCase(Obj.GetValue('chassis_key', '')); + if Info.Key = '' then Continue; + Info.DisplayName := Obj.GetValue('display_name', ''); + Info.Access := ToyotaAccessFromString(Obj.GetValue('access', '')); + Info.Notes := Obj.GetValue('notes', ''); + GToyotaPlatforms.AddOrSetValue(Info.Key, Info); + end; + finally + // Free the document + Doc.Free; + end; +end; + +//------------------------------------------------------------------------------ +// ENCODE TOYOTA KEY REGISTER REQUEST +//------------------------------------------------------------------------------ +function EncodeToyotaKeyRegisterRequest(const Req: TToyotaKeyRegisterRequest): TBytes; +var + Cursor, PINLen, I: Integer; +begin + if Length(Req.VIN) <> 17 then + raise EOBDToyotaKey.CreateFmt('VIN must be 17 chars (got %d)', [Length(Req.VIN)]); + PINLen := Length(Req.PIN); + if (not Req.MasterKeyPresent) and (PINLen = 0) then + raise EOBDToyotaKey.Create( + 'PIN required when no master key is in the slot'); + if PINLen > 16 then + raise EOBDToyotaKey.CreateFmt('PIN exceeds 16 chars (got %d)', [PINLen]); + // Layout: 17 VIN + 1 Mode + 1 MasterKeyPresent + 1 PIN-len + PIN + SetLength(Result, 17 + 1 + 1 + 1 + PINLen); + for I := 0 to 16 do Result[I] := Byte(Ord(Req.VIN[I + 1])); + Cursor := 17; + Result[Cursor] := Byte(Req.Mode); Inc(Cursor); + if Req.MasterKeyPresent then Result[Cursor] := $01 else Result[Cursor] := $00; + Inc(Cursor); + Result[Cursor] := Byte(PINLen); Inc(Cursor); + for I := 0 to PINLen - 1 do + Result[Cursor + I] := Byte(Ord(Req.PIN[I + 1])); +end; + +//------------------------------------------------------------------------------ +// DECODE TOYOTA KEY REGISTER REQUEST +//------------------------------------------------------------------------------ +function DecodeToyotaKeyRegisterRequest(const Bytes: TBytes): TToyotaKeyRegisterRequest; +var + Cursor, PINLen, I: Integer; +begin + if Length(Bytes) < 17 + 3 then + raise EOBDToyotaKey.Create('Toyota key register request too short'); + // Allocate Result.VIN + SetLength(Result.VIN, 17); + for I := 0 to 16 do Result.VIN[I + 1] := Char(Bytes[I]); + Cursor := 17; + Result.Mode := TToyotaKeyMode(Bytes[Cursor]); Inc(Cursor); + Result.MasterKeyPresent := Bytes[Cursor] <> 0; Inc(Cursor); + PINLen := Bytes[Cursor]; Inc(Cursor); + if Cursor + PINLen > Length(Bytes) then + raise EOBDToyotaKey.Create('Toyota key request truncated at PIN'); + if PINLen > 0 then + begin + // Allocate Result.PIN + SetLength(Result.PIN, PINLen); + for I := 0 to PINLen - 1 do + Result.PIN[I + 1] := Char(Bytes[Cursor + I]); + end; +end; + +//------------------------------------------------------------------------------ +// ENCODE TOYOTA KEY REGISTER RESPONSE +//------------------------------------------------------------------------------ +function EncodeToyotaKeyRegisterResponse(const Resp: TToyotaKeyRegisterResponse): TBytes; +var + Cursor: Integer; +begin + if Length(Resp.AddedKeyId) <> 4 then + raise EOBDToyotaKey.Create('AddedKeyId must be 4 bytes'); + // Allocate Result + SetLength(Result, 3 + 4); + Result[0] := Byte(Resp.Mode); + if Resp.Success then Result[1] := $01 else Result[1] := $00; + Result[2] := Resp.KeyCount; + Cursor := 3; + Move(Resp.AddedKeyId[0], Result[Cursor], 4); +end; + +//------------------------------------------------------------------------------ +// DECODE TOYOTA KEY REGISTER RESPONSE +//------------------------------------------------------------------------------ +function DecodeToyotaKeyRegisterResponse(const Bytes: TBytes): TToyotaKeyRegisterResponse; +begin + if Length(Bytes) <> 7 then + raise EOBDToyotaKey.CreateFmt( + 'Toyota response must be 7 bytes (got %d)', [Length(Bytes)]); + Result.Mode := TToyotaKeyMode(Bytes[0]); + Result.Success := Bytes[1] <> 0; + Result.KeyCount := Bytes[2]; + // Allocate Result.AddedKeyId + SetLength(Result.AddedKeyId, 4); + Move(Bytes[3], Result.AddedKeyId[0], 4); +end; + +//------------------------------------------------------------------------------ +// FIND TOYOTA PLATFORM +//------------------------------------------------------------------------------ +function FindToyotaPlatform(const ChassisKey: string): TToyotaPlatformInfo; +var + Lookup: string; +begin + Lookup := LowerCase(ChassisKey); + if (GToyotaPlatforms <> nil) and GToyotaPlatforms.TryGetValue(Lookup, Result) then Exit; + Result.Key := Lookup; + Result.DisplayName := ChassisKey; + Result.Access := tpaCertificateRequired; + Result.Notes := 'Unknown platform; assume certificate-locked.'; +end; + +initialization + GToyotaPlatforms := TDictionary.Create; + LoadToyotaCatalog; + +finalization + GToyotaPlatforms.Free; + +end. diff --git a/src/Services/OBD.OEM.Lada.pas b/src/Services/OBD.OEM.Lada.pas index 5ad46505..420c81a0 100644 --- a/src/Services/OBD.OEM.Lada.pas +++ b/src/Services/OBD.OEM.Lada.pas @@ -24,13 +24,18 @@ interface TOBDOEMExtensionLada = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; function DtcCatalogFileName: string; override; @@ -46,21 +51,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionLada.ManufacturerKey: string; -begin Result := 'LADA'; end; +begin + Result := 'LADA'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionLada.DisplayName: string; -begin Result := 'AvtoVAZ (Lada)'; end; +begin + Result := 'AvtoVAZ (Lada)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionLada.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in lada.json. Result := VINMatchesCatalog('lada.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionLada.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are lada.json // + uds-standard.json. Hardcoded entries removed. @@ -71,16 +96,28 @@ procedure TOBDOEMExtensionLada.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionLada.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('lada.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionLada.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -88,9 +125,17 @@ procedure TOBDOEMExtensionLada.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionLada.DtcCatalogFileName: string; -begin Result := 'dtc-lada.json'; end; +begin + Result := 'dtc-lada.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionLada.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Lucid.pas b/src/Services/OBD.OEM.Lucid.pas index 85edb805..a0eeb6c7 100644 --- a/src/Services/OBD.OEM.Lucid.pas +++ b/src/Services/OBD.OEM.Lucid.pas @@ -24,13 +24,18 @@ interface TOBDOEMExtensionLucid = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; function DtcCatalogFileName: string; override; @@ -46,21 +51,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionLucid.ManufacturerKey: string; -begin Result := 'LUCID'; end; +begin + Result := 'LUCID'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionLucid.DisplayName: string; -begin Result := 'Lucid Group, Inc.'; end; +begin + Result := 'Lucid Group, Inc.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionLucid.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in lucid.json. Result := VINMatchesCatalog('lucid.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionLucid.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are lucid.json // + uds-standard.json. Hardcoded entries removed. @@ -71,16 +96,28 @@ procedure TOBDOEMExtensionLucid.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionLucid.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('lucid.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionLucid.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -88,9 +125,17 @@ procedure TOBDOEMExtensionLucid.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionLucid.DtcCatalogFileName: string; -begin Result := 'dtc-lucid.json'; end; +begin + Result := 'dtc-lucid.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionLucid.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.MAN.pas b/src/Services/OBD.OEM.MAN.pas index a11b7c26..70a9b7d7 100644 --- a/src/Services/OBD.OEM.MAN.pas +++ b/src/Services/OBD.OEM.MAN.pas @@ -25,13 +25,18 @@ interface TOBDOEMExtensionMAN = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -49,21 +54,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionMAN.ManufacturerKey: string; -begin Result := 'MAN'; end; +begin + Result := 'MAN'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMAN.DisplayName: string; -begin Result := 'MAN Truck & Bus SE (Traton Group)'; end; +begin + Result := 'MAN Truck & Bus SE (Traton Group)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionMAN.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in man.json. Result := VINMatchesCatalog('man.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMAN.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are man.json // + uds-standard.json. Hardcoded entries removed. @@ -74,25 +99,45 @@ procedure TOBDOEMExtensionMAN.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMAN.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('man.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionMAN.CreateSessionNegotiator: IOBDSessionNegotiator; -begin Result := TOBDHDSessionNegotiator.Create; end; +begin + Result := TOBDHDSessionNegotiator.Create; +end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMAN.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMAN.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -100,9 +145,17 @@ procedure TOBDOEMExtensionMAN.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMAN.DtcCatalogFileName: string; -begin Result := 'dtc-man.json'; end; +begin + Result := 'dtc-man.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionMAN.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.MINI.pas b/src/Services/OBD.OEM.MINI.pas index 02768726..7f1e2f27 100644 --- a/src/Services/OBD.OEM.MINI.pas +++ b/src/Services/OBD.OEM.MINI.pas @@ -21,10 +21,12 @@ interface System.SysUtils, OBD.OEM, OBD.OEM.Session, OBD.OEM.SeedKey, OBD.OEM.DTC; type - /// MINI uses BMW E-Sys / ISTA, so the same negotiator - /// applies (security access required for both extended + - /// programming sessions; 1500 ms heartbeat for older R-series - /// DMEs). + /// + /// MINI uses BMW E-Sys / ISTA, so the same negotiator + /// applies (security access required for both extended + + /// programming sessions; 1500 ms heartbeat for older R-series + /// DMEs). + /// TOBDMINISessionNegotiator = class(TOBDStandardSessionNegotiator) public function RequiresSecurityAccess(SessionType: TOBDSessionType): Boolean; override; @@ -35,13 +37,18 @@ TOBDMINISessionNegotiator = class(TOBDStandardSessionNegotiator) TOBDOEMExtensionMINI = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -59,6 +66,9 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// REQUIRES SECURITY ACCESS +//------------------------------------------------------------------------------ function TOBDMINISessionNegotiator.RequiresSecurityAccess( SessionType: TOBDSessionType): Boolean; begin @@ -66,27 +76,57 @@ function TOBDMINISessionNegotiator.RequiresSecurityAccess( sstOEMSpecific1, sstOEMSpecific2]; end; +//------------------------------------------------------------------------------ +// DEFAULT TESTER PRESENT MS +//------------------------------------------------------------------------------ function TOBDMINISessionNegotiator.DefaultTesterPresentMs: Cardinal; -begin Result := 1500; end; +begin + Result := 1500; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDMINISessionNegotiator.DisplayName: string; -begin Result := 'MINI (BMW E-Sys / ISTA)'; end; +begin + Result := 'MINI (BMW E-Sys / ISTA)'; +end; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionMINI.ManufacturerKey: string; -begin Result := 'MINI'; end; +begin + Result := 'MINI'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMINI.DisplayName: string; -begin Result := 'MINI (BMW Group sub-brand)'; end; +begin + Result := 'MINI (BMW Group sub-brand)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionMINI.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in mini.json. Result := VINMatchesCatalog('mini.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMINI.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are mini.json // + uds-standard.json. Hardcoded entries removed. @@ -97,19 +137,36 @@ procedure TOBDOEMExtensionMINI.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMINI.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('mini.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionMINI.CreateSessionNegotiator: IOBDSessionNegotiator; -begin Result := TOBDMINISessionNegotiator.Create; end; +begin + Result := TOBDMINISessionNegotiator.Create; +end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMINI.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); const @@ -125,6 +182,9 @@ procedure TOBDOEMExtensionMINI.SeedDefaultSeedKeyAlgorithms( 'MINI (BMW E-Sys lineage) XOR-mask placeholder', 'community-pr', False)); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMINI.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -132,9 +192,17 @@ procedure TOBDOEMExtensionMINI.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMINI.DtcCatalogFileName: string; -begin Result := 'dtc-mini.json'; end; +begin + Result := 'dtc-mini.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionMINI.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Mahindra.pas b/src/Services/OBD.OEM.Mahindra.pas index d8474e34..71864502 100644 --- a/src/Services/OBD.OEM.Mahindra.pas +++ b/src/Services/OBD.OEM.Mahindra.pas @@ -24,13 +24,18 @@ interface TOBDOEMExtensionMahindra = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -47,21 +52,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionMahindra.ManufacturerKey: string; -begin Result := 'MAHINDRA'; end; +begin + Result := 'MAHINDRA'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMahindra.DisplayName: string; -begin Result := 'Mahindra & Mahindra Ltd.'; end; +begin + Result := 'Mahindra & Mahindra Ltd.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionMahindra.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in mahindra.json. Result := VINMatchesCatalog('mahindra.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMahindra.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are mahindra.json // + uds-standard.json. Hardcoded entries removed. @@ -72,22 +97,37 @@ procedure TOBDOEMExtensionMahindra.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMahindra.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('mahindra.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMahindra.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMahindra.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -95,9 +135,17 @@ procedure TOBDOEMExtensionMahindra.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMahindra.DtcCatalogFileName: string; -begin Result := 'dtc-mahindra.json'; end; +begin + Result := 'dtc-mahindra.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionMahindra.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Marine.pas b/src/Services/OBD.OEM.Marine.pas index 7e2a2ce3..c02c38f1 100644 --- a/src/Services/OBD.OEM.Marine.pas +++ b/src/Services/OBD.OEM.Marine.pas @@ -23,13 +23,18 @@ TOBDOEMMarineBase = class abstract(TOBDOEMExtensionBase) protected function JsonFilename: string; virtual; abstract; procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; function DtcCatalogFileName: string; override; @@ -73,64 +78,153 @@ implementation uses OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMMarineBase.ApplicableToVIN(const VIN: string): Boolean; begin Result := VINMatchesCatalog(JsonFilename, VIN); end; +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMMarineBase.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin MergeCatalogJSON(JsonFilename, DIDs, Routines, ECUs); MergeCatalogJSON('uds-standard.json', DIDs, Routines, ECUs); end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMMarineBase.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON(JsonFilename, CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMMarineBase.DtcCatalogFileName: string; begin Result := 'dtc-' + JsonFilename; end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMMarineBase.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin MergeDtcCatalog('dtc-iso-15031.json', Cat); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMMercuryMarine.JsonFilename: string; begin Result := 'mercury-marine.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMMercuryMarine.ManufacturerKey: string; begin Result := 'MERCURY-MARINE'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMMercuryMarine.DisplayName: string; begin Result := 'Mercury Marine (Brunswick)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMVolvoPenta.JsonFilename: string; begin Result := 'volvo-penta.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMVolvoPenta.ManufacturerKey: string; begin Result := 'VOLVO-PENTA'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMVolvoPenta.DisplayName: string; begin Result := 'Volvo Penta'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMYanmarMarine.JsonFilename: string; begin Result := 'yanmar-marine.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMYanmarMarine.ManufacturerKey: string; begin Result := 'YANMAR-MARINE'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMYanmarMarine.DisplayName: string; begin Result := 'Yanmar Marine'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMMtu.JsonFilename: string; begin Result := 'mtu.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMMtu.ManufacturerKey: string; begin Result := 'MTU'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMMtu.DisplayName: string; begin Result := 'MTU (Rolls-Royce Power Systems)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMCumminsMarine.JsonFilename: string; begin Result := 'cummins-marine.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMCumminsMarine.ManufacturerKey: string; begin Result := 'CUMMINS-MARINE'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMCumminsMarine.DisplayName: string; begin Result := 'Cummins Marine'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMYamahaMarine.JsonFilename: string; begin Result := 'yamaha-marine.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMYamahaMarine.ManufacturerKey: string; begin Result := 'YAMAHA-MARINE'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMYamahaMarine.DisplayName: string; begin Result := 'Yamaha Marine'; end; initialization diff --git a/src/Services/OBD.OEM.Mazda.pas b/src/Services/OBD.OEM.Mazda.pas index ee722f10..e777b4bf 100644 --- a/src/Services/OBD.OEM.Mazda.pas +++ b/src/Services/OBD.OEM.Mazda.pas @@ -21,13 +21,18 @@ interface TOBDOEMExtensionMazda = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -44,21 +49,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionMazda.ManufacturerKey: string; -begin Result := 'MAZDA'; end; +begin + Result := 'MAZDA'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMazda.DisplayName: string; -begin Result := 'Mazda Motor Corporation'; end; +begin + Result := 'Mazda Motor Corporation'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionMazda.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in mazda.json. Result := VINMatchesCatalog('mazda.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMazda.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are mazda.json // + uds-standard.json. Hardcoded entries removed. @@ -69,16 +94,28 @@ procedure TOBDOEMExtensionMazda.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMazda.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('mazda.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMazda.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin @@ -88,6 +125,9 @@ procedure TOBDOEMExtensionMazda.SeedDefaultSeedKeyAlgorithms( Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMazda.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -95,9 +135,17 @@ procedure TOBDOEMExtensionMazda.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMazda.DtcCatalogFileName: string; -begin Result := 'dtc-mazda.json'; end; +begin + Result := 'dtc-mazda.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionMazda.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.McLaren.pas b/src/Services/OBD.OEM.McLaren.pas index 37fbe17f..e48bfe1b 100644 --- a/src/Services/OBD.OEM.McLaren.pas +++ b/src/Services/OBD.OEM.McLaren.pas @@ -25,13 +25,18 @@ interface TOBDOEMExtensionMcLaren = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; function DtcCatalogFileName: string; override; @@ -47,21 +52,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionMcLaren.ManufacturerKey: string; -begin Result := 'MCLAREN'; end; +begin + Result := 'MCLAREN'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMcLaren.DisplayName: string; -begin Result := 'McLaren Automotive Ltd.'; end; +begin + Result := 'McLaren Automotive Ltd.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionMcLaren.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in mclaren.json. Result := VINMatchesCatalog('mclaren.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMcLaren.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are mclaren.json // + uds-standard.json. Hardcoded entries removed. @@ -72,16 +97,28 @@ procedure TOBDOEMExtensionMcLaren.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMcLaren.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('mclaren.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMcLaren.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -89,9 +126,17 @@ procedure TOBDOEMExtensionMcLaren.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMcLaren.DtcCatalogFileName: string; -begin Result := 'dtc-mclaren.json'; end; +begin + Result := 'dtc-mclaren.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionMcLaren.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Mercedes.pas b/src/Services/OBD.OEM.Mercedes.pas index a2693de9..9a2f3f7a 100644 --- a/src/Services/OBD.OEM.Mercedes.pas +++ b/src/Services/OBD.OEM.Mercedes.pas @@ -39,13 +39,18 @@ TOBDMercedesSessionNegotiator = class(TOBDStandardSessionNegotiator) TOBDOEMExtensionMercedes = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -63,6 +68,9 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// BEGIN SESSION PLAN +//------------------------------------------------------------------------------ function TOBDMercedesSessionNegotiator.BeginSessionPlan( SessionType: TOBDSessionType; const ECUAddress: Word): TOBDSessionPlan; @@ -79,21 +87,33 @@ function TOBDMercedesSessionNegotiator.BeginSessionPlan( ]; end; +//------------------------------------------------------------------------------ +// DEFAULT TESTER PRESENT MS +//------------------------------------------------------------------------------ function TOBDMercedesSessionNegotiator.DefaultTesterPresentMs: Cardinal; begin Result := 1500; end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDMercedesSessionNegotiator.DisplayName: string; begin Result := 'Mercedes XENTRY'; end; +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionMercedes.CreateSessionNegotiator: IOBDSessionNegotiator; begin Result := TOBDMercedesSessionNegotiator.Create; end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMercedes.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin @@ -103,6 +123,9 @@ procedure TOBDOEMExtensionMercedes.SeedDefaultSeedKeyAlgorithms( Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMercedes.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -110,23 +133,49 @@ procedure TOBDOEMExtensionMercedes.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMercedes.DtcCatalogFileName: string; -begin Result := 'dtc-mercedes.json'; end; +begin + Result := 'dtc-mercedes.json'; +end; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionMercedes.ManufacturerKey: string; -begin Result := 'MB'; end; +begin + Result := 'MB'; +end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMercedes.DisplayName: string; -begin Result := 'Mercedes-Benz Group'; end; +begin + Result := 'Mercedes-Benz Group'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionMercedes.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in mercedes.json. Result := VINMatchesCatalog('mercedes.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMercedes.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are mercedes.json // + uds-standard.json. Hardcoded entries removed. @@ -137,16 +186,28 @@ procedure TOBDOEMExtensionMercedes.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMercedes.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('mercedes.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionMercedes.DecodeDID(const DID: Word; const Payload: TBytes): string; var diff --git a/src/Services/OBD.OEM.Mitsubishi.pas b/src/Services/OBD.OEM.Mitsubishi.pas index 9f4d99d7..1dd91c17 100644 --- a/src/Services/OBD.OEM.Mitsubishi.pas +++ b/src/Services/OBD.OEM.Mitsubishi.pas @@ -21,13 +21,18 @@ interface TOBDOEMExtensionMitsubishi = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -44,21 +49,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionMitsubishi.ManufacturerKey: string; -begin Result := 'MITSU'; end; +begin + Result := 'MITSU'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMitsubishi.DisplayName: string; -begin Result := 'Mitsubishi Motors Corp.'; end; +begin + Result := 'Mitsubishi Motors Corp.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionMitsubishi.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in mitsubishi.json. Result := VINMatchesCatalog('mitsubishi.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMitsubishi.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are mitsubishi.json // + uds-standard.json. Hardcoded entries removed. @@ -69,22 +94,37 @@ procedure TOBDOEMExtensionMitsubishi.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMitsubishi.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('mitsubishi.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMitsubishi.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionMitsubishi.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -92,9 +132,17 @@ procedure TOBDOEMExtensionMitsubishi.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionMitsubishi.DtcCatalogFileName: string; -begin Result := 'dtc-mitsubishi.json'; end; +begin + Result := 'dtc-mitsubishi.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionMitsubishi.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Motorcycles.pas b/src/Services/OBD.OEM.Motorcycles.pas index ed4e9dfd..7ab4f44b 100644 --- a/src/Services/OBD.OEM.Motorcycles.pas +++ b/src/Services/OBD.OEM.Motorcycles.pas @@ -24,24 +24,33 @@ interface System.SysUtils, OBD.OEM, OBD.OEM.Session, OBD.OEM.SeedKey, OBD.OEM.DTC; type - /// Shared shell — concrete classes override - /// ManufacturerKey, DisplayName, and - /// JsonFilename. The base implementations of - /// BuildCatalog / BuildExtendedCatalog / - /// SeedDefaultDtcCatalog chain through to JSON. + /// + /// Shared shell — concrete classes override + /// ManufacturerKey, DisplayName, and + /// JsonFilename. The base implementations of + /// BuildCatalog / BuildExtendedCatalog / + /// SeedDefaultDtcCatalog chain through to JSON. + /// TOBDOEMMotorcycleBase = class abstract(TOBDOEMExtensionBase) protected - /// Filename under catalogs/motorcycle/. Override - /// in subclasses. + /// + /// Filename under catalogs/motorcycle/. Override + /// in subclasses. + /// function JsonFilename: string; virtual; abstract; procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; function DtcCatalogFileName: string; override; @@ -134,26 +143,43 @@ function TOBDOEMMotorcycleBase.ApplicableToVIN(const VIN: string): Boolean; Result := VINMatchesCatalog(JsonFilename, VIN); end; +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMMotorcycleBase.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin MergeCatalogJSON(JsonFilename, DIDs, Routines, ECUs); MergeCatalogJSON('uds-standard.json', DIDs, Routines, ECUs); end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMMotorcycleBase.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON(JsonFilename, CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMMotorcycleBase.DtcCatalogFileName: string; begin // dtc-.json convention shared with the car catalogs. @@ -161,6 +187,9 @@ function TOBDOEMMotorcycleBase.DtcCatalogFileName: string; Result := 'dtc-' + JsonFilename; end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMMotorcycleBase.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin // Universal SAE J2012 codes first, OEM-specific overlays second. @@ -172,59 +201,210 @@ procedure TOBDOEMMotorcycleBase.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); // Concrete OEMs — each is a 4-line shell pointing at its JSON. //------------------------------------------------------------------------------ function TOBDOEMDucati.JsonFilename: string; begin Result := 'ducati.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMDucati.ManufacturerKey: string; begin Result := 'DUCATI'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMDucati.DisplayName: string; begin Result := 'Ducati Motor Holding'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMHarleyDavidson.JsonFilename: string; begin Result := 'harley-davidson.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMHarleyDavidson.ManufacturerKey: string; begin Result := 'HARLEY-DAVIDSON'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMHarleyDavidson.DisplayName: string; begin Result := 'Harley-Davidson'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMTriumph.JsonFilename: string; begin Result := 'triumph.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMTriumph.ManufacturerKey: string; begin Result := 'TRIUMPH'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMTriumph.DisplayName: string; begin Result := 'Triumph Motorcycles'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMBmwMotorrad.JsonFilename: string; begin Result := 'bmw-motorrad.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMBmwMotorrad.ManufacturerKey: string; begin Result := 'BMW-MOTORRAD'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMBmwMotorrad.DisplayName: string; begin Result := 'BMW Motorrad'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMKtm.JsonFilename: string; begin Result := 'ktm.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMKtm.ManufacturerKey: string; begin Result := 'KTM'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMKtm.DisplayName: string; begin Result := 'KTM AG'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMYamahaMoto.JsonFilename: string; begin Result := 'yamaha-moto.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMYamahaMoto.ManufacturerKey: string; begin Result := 'YAMAHA-MOTO'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMYamahaMoto.DisplayName: string; begin Result := 'Yamaha Motor (motorcycles)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMHondaMoto.JsonFilename: string; begin Result := 'honda-moto.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMHondaMoto.ManufacturerKey: string; begin Result := 'HONDA-MOTO'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMHondaMoto.DisplayName: string; begin Result := 'Honda Motor (motorcycles)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMKawasaki.JsonFilename: string; begin Result := 'kawasaki.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMKawasaki.ManufacturerKey: string; begin Result := 'KAWASAKI'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMKawasaki.DisplayName: string; begin Result := 'Kawasaki Heavy Industries'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMSuzukiMoto.JsonFilename: string; begin Result := 'suzuki-moto.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMSuzukiMoto.ManufacturerKey: string; begin Result := 'SUZUKI-MOTO'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMSuzukiMoto.DisplayName: string; begin Result := 'Suzuki Motor (motorcycles)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMIndianMotorcycle.JsonFilename: string; begin Result := 'indian-motorcycle.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMIndianMotorcycle.ManufacturerKey: string; begin Result := 'INDIAN-MOTORCYCLE'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMIndianMotorcycle.DisplayName: string; begin Result := 'Indian Motorcycle (Polaris)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMRoyalEnfield.JsonFilename: string; begin Result := 'royal-enfield.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMRoyalEnfield.ManufacturerKey: string; begin Result := 'ROYAL-ENFIELD'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMRoyalEnfield.DisplayName: string; begin Result := 'Royal Enfield (Eicher)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMMVAgusta.JsonFilename: string; begin Result := 'mv-agusta.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMMVAgusta.ManufacturerKey: string; begin Result := 'MV-AGUSTA'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMMVAgusta.DisplayName: string; begin Result := 'MV Agusta'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMAprilia.JsonFilename: string; begin Result := 'aprilia.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMAprilia.ManufacturerKey: string; begin Result := 'APRILIA'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMAprilia.DisplayName: string; begin Result := 'Aprilia (Piaggio)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMHusqvarnaMoto.JsonFilename: string; begin Result := 'husqvarna-moto.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMHusqvarnaMoto.ManufacturerKey: string; begin Result := 'HUSQVARNA-MOTO'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMHusqvarnaMoto.DisplayName: string; begin Result := 'Husqvarna Motorcycles (KTM AG)'; end; initialization diff --git a/src/Services/OBD.OEM.NIO.pas b/src/Services/OBD.OEM.NIO.pas index 71ae4b0f..fa2b9a93 100644 --- a/src/Services/OBD.OEM.NIO.pas +++ b/src/Services/OBD.OEM.NIO.pas @@ -25,13 +25,18 @@ interface TOBDOEMExtensionNIO = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -48,21 +53,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionNIO.ManufacturerKey: string; -begin Result := 'NIO'; end; +begin + Result := 'NIO'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionNIO.DisplayName: string; -begin Result := 'NIO Inc.'; end; +begin + Result := 'NIO Inc.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionNIO.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in nio.json. Result := VINMatchesCatalog('nio.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionNIO.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are nio.json // + uds-standard.json. Hardcoded entries removed. @@ -73,22 +98,37 @@ procedure TOBDOEMExtensionNIO.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionNIO.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('nio.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionNIO.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionNIO.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -96,9 +136,17 @@ procedure TOBDOEMExtensionNIO.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionNIO.DtcCatalogFileName: string; -begin Result := 'dtc-nio.json'; end; +begin + Result := 'dtc-nio.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionNIO.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Nissan.pas b/src/Services/OBD.OEM.Nissan.pas index b37a15f1..06f5bb4f 100644 --- a/src/Services/OBD.OEM.Nissan.pas +++ b/src/Services/OBD.OEM.Nissan.pas @@ -21,13 +21,18 @@ interface TOBDOEMExtensionNissan = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -44,21 +49,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionNissan.ManufacturerKey: string; -begin Result := 'NISSAN'; end; +begin + Result := 'NISSAN'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionNissan.DisplayName: string; -begin Result := 'Nissan Motor (incl. Infiniti, Datsun)'; end; +begin + Result := 'Nissan Motor (incl. Infiniti, Datsun)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionNissan.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in nissan.json. Result := VINMatchesCatalog('nissan.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionNissan.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are nissan.json // + uds-standard.json. Hardcoded entries removed. @@ -69,16 +94,28 @@ procedure TOBDOEMExtensionNissan.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionNissan.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('nissan.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionNissan.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin @@ -88,6 +125,9 @@ procedure TOBDOEMExtensionNissan.SeedDefaultSeedKeyAlgorithms( Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionNissan.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -95,9 +135,17 @@ procedure TOBDOEMExtensionNissan.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionNissan.DtcCatalogFileName: string; -begin Result := 'dtc-nissan.json'; end; +begin + Result := 'dtc-nissan.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionNissan.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.PACCAR.pas b/src/Services/OBD.OEM.PACCAR.pas index ff523000..f19257fe 100644 --- a/src/Services/OBD.OEM.PACCAR.pas +++ b/src/Services/OBD.OEM.PACCAR.pas @@ -25,13 +25,18 @@ interface TOBDOEMExtensionPACCAR = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -49,21 +54,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionPACCAR.ManufacturerKey: string; -begin Result := 'PACCAR'; end; +begin + Result := 'PACCAR'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionPACCAR.DisplayName: string; -begin Result := 'PACCAR Inc. (Peterbilt / Kenworth / DAF / Leyland)'; end; +begin + Result := 'PACCAR Inc. (Peterbilt / Kenworth / DAF / Leyland)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionPACCAR.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in paccar.json. Result := VINMatchesCatalog('paccar.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionPACCAR.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are paccar.json // + uds-standard.json. Hardcoded entries removed. @@ -74,25 +99,45 @@ procedure TOBDOEMExtensionPACCAR.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionPACCAR.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('paccar.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionPACCAR.CreateSessionNegotiator: IOBDSessionNegotiator; -begin Result := TOBDHDSessionNegotiator.Create; end; +begin + Result := TOBDHDSessionNegotiator.Create; +end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionPACCAR.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionPACCAR.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -100,9 +145,17 @@ procedure TOBDOEMExtensionPACCAR.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionPACCAR.DtcCatalogFileName: string; -begin Result := 'dtc-paccar.json'; end; +begin + Result := 'dtc-paccar.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionPACCAR.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Polestar.pas b/src/Services/OBD.OEM.Polestar.pas index 55a5d3b9..2b8c6810 100644 --- a/src/Services/OBD.OEM.Polestar.pas +++ b/src/Services/OBD.OEM.Polestar.pas @@ -23,13 +23,18 @@ interface TOBDOEMExtensionPolestar = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -46,21 +51,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionPolestar.ManufacturerKey: string; -begin Result := 'POLESTAR'; end; +begin + Result := 'POLESTAR'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionPolestar.DisplayName: string; -begin Result := 'Polestar Performance AB (Geely)'; end; +begin + Result := 'Polestar Performance AB (Geely)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionPolestar.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in polestar.json. Result := VINMatchesCatalog('polestar.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionPolestar.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are polestar.json // + uds-standard.json. Hardcoded entries removed. @@ -71,22 +96,37 @@ procedure TOBDOEMExtensionPolestar.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionPolestar.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('polestar.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionPolestar.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionPolestar.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -94,9 +134,17 @@ procedure TOBDOEMExtensionPolestar.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionPolestar.DtcCatalogFileName: string; -begin Result := 'dtc-polestar.json'; end; +begin + Result := 'dtc-polestar.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionPolestar.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Porsche.pas b/src/Services/OBD.OEM.Porsche.pas index 31ae211e..2010f135 100644 --- a/src/Services/OBD.OEM.Porsche.pas +++ b/src/Services/OBD.OEM.Porsche.pas @@ -23,13 +23,18 @@ interface TOBDOEMExtensionPorsche = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -46,21 +51,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionPorsche.ManufacturerKey: string; -begin Result := 'PORSCHE'; end; +begin + Result := 'PORSCHE'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionPorsche.DisplayName: string; -begin Result := 'Dr. Ing. h.c. F. Porsche AG'; end; +begin + Result := 'Dr. Ing. h.c. F. Porsche AG'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionPorsche.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in porsche.json. Result := VINMatchesCatalog('porsche.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionPorsche.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are porsche.json // + uds-standard.json. Hardcoded entries removed. @@ -71,22 +96,37 @@ procedure TOBDOEMExtensionPorsche.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionPorsche.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('porsche.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionPorsche.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionPorsche.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -94,9 +134,17 @@ procedure TOBDOEMExtensionPorsche.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionPorsche.DtcCatalogFileName: string; -begin Result := 'dtc-porsche.json'; end; +begin + Result := 'dtc-porsche.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionPorsche.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Powersports.pas b/src/Services/OBD.OEM.Powersports.pas index 2fa9cd28..1b510e62 100644 --- a/src/Services/OBD.OEM.Powersports.pas +++ b/src/Services/OBD.OEM.Powersports.pas @@ -19,13 +19,18 @@ TOBDOEMPowersportsBase = class abstract(TOBDOEMExtensionBase) protected function JsonFilename: string; virtual; abstract; procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; function DtcCatalogFileName: string; override; @@ -64,63 +69,147 @@ implementation uses OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMPowersportsBase.ApplicableToVIN(const VIN: string): Boolean; begin Result := VINMatchesCatalog(JsonFilename, VIN); end; +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMPowersportsBase.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin MergeCatalogJSON(JsonFilename, DIDs, Routines, ECUs); MergeCatalogJSON('uds-standard.json', DIDs, Routines, ECUs); end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMPowersportsBase.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON(JsonFilename, CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMPowersportsBase.DtcCatalogFileName: string; begin Result := 'dtc-' + JsonFilename; end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMPowersportsBase.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin MergeDtcCatalog('dtc-iso-15031.json', Cat); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMPolaris.JsonFilename: string; begin Result := 'polaris.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMPolaris.ManufacturerKey: string; begin Result := 'POLARIS'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMPolaris.DisplayName: string; begin Result := 'Polaris Industries'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMCanAmBrp.JsonFilename: string; begin Result := 'can-am-brp.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMCanAmBrp.ManufacturerKey: string; begin Result := 'CAN-AM-BRP'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMCanAmBrp.DisplayName: string; begin Result := 'Can-Am / BRP (Bombardier)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMArcticCat.JsonFilename: string; begin Result := 'arctic-cat.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMArcticCat.ManufacturerKey: string; begin Result := 'ARCTIC-CAT'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMArcticCat.DisplayName: string; begin Result := 'Arctic Cat (Textron)'; end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMYamahaWaverunner.JsonFilename: string; - begin Result := 'yamaha-waverunner.json'; end; + begin + Result := 'yamaha-waverunner.json'; + end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMYamahaWaverunner.ManufacturerKey: string; - begin Result := 'YAMAHA-WAVERUNNER'; end; + begin + Result := 'YAMAHA-WAVERUNNER'; + end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMYamahaWaverunner.DisplayName: string; - begin Result := 'Yamaha WaveRunner (Yamaha Marine subdivision)'; end; + begin + Result := 'Yamaha WaveRunner (Yamaha Marine subdivision)'; + end; +//------------------------------------------------------------------------------ +// JSON FILENAME +//------------------------------------------------------------------------------ function TOBDOEMKawasakiJet.JsonFilename: string; begin Result := 'kawasaki-jet.json'; end; + +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMKawasakiJet.ManufacturerKey: string; begin Result := 'KAWASAKI-JET'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMKawasakiJet.DisplayName: string; begin Result := 'Kawasaki Jet Ski'; end; initialization diff --git a/src/Services/OBD.OEM.Renault.pas b/src/Services/OBD.OEM.Renault.pas index edfd7bf4..f875d71d 100644 --- a/src/Services/OBD.OEM.Renault.pas +++ b/src/Services/OBD.OEM.Renault.pas @@ -23,13 +23,18 @@ interface TOBDOEMExtensionRenault = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -46,21 +51,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionRenault.ManufacturerKey: string; -begin Result := 'RNLT'; end; +begin + Result := 'RNLT'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionRenault.DisplayName: string; -begin Result := 'Renault Group (Renault / Dacia / Alpine)'; end; +begin + Result := 'Renault Group (Renault / Dacia / Alpine)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionRenault.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in renault.json. Result := VINMatchesCatalog('renault.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionRenault.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are renault.json // + uds-standard.json. Hardcoded entries removed. @@ -71,16 +96,28 @@ procedure TOBDOEMExtensionRenault.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionRenault.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('renault.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionRenault.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); const @@ -97,6 +134,9 @@ procedure TOBDOEMExtensionRenault.SeedDefaultSeedKeyAlgorithms( 'Renault CLIP community XOR-mask placeholder', 'community-pr', False)); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionRenault.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -104,9 +144,17 @@ procedure TOBDOEMExtensionRenault.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionRenault.DtcCatalogFileName: string; -begin Result := 'dtc-renault.json'; end; +begin + Result := 'dtc-renault.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionRenault.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Rivian.pas b/src/Services/OBD.OEM.Rivian.pas index 65125aa2..11d66f63 100644 --- a/src/Services/OBD.OEM.Rivian.pas +++ b/src/Services/OBD.OEM.Rivian.pas @@ -24,13 +24,18 @@ interface TOBDOEMExtensionRivian = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; function DtcCatalogFileName: string; override; @@ -46,21 +51,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionRivian.ManufacturerKey: string; -begin Result := 'RIVIAN'; end; +begin + Result := 'RIVIAN'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionRivian.DisplayName: string; -begin Result := 'Rivian Automotive, Inc.'; end; +begin + Result := 'Rivian Automotive, Inc.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionRivian.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in rivian.json. Result := VINMatchesCatalog('rivian.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionRivian.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are rivian.json // + uds-standard.json. Hardcoded entries removed. @@ -71,16 +96,28 @@ procedure TOBDOEMExtensionRivian.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionRivian.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('rivian.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionRivian.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -88,9 +125,17 @@ procedure TOBDOEMExtensionRivian.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionRivian.DtcCatalogFileName: string; -begin Result := 'dtc-rivian.json'; end; +begin + Result := 'dtc-rivian.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionRivian.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.RollsRoyce.pas b/src/Services/OBD.OEM.RollsRoyce.pas index aa1c6e8a..fcf66fd3 100644 --- a/src/Services/OBD.OEM.RollsRoyce.pas +++ b/src/Services/OBD.OEM.RollsRoyce.pas @@ -20,9 +20,11 @@ interface System.SysUtils, OBD.OEM, OBD.OEM.Session, OBD.OEM.SeedKey, OBD.OEM.DTC; type - /// Rolls-Royce inherits the BMW Group session-negotiation - /// lineage: security access required for extended + programming; - /// 1500 ms heartbeat for older DMEs. + /// + /// Rolls-Royce inherits the BMW Group session-negotiation + /// lineage: security access required for extended + programming; + /// 1500 ms heartbeat for older DMEs. + /// TOBDRRSessionNegotiator = class(TOBDStandardSessionNegotiator) public function RequiresSecurityAccess(SessionType: TOBDSessionType): Boolean; override; @@ -33,13 +35,18 @@ TOBDRRSessionNegotiator = class(TOBDStandardSessionNegotiator) TOBDOEMExtensionRollsRoyce = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -56,6 +63,9 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// REQUIRES SECURITY ACCESS +//------------------------------------------------------------------------------ function TOBDRRSessionNegotiator.RequiresSecurityAccess( SessionType: TOBDSessionType): Boolean; begin @@ -63,32 +73,65 @@ function TOBDRRSessionNegotiator.RequiresSecurityAccess( sstOEMSpecific1, sstOEMSpecific2]; end; +//------------------------------------------------------------------------------ +// DEFAULT TESTER PRESENT MS +//------------------------------------------------------------------------------ function TOBDRRSessionNegotiator.DefaultTesterPresentMs: Cardinal; -begin Result := 1500; end; +begin + Result := 1500; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDRRSessionNegotiator.DisplayName: string; -begin Result := 'Rolls-Royce (BMW E-Sys / ISTA)'; end; +begin + Result := 'Rolls-Royce (BMW E-Sys / ISTA)'; +end; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionRollsRoyce.ManufacturerKey: string; -begin Result := 'ROLLS_ROYCE'; end; +begin + Result := 'ROLLS_ROYCE'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionRollsRoyce.DisplayName: string; -begin Result := 'Rolls-Royce Motor Cars Ltd.'; end; +begin + Result := 'Rolls-Royce Motor Cars Ltd.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionRollsRoyce.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in rolls-royce.json. Result := VINMatchesCatalog('rolls-royce.json', VIN); end; + +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionRollsRoyce.CreateSessionNegotiator: IOBDSessionNegotiator; begin Result := TOBDRRSessionNegotiator.Create; end; +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionRollsRoyce.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are rolls-royce.json // + uds-standard.json. Hardcoded entries removed. @@ -99,16 +142,28 @@ procedure TOBDOEMExtensionRollsRoyce.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionRollsRoyce.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('rolls-royce.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionRollsRoyce.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -116,9 +171,17 @@ procedure TOBDOEMExtensionRollsRoyce.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionRollsRoyce.DtcCatalogFileName: string; -begin Result := 'dtc-rolls-royce.json'; end; +begin + Result := 'dtc-rolls-royce.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionRollsRoyce.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.RoutineControl.pas b/src/Services/OBD.OEM.RoutineControl.pas index 9b61edfe..414d7d47 100644 --- a/src/Services/OBD.OEM.RoutineControl.pas +++ b/src/Services/OBD.OEM.RoutineControl.pas @@ -32,9 +32,11 @@ EOBDRoutineError = class(Exception); rcRequestResults = $03 ); - /// One field in a routine's argument or response schema. - /// The wire format mirrors the DID decoder spec (v3.3) so callers - /// can reuse decoder kinds across both interfaces. + /// + /// One field in a routine's argument or response schema. + /// The wire format mirrors the DID decoder spec (v3.3) so callers + /// can reuse decoder kinds across both interfaces. + /// TOBDRoutineFieldKind = ( rfkUInt8, rfkUInt16BE, @@ -61,10 +63,12 @@ TOBDRoutineField = record BitNames: TDictionary; end; - /// Schema for one routine. InputFields is the - /// expected ordered argument list for a StartRoutine request; - /// OutputFields is what the response encodes after the SF - /// + RID echo. Either may be empty. + /// + /// Schema for one routine. InputFields is the + /// expected ordered argument list for a StartRoutine request; + /// OutputFields is what the response encodes after the SF + /// + RID echo. Either may be empty. + /// TOBDRoutineSchema = record Identifier: Word; Name: string; @@ -93,25 +97,35 @@ TOBDRoutineRequestBuilder = record procedure AddUInt32BE(const Value: Cardinal); procedure AddInt16BE(const Value: SmallInt); procedure AddInt32BE(const Value: Integer); - /// ASCII bytes; pads with 0x00 to FixedLength when - /// > 0, truncates with an exception when the input is too long. + /// + /// ASCII bytes; pads with 0x00 to FixedLength when + /// > 0, truncates with an exception when the input is too long. + /// procedure AddAscii(const S: string; const FixedLength: Integer = 0); procedure AddRawBytes(const Bytes: TBytes); procedure AddBcdDate(const Year, Month, Day: Byte); - /// Encode the year as 2 BCD nibbles (00..99) — a common - /// layout in OEM "set ECU date" routines. + /// + /// Encode the year as 2 BCD nibbles (00..99) — a common + /// layout in OEM "set ECU date" routines. + /// procedure AddBcdYear(const Year: Byte); - /// The accumulated payload (no SID / SF / RID prefix). + /// + /// The accumulated payload (no SID / SF / RID prefix). + /// function PayloadBytes: TBytes; - /// Wrap the payload as a complete UDS frame: - /// 31 SF HiRID LoRID PAYLOAD. SubFunction is one of - /// rcStart / rcStop / rcRequestResults. + /// + /// Wrap the payload as a complete UDS frame: + /// 31 SF HiRID LoRID PAYLOAD. SubFunction is one of + /// rcStart / rcStop / rcRequestResults. + /// function ToFrame(const SubFunction: TOBDRoutineSubFunction; const RID: Word): TBytes; - /// Reset the builder so a new request can be assembled. + /// + /// Reset the builder so a new request can be assembled. + /// procedure Clear; end; @@ -142,21 +156,29 @@ TOBDRoutineResponseReader = record // Top-level wire helpers //============================================================================== -/// Build a 31 01 RID [DATA] StartRoutine frame. +/// +/// Build a 31 01 RID [DATA] StartRoutine frame. +/// function BuildStartRoutine(const RID: Word; const InputData: TBytes = nil): TBytes; -/// Build a 31 02 RID StopRoutine frame. +/// +/// Build a 31 02 RID StopRoutine frame. +/// function BuildStopRoutine(const RID: Word): TBytes; -/// Build a 31 03 RID RequestRoutineResults frame. +/// +/// Build a 31 03 RID RequestRoutineResults frame. +/// function BuildRequestRoutineResults(const RID: Word): TBytes; -/// Parse a positive 71 SF RID [STATUS] response. Returns the -/// status payload (everything after the RID); throws when the -/// response doesn't match ExpectedSF / ExpectedRID or -/// when the SID isn't 0x71. Negative responses (7F 31 NRC) raise -/// EOBDRoutineError with the NRC in the message. +/// +/// Parse a positive 71 SF RID [STATUS] response. Returns the +/// status payload (everything after the RID); throws when the +/// response doesn't match ExpectedSF / ExpectedRID or +/// when the SID isn't 0x71. Negative responses (7F 31 NRC) raise +/// EOBDRoutineError with the NRC in the message. +/// function ParseRoutineResponse(const Response: TBytes; const ExpectedSF: TOBDRoutineSubFunction; const ExpectedRID: Word): TBytes; @@ -165,11 +187,13 @@ function ParseRoutineResponse(const Response: TBytes; // Schema-driven decoding //============================================================================== -/// Decode Bytes against Schema.OutputFields. -/// Returns one TOBDDecodedField per output field with a -/// human-readable Display string. Stops with no error if -/// the payload is shorter than the schema (the trailing fields are -/// simply absent — useful when an OEM truncates the optional tail). +/// +/// Decode Bytes against Schema.OutputFields. +/// Returns one TOBDDecodedField per output field with a +/// human-readable Display string. Stops with no error if +/// the payload is shorter than the schema (the trailing fields are +/// simply absent — useful when an OEM truncates the optional tail). +/// function DecodeRoutineOutput(const Schema: TOBDRoutineSchema; const Bytes: TBytes): TArray; @@ -181,6 +205,10 @@ implementation //============================================================================== // Helpers //============================================================================== + +//------------------------------------------------------------------------------ +// BYTES APPEND +//------------------------------------------------------------------------------ function BytesAppend(const A, B: TBytes): TBytes; begin SetLength(Result, Length(A) + Length(B)); @@ -188,6 +216,9 @@ function BytesAppend(const A, B: TBytes): TBytes; if Length(B) > 0 then Move(B[0], Result[Length(A)], Length(B)); end; +//------------------------------------------------------------------------------ +// FORMAT NUMERIC +//------------------------------------------------------------------------------ function FormatNumeric(const Value: Double; const Field: TOBDRoutineField): string; var Combined: Double; @@ -202,6 +233,10 @@ function FormatNumeric(const Value: Double; const Field: TOBDRoutineField): stri //============================================================================== // TOBDRoutineRequestBuilder //============================================================================== + +//------------------------------------------------------------------------------ +// APPEND BYTE +//------------------------------------------------------------------------------ procedure TOBDRoutineRequestBuilder.AppendByte(B: Byte); var N: Integer; @@ -211,20 +246,34 @@ procedure TOBDRoutineRequestBuilder.AppendByte(B: Byte); FBytes[N] := B; end; +//------------------------------------------------------------------------------ +// APPEND BYTES +//------------------------------------------------------------------------------ procedure TOBDRoutineRequestBuilder.AppendBytes(const Source: TBytes); begin FBytes := BytesAppend(FBytes, Source); end; +//------------------------------------------------------------------------------ +// ADD UINT8 +//------------------------------------------------------------------------------ procedure TOBDRoutineRequestBuilder.AddUInt8(const Value: Byte); -begin AppendByte(Value); end; +begin + AppendByte(Value); +end; +//------------------------------------------------------------------------------ +// ADD UINT16 BE +//------------------------------------------------------------------------------ procedure TOBDRoutineRequestBuilder.AddUInt16BE(const Value: Word); begin AppendByte(Byte(Value shr 8)); AppendByte(Byte(Value and $FF)); end; +//------------------------------------------------------------------------------ +// ADD UINT32 BE +//------------------------------------------------------------------------------ procedure TOBDRoutineRequestBuilder.AddUInt32BE(const Value: Cardinal); begin AppendByte(Byte(Value shr 24)); @@ -233,12 +282,25 @@ procedure TOBDRoutineRequestBuilder.AddUInt32BE(const Value: Cardinal); AppendByte(Byte(Value and $FF)); end; +//------------------------------------------------------------------------------ +// ADD INT16 BE +//------------------------------------------------------------------------------ procedure TOBDRoutineRequestBuilder.AddInt16BE(const Value: SmallInt); -begin AddUInt16BE(Word(Value)); end; +begin + AddUInt16BE(Word(Value)); +end; +//------------------------------------------------------------------------------ +// ADD INT32 BE +//------------------------------------------------------------------------------ procedure TOBDRoutineRequestBuilder.AddInt32BE(const Value: Integer); -begin AddUInt32BE(Cardinal(Value)); end; +begin + AddUInt32BE(Cardinal(Value)); +end; +//------------------------------------------------------------------------------ +// ADD ASCII +//------------------------------------------------------------------------------ procedure TOBDRoutineRequestBuilder.AddAscii(const S: string; const FixedLength: Integer); var @@ -260,9 +322,17 @@ procedure TOBDRoutineRequestBuilder.AddAscii(const S: string; end; end; +//------------------------------------------------------------------------------ +// ADD RAW BYTES +//------------------------------------------------------------------------------ procedure TOBDRoutineRequestBuilder.AddRawBytes(const Bytes: TBytes); -begin AppendBytes(Bytes); end; +begin + AppendBytes(Bytes); +end; +//------------------------------------------------------------------------------ +// BYTE TO BCD +//------------------------------------------------------------------------------ function ByteToBcd(const B: Byte): Byte; begin if B > 99 then @@ -270,6 +340,9 @@ function ByteToBcd(const B: Byte): Byte; Result := ((B div 10) shl 4) or (B mod 10); end; +//------------------------------------------------------------------------------ +// ADD BCD DATE +//------------------------------------------------------------------------------ procedure TOBDRoutineRequestBuilder.AddBcdDate( const Year, Month, Day: Byte); begin @@ -278,14 +351,25 @@ procedure TOBDRoutineRequestBuilder.AddBcdDate( AppendByte(ByteToBcd(Day)); end; +//------------------------------------------------------------------------------ +// ADD BCD YEAR +//------------------------------------------------------------------------------ procedure TOBDRoutineRequestBuilder.AddBcdYear(const Year: Byte); -begin AppendByte(ByteToBcd(Year)); end; +begin + AppendByte(ByteToBcd(Year)); +end; +//------------------------------------------------------------------------------ +// PAYLOAD BYTES +//------------------------------------------------------------------------------ function TOBDRoutineRequestBuilder.PayloadBytes: TBytes; begin Result := Copy(FBytes, 0, Length(FBytes)); end; +//------------------------------------------------------------------------------ +// TO FRAME +//------------------------------------------------------------------------------ function TOBDRoutineRequestBuilder.ToFrame( const SubFunction: TOBDRoutineSubFunction; const RID: Word): TBytes; var @@ -296,12 +380,21 @@ function TOBDRoutineRequestBuilder.ToFrame( Result := BytesAppend(Header, FBytes); end; +//------------------------------------------------------------------------------ +// CLEAR +//------------------------------------------------------------------------------ procedure TOBDRoutineRequestBuilder.Clear; -begin SetLength(FBytes, 0); end; +begin + SetLength(FBytes, 0); +end; //============================================================================== // TOBDRoutineResponseReader //============================================================================== + +//------------------------------------------------------------------------------ +// WRAP +//------------------------------------------------------------------------------ class function TOBDRoutineResponseReader.Wrap( const Bytes: TBytes): TOBDRoutineResponseReader; begin @@ -309,6 +402,9 @@ class function TOBDRoutineResponseReader.Wrap( Result.FCursor := 0; end; +//------------------------------------------------------------------------------ +// REQUIRE BYTES +//------------------------------------------------------------------------------ procedure TOBDRoutineResponseReader.RequireBytes(const Count: Integer); begin if FCursor + Count > Length(FBytes) then @@ -317,6 +413,9 @@ procedure TOBDRoutineResponseReader.RequireBytes(const Count: Integer); [FCursor, Count, Length(FBytes) - FCursor]); end; +//------------------------------------------------------------------------------ +// READ UINT8 +//------------------------------------------------------------------------------ function TOBDRoutineResponseReader.ReadUInt8: Byte; begin RequireBytes(1); @@ -324,6 +423,9 @@ function TOBDRoutineResponseReader.ReadUInt8: Byte; Inc(FCursor); end; +//------------------------------------------------------------------------------ +// READ UINT16 BE +//------------------------------------------------------------------------------ function TOBDRoutineResponseReader.ReadUInt16BE: Word; begin RequireBytes(2); @@ -331,6 +433,9 @@ function TOBDRoutineResponseReader.ReadUInt16BE: Word; Inc(FCursor, 2); end; +//------------------------------------------------------------------------------ +// READ UINT32 BE +//------------------------------------------------------------------------------ function TOBDRoutineResponseReader.ReadUInt32BE: Cardinal; begin RequireBytes(4); @@ -341,12 +446,25 @@ function TOBDRoutineResponseReader.ReadUInt32BE: Cardinal; Inc(FCursor, 4); end; +//------------------------------------------------------------------------------ +// READ INT16 BE +//------------------------------------------------------------------------------ function TOBDRoutineResponseReader.ReadInt16BE: SmallInt; -begin Result := SmallInt(ReadUInt16BE); end; +begin + Result := SmallInt(ReadUInt16BE); +end; +//------------------------------------------------------------------------------ +// READ INT32 BE +//------------------------------------------------------------------------------ function TOBDRoutineResponseReader.ReadInt32BE: Integer; -begin Result := Integer(ReadUInt32BE); end; +begin + Result := Integer(ReadUInt32BE); +end; +//------------------------------------------------------------------------------ +// READ ASCII +//------------------------------------------------------------------------------ function TOBDRoutineResponseReader.ReadAscii(const ByteCount: Integer): string; var Slice: TBytes; @@ -360,6 +478,9 @@ function TOBDRoutineResponseReader.ReadAscii(const ByteCount: Integer): string; Result := Result.TrimRight([#0]); end; +//------------------------------------------------------------------------------ +// READ HEX BYTES +//------------------------------------------------------------------------------ function TOBDRoutineResponseReader.ReadHexBytes(const ByteCount: Integer): TBytes; begin RequireBytes(ByteCount); @@ -368,6 +489,9 @@ function TOBDRoutineResponseReader.ReadHexBytes(const ByteCount: Integer): TByte Inc(FCursor, ByteCount); end; +//------------------------------------------------------------------------------ +// READ BCD DATE +//------------------------------------------------------------------------------ function TOBDRoutineResponseReader.ReadBcdDate: string; begin RequireBytes(3); @@ -376,15 +500,29 @@ function TOBDRoutineResponseReader.ReadBcdDate: string; Inc(FCursor, 3); end; +//------------------------------------------------------------------------------ +// HAS MORE +//------------------------------------------------------------------------------ function TOBDRoutineResponseReader.HasMore: Boolean; -begin Result := FCursor < Length(FBytes); end; +begin + Result := FCursor < Length(FBytes); +end; +//------------------------------------------------------------------------------ +// REMAINING +//------------------------------------------------------------------------------ function TOBDRoutineResponseReader.Remaining: Integer; -begin Result := Length(FBytes) - FCursor; end; +begin + Result := Length(FBytes) - FCursor; +end; //============================================================================== // Wire helpers //============================================================================== + +//------------------------------------------------------------------------------ +// BUILD START ROUTINE +//------------------------------------------------------------------------------ function BuildStartRoutine(const RID: Word; const InputData: TBytes): TBytes; var Header: TBytes; @@ -393,16 +531,25 @@ function BuildStartRoutine(const RID: Word; const InputData: TBytes): TBytes; Result := BytesAppend(Header, InputData); end; +//------------------------------------------------------------------------------ +// BUILD STOP ROUTINE +//------------------------------------------------------------------------------ function BuildStopRoutine(const RID: Word): TBytes; begin Result := TBytes.Create($31, $02, Byte(RID shr 8), Byte(RID and $FF)); end; +//------------------------------------------------------------------------------ +// BUILD REQUEST ROUTINE RESULTS +//------------------------------------------------------------------------------ function BuildRequestRoutineResults(const RID: Word): TBytes; begin Result := TBytes.Create($31, $03, Byte(RID shr 8), Byte(RID and $FF)); end; +//------------------------------------------------------------------------------ +// PARSE ROUTINE RESPONSE +//------------------------------------------------------------------------------ function ParseRoutineResponse(const Response: TBytes; const ExpectedSF: TOBDRoutineSubFunction; const ExpectedRID: Word): TBytes; @@ -446,8 +593,13 @@ function ParseRoutineResponse(const Response: TBytes; //============================================================================== // Schema-driven output decoding //============================================================================== + +//------------------------------------------------------------------------------ +// DECODE FIELD +//------------------------------------------------------------------------------ function DecodeField(const Field: TOBDRoutineField; - var Reader: TOBDRoutineResponseReader): TOBDDecodedField; + var + Reader: TOBDRoutineResponseReader): TOBDDecodedField; var StartCursor, BitCount, Bit: Integer; N: UInt64; @@ -533,6 +685,9 @@ function DecodeField(const Field: TOBDRoutineField; Result.Raw := Copy(Reader.FBytes, StartCursor, Reader.Cursor - StartCursor); end; +//------------------------------------------------------------------------------ +// DECODE ROUTINE OUTPUT +//------------------------------------------------------------------------------ function DecodeRoutineOutput(const Schema: TOBDRoutineSchema; const Bytes: TBytes): TArray; var diff --git a/src/Services/OBD.OEM.SCN.Mercedes.pas b/src/Services/OBD.OEM.SCN.Mercedes.pas new file mode 100644 index 00000000..3c904ce1 --- /dev/null +++ b/src/Services/OBD.OEM.SCN.Mercedes.pas @@ -0,0 +1,286 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.SCN.Mercedes.pas +// CONTENTS : Mercedes-Benz SCN coding flow framing +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.SCN.Mercedes; + +interface + +uses + System.SysUtils; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDMBSCN = class(Exception); + EOBDMBSCNNoSolver = class(EOBDMBSCN); + + TMBSCNVersionRequest = record + VIN: string; // 17 ASCII chars + ECUId: Word; // XENTRY ECU identifier + end; + + TMBSCNVersionResponse = record + CurrentSCN: TBytes; // typically 8..16 bytes opaque + HardwareNum: string; // ASCII part number + SoftwareNum: string; // ASCII part number + end; + + TMBSCNCodingRequest = record + /// + /// Vin. + /// + VIN: string; + /// + /// Ecu id. + /// + ECUId: Word; + Variant: TBytes; // OEM variant code per ECU + AccessoryList: TBytes; // OEM accessory bitmap / list + end; + + TMBSCNCodingResponse = record + /// + /// New scn. + /// + NewSCN: TBytes; + ServerSignature: TBytes; // server-side signature, opaque + end; + + IMBSCNSolver = interface + ['{0A8F4B2D-8E1C-4D3A-B7E9-1F4C9E8D7A11}'] + function FetchCurrentVersion(const Req: TMBSCNVersionRequest): + TMBSCNVersionResponse; + /// + /// Request coding. + /// + function RequestCoding(const Req: TMBSCNCodingRequest): + TMBSCNCodingResponse; + end; + + TMBSCNSolverNotAvailable = class(TInterfacedObject, IMBSCNSolver) + public + /// + /// Fetch current version. + /// + function FetchCurrentVersion(const Req: TMBSCNVersionRequest): + TMBSCNVersionResponse; + /// + /// Request coding. + /// + function RequestCoding(const Req: TMBSCNCodingRequest): + TMBSCNCodingResponse; + end; + +function EncodeMBSCNVersionRequest(const Req: TMBSCNVersionRequest): TBytes; +function DecodeMBSCNVersionRequest(const Bytes: TBytes): TMBSCNVersionRequest; +function EncodeMBSCNCodingRequest(const Req: TMBSCNCodingRequest): TBytes; +function DecodeMBSCNCodingRequest(const Bytes: TBytes): TMBSCNCodingRequest; +function EncodeMBSCNCodingResponse(const Resp: TMBSCNCodingResponse): TBytes; +function DecodeMBSCNCodingResponse(const Bytes: TBytes): TMBSCNCodingResponse; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// PUT WORD +//------------------------------------------------------------------------------ +function PutWord(var Out_: TBytes; Cursor: Integer; W: Word): Integer; +begin + Out_[Cursor] := Byte(W shr 8); + Out_[Cursor + 1] := Byte(W and $FF); + Result := Cursor + 2; +end; + +//------------------------------------------------------------------------------ +// GET WORD +//------------------------------------------------------------------------------ +function GetWord(const B: TBytes; Off: Integer): Word; +begin + Result := (UInt16(B[Off]) shl 8) or B[Off + 1]; +end; + +//------------------------------------------------------------------------------ +// ENCODE MBSCNVERSION REQUEST +//------------------------------------------------------------------------------ +function EncodeMBSCNVersionRequest(const Req: TMBSCNVersionRequest): TBytes; +var + I: Integer; +begin + if Length(Req.VIN) <> 17 then + raise EOBDMBSCN.CreateFmt('VIN must be 17 chars (got %d)', [Length(Req.VIN)]); + // Layout: 17 VIN bytes + 2 ECUId bytes (BE) + SetLength(Result, 17 + 2); + for I := 0 to 16 do Result[I] := Byte(Ord(Req.VIN[I + 1])); + PutWord(Result, 17, Req.ECUId); +end; + +//------------------------------------------------------------------------------ +// DECODE MBSCNVERSION REQUEST +//------------------------------------------------------------------------------ +function DecodeMBSCNVersionRequest(const Bytes: TBytes): TMBSCNVersionRequest; +var + I: Integer; +begin + if Length(Bytes) <> 19 then + raise EOBDMBSCN.CreateFmt( + 'SCN version request must be 19 bytes (got %d)', [Length(Bytes)]); + // Allocate Result.VIN + SetLength(Result.VIN, 17); + for I := 0 to 16 do Result.VIN[I + 1] := Char(Bytes[I]); + Result.ECUId := GetWord(Bytes, 17); +end; + +//------------------------------------------------------------------------------ +// ENCODE MBSCNCODING REQUEST +//------------------------------------------------------------------------------ +function EncodeMBSCNCodingRequest(const Req: TMBSCNCodingRequest): TBytes; +var + Cursor, I: Integer; +begin + if Length(Req.VIN) <> 17 then + raise EOBDMBSCN.Create('VIN must be 17 chars'); + if Length(Req.Variant) > $FFFF then + raise EOBDMBSCN.Create('Variant exceeds 65535 bytes'); + if Length(Req.AccessoryList) > $FFFF then + raise EOBDMBSCN.Create('AccessoryList exceeds 65535 bytes'); + SetLength(Result, + 17 // VIN + + 2 // ECUId + + 2 + Length(Req.Variant) // Variant + + 2 + Length(Req.AccessoryList) // AccessoryList + ); + for I := 0 to 16 do Result[I] := Byte(Ord(Req.VIN[I + 1])); + Cursor := 17; + Cursor := PutWord(Result, Cursor, Req.ECUId); + Cursor := PutWord(Result, Cursor, Word(Length(Req.Variant))); + if Length(Req.Variant) > 0 then + begin + Move(Req.Variant[0], Result[Cursor], Length(Req.Variant)); + Inc(Cursor, Length(Req.Variant)); + end; + PutWord(Result, Cursor, Word(Length(Req.AccessoryList))); + Inc(Cursor, 2); + if Length(Req.AccessoryList) > 0 then + Move(Req.AccessoryList[0], Result[Cursor], Length(Req.AccessoryList)); +end; + +//------------------------------------------------------------------------------ +// DECODE MBSCNCODING REQUEST +//------------------------------------------------------------------------------ +function DecodeMBSCNCodingRequest(const Bytes: TBytes): TMBSCNCodingRequest; +var + Cursor, Len, I: Integer; +begin + if Length(Bytes) < 17 + 2 + 2 + 2 then + raise EOBDMBSCN.Create('SCN coding request too short'); + // Allocate Result.VIN + SetLength(Result.VIN, 17); + for I := 0 to 16 do Result.VIN[I + 1] := Char(Bytes[I]); + Cursor := 17; + Result.ECUId := GetWord(Bytes, Cursor); Inc(Cursor, 2); + Len := GetWord(Bytes, Cursor); Inc(Cursor, 2); + if Cursor + Len > Length(Bytes) then + raise EOBDMBSCN.Create('Variant payload truncated'); + // Allocate Result.Variant + SetLength(Result.Variant, Len); + if Len > 0 then Move(Bytes[Cursor], Result.Variant[0], Len); + Inc(Cursor, Len); + if Cursor + 2 > Length(Bytes) then + raise EOBDMBSCN.Create('AccessoryList length missing'); + Len := GetWord(Bytes, Cursor); Inc(Cursor, 2); + if Cursor + Len > Length(Bytes) then + raise EOBDMBSCN.Create('AccessoryList truncated'); + // Allocate Result.AccessoryList + SetLength(Result.AccessoryList, Len); + if Len > 0 then Move(Bytes[Cursor], Result.AccessoryList[0], Len); +end; + +//------------------------------------------------------------------------------ +// ENCODE MBSCNCODING RESPONSE +//------------------------------------------------------------------------------ +function EncodeMBSCNCodingResponse(const Resp: TMBSCNCodingResponse): TBytes; +var + Cursor: Integer; +begin + if Length(Resp.NewSCN) > $FFFF then + raise EOBDMBSCN.Create('NewSCN exceeds 65535 bytes'); + if Length(Resp.ServerSignature) > $FFFF then + raise EOBDMBSCN.Create('ServerSignature exceeds 65535 bytes'); + SetLength(Result, + 2 + Length(Resp.NewSCN) + 2 + Length(Resp.ServerSignature)); + Cursor := PutWord(Result, 0, Word(Length(Resp.NewSCN))); + if Length(Resp.NewSCN) > 0 then + begin + Move(Resp.NewSCN[0], Result[Cursor], Length(Resp.NewSCN)); + Inc(Cursor, Length(Resp.NewSCN)); + end; + PutWord(Result, Cursor, Word(Length(Resp.ServerSignature))); + Inc(Cursor, 2); + if Length(Resp.ServerSignature) > 0 then + Move(Resp.ServerSignature[0], Result[Cursor], Length(Resp.ServerSignature)); +end; + +//------------------------------------------------------------------------------ +// DECODE MBSCNCODING RESPONSE +//------------------------------------------------------------------------------ +function DecodeMBSCNCodingResponse(const Bytes: TBytes): TMBSCNCodingResponse; +var + Cursor, Len: Integer; +begin + if Length(Bytes) < 4 then + raise EOBDMBSCN.Create('SCN coding response too short'); + Cursor := 0; + Len := GetWord(Bytes, Cursor); Inc(Cursor, 2); + if Cursor + Len > Length(Bytes) then + raise EOBDMBSCN.Create('NewSCN truncated'); + // Allocate Result.NewSCN + SetLength(Result.NewSCN, Len); + if Len > 0 then Move(Bytes[Cursor], Result.NewSCN[0], Len); + Inc(Cursor, Len); + if Cursor + 2 > Length(Bytes) then + raise EOBDMBSCN.Create('ServerSignature length missing'); + Len := GetWord(Bytes, Cursor); Inc(Cursor, 2); + if Cursor + Len > Length(Bytes) then + raise EOBDMBSCN.Create('ServerSignature truncated'); + // Allocate Result.ServerSignature + SetLength(Result.ServerSignature, Len); + if Len > 0 then Move(Bytes[Cursor], Result.ServerSignature[0], Len); +end; + +{ TMBSCNSolverNotAvailable } + +//------------------------------------------------------------------------------ +// FETCH CURRENT VERSION +//------------------------------------------------------------------------------ +function TMBSCNSolverNotAvailable.FetchCurrentVersion( + const Req: TMBSCNVersionRequest): TMBSCNVersionResponse; +begin + raise EOBDMBSCNNoSolver.Create( + 'Mercedes SCN version-fetch solver not available in this build. ' + + 'Plug in a dealer-portal client or a captured (req, resp) replay ' + + '(see docs/DATA_GAPS.md).'); +end; + +//------------------------------------------------------------------------------ +// REQUEST CODING +//------------------------------------------------------------------------------ +function TMBSCNSolverNotAvailable.RequestCoding( + const Req: TMBSCNCodingRequest): TMBSCNCodingResponse; +begin + raise EOBDMBSCNNoSolver.Create( + 'Mercedes SCN coding solver not available in this build (see ' + + 'docs/DATA_GAPS.md).'); +end; + +end. diff --git a/src/Services/OBD.OEM.Scania.pas b/src/Services/OBD.OEM.Scania.pas index afff0ecc..982dfb60 100644 --- a/src/Services/OBD.OEM.Scania.pas +++ b/src/Services/OBD.OEM.Scania.pas @@ -25,13 +25,18 @@ interface TOBDOEMExtensionScania = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -49,21 +54,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionScania.ManufacturerKey: string; -begin Result := 'SCANIA'; end; +begin + Result := 'SCANIA'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionScania.DisplayName: string; -begin Result := 'Scania AB (Traton Group)'; end; +begin + Result := 'Scania AB (Traton Group)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionScania.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in scania.json. Result := VINMatchesCatalog('scania.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionScania.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are scania.json // + uds-standard.json. Hardcoded entries removed. @@ -74,25 +99,45 @@ procedure TOBDOEMExtensionScania.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionScania.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('scania.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionScania.CreateSessionNegotiator: IOBDSessionNegotiator; -begin Result := TOBDHDSessionNegotiator.Create; end; +begin + Result := TOBDHDSessionNegotiator.Create; +end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionScania.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionScania.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -100,9 +145,17 @@ procedure TOBDOEMExtensionScania.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionScania.DtcCatalogFileName: string; -begin Result := 'dtc-scania.json'; end; +begin + Result := 'dtc-scania.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionScania.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.SeedKey.pas b/src/Services/OBD.OEM.SeedKey.pas index 1e8152e9..4acfc4fc 100644 --- a/src/Services/OBD.OEM.SeedKey.pas +++ b/src/Services/OBD.OEM.SeedKey.pas @@ -36,24 +36,34 @@ EOBDSeedKeyError = class(Exception); /// IOBDSeedKeyAlgorithm = interface ['{C7F9D2A4-1B5E-4F8C-9D3A-6E7B2F4D8C1A}'] - /// Compute the response key for Seed at Level. - /// Throws EOBDSeedKeyError on invalid seed length. + /// + /// Compute the response key for Seed at Level. + /// Throws EOBDSeedKeyError on invalid seed length. + /// function ComputeKey(const Seed: TBytes; const Level: Byte): TBytes; - /// Display label for logs / audit trails. + /// + /// Display label for logs / audit trails. + /// function Description: string; - /// Provenance: "public-domain", "iso-14229-1", - /// "oem-spec", "community-pr", … + /// + /// Provenance: "public-domain", "iso-14229-1", + /// "oem-spec", "community-pr", … + /// function Source: string; - /// True only when matched against an OEM spec or - /// reproducible capture fixture. Production callers filter - /// unverified algorithms out of flashing paths. + /// + /// True only when matched against an OEM spec or + /// reproducible capture fixture. Production callers filter + /// unverified algorithms out of flashing paths. + /// function Verified: Boolean; end; - /// Per-OEM, per-level registry. Each level can have multiple - /// candidate algorithms — useful when an OEM rolled the algorithm - /// across model years and the caller hasn't yet picked the right - /// variant. + /// + /// Per-OEM, per-level registry. Each level can have multiple + /// candidate algorithms — useful when an OEM rolled the algorithm + /// across model years and the caller hasn't yet picked the right + /// variant. + /// TOBDSeedKeyRegistry = class strict private FLock: TCriticalSection; @@ -64,38 +74,54 @@ TOBDSeedKeyRegistry = class constructor Create; destructor Destroy; override; - /// Register Algo for Level. Newer registrations - /// take precedence (LIFO) — production users register their NDA- - /// algorithm last so it shadows the public starter. + /// + /// Register Algo for Level. Newer registrations + /// take precedence (LIFO) — production users register their NDA- + /// algorithm last so it shadows the public starter. + /// procedure RegisterAlgorithm(const Level: Byte; const Algo: IOBDSeedKeyAlgorithm); procedure UnregisterAlgorithm(const Level: Byte; const Algo: IOBDSeedKeyAlgorithm); - /// The primary algorithm for the level (most-recently - /// registered). Returns nil when none is registered. + /// + /// The primary algorithm for the level (most-recently + /// registered). Returns nil when none is registered. + /// function Find(const Level: Byte): IOBDSeedKeyAlgorithm; - /// All algorithms registered for the level, newest first. + /// + /// All algorithms registered for the level, newest first. + /// function FindAll(const Level: Byte): TArray; - /// True if at least one algorithm is registered. + /// + /// True if at least one algorithm is registered. + /// function HasAlgorithm(const Level: Byte): Boolean; - /// Levels that have at least one algorithm registered. + /// + /// Levels that have at least one algorithm registered. + /// function Levels: TArray; - /// Drop every registration. Test-only helper; production - /// callers replace specific levels via Unregister. + /// + /// Drop every registration. Test-only helper; production + /// callers replace specific levels via Unregister. + /// procedure Clear; end; - /// Convenience base for algorithm implementations. + /// + /// Convenience base for algorithm implementations. + /// TOBDSeedKeyAlgorithmBase = class(TInterfacedObject, IOBDSeedKeyAlgorithm) strict private FDescription: string; FSource: string; FVerified: Boolean; protected - /// Validate the seed length expected by this algorithm. - /// Default: any non-empty length is acceptable. Subclasses - /// override for fixed-width seeds. + /// + /// Validate the seed length expected by this algorithm. + /// Default: any non-empty length is acceptable. Subclasses + /// override for fixed-width seeds. + /// procedure CheckSeed(const Seed: TBytes); virtual; public constructor Create(const ADescription, ASource: string; @@ -181,16 +207,22 @@ TOBDSeedKeyConstant = class(TOBDSeedKeyAlgorithmBase) // Helpers used by SecurityAccess flow code. //---------------------------------------------------------------------------- - /// Build the UDS request frame for a seed read at the given - /// level — returns SID 0x27 followed by the level byte. + /// + /// Build the UDS request frame for a seed read at the given + /// level — returns SID 0x27 followed by the level byte. + /// function RequestSeedFrame(const Level: Byte): TBytes; - /// Build the UDS request frame for a key send — SID 0x27 + - /// (Level + 1) + key bytes (ISO 14229 §10.5). + /// + /// Build the UDS request frame for a key send — SID 0x27 + + /// (Level + 1) + key bytes (ISO 14229 §10.5). + /// function SendKeyFrame(const Level: Byte; const Key: TBytes): TBytes; - /// Extract the seed bytes from a positive 0x67 0xLL response - /// payload. Throws EOBDSeedKeyError on a malformed reply. + /// + /// Extract the seed bytes from a positive 0x67 0xLL response + /// payload. Throws EOBDSeedKeyError on a malformed reply. + /// function ExtractSeed(const Response: TBytes; const Level: Byte): TBytes; implementation @@ -198,6 +230,10 @@ implementation //============================================================================== // TOBDSeedKeyRegistry //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDSeedKeyRegistry.Create; begin inherited Create; @@ -205,6 +241,9 @@ constructor TOBDSeedKeyRegistry.Create; FByLevel := TObjectDictionary>.Create([doOwnsValues]); end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDSeedKeyRegistry.Destroy; begin FByLevel.Free; @@ -212,6 +251,9 @@ destructor TOBDSeedKeyRegistry.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// LIST FOR +//------------------------------------------------------------------------------ function TOBDSeedKeyRegistry.ListFor(const Level: Byte; CreateIfMissing: Boolean): TList; begin @@ -223,6 +265,9 @@ function TOBDSeedKeyRegistry.ListFor(const Level: Byte; end; end; +//------------------------------------------------------------------------------ +// REGISTER ALGORITHM +//------------------------------------------------------------------------------ procedure TOBDSeedKeyRegistry.RegisterAlgorithm(const Level: Byte; const Algo: IOBDSeedKeyAlgorithm); var @@ -239,6 +284,9 @@ procedure TOBDSeedKeyRegistry.RegisterAlgorithm(const Level: Byte; end; end; +//------------------------------------------------------------------------------ +// UNREGISTER ALGORITHM +//------------------------------------------------------------------------------ procedure TOBDSeedKeyRegistry.UnregisterAlgorithm(const Level: Byte; const Algo: IOBDSeedKeyAlgorithm); var @@ -254,6 +302,9 @@ procedure TOBDSeedKeyRegistry.UnregisterAlgorithm(const Level: Byte; end; end; +//------------------------------------------------------------------------------ +// FIND +//------------------------------------------------------------------------------ function TOBDSeedKeyRegistry.Find( const Level: Byte): IOBDSeedKeyAlgorithm; var @@ -269,6 +320,9 @@ function TOBDSeedKeyRegistry.Find( end; end; +//------------------------------------------------------------------------------ +// FIND ALL +//------------------------------------------------------------------------------ function TOBDSeedKeyRegistry.FindAll( const Level: Byte): TArray; var @@ -284,6 +338,9 @@ function TOBDSeedKeyRegistry.FindAll( end; end; +//------------------------------------------------------------------------------ +// HAS ALGORITHM +//------------------------------------------------------------------------------ function TOBDSeedKeyRegistry.HasAlgorithm(const Level: Byte): Boolean; var L: TList; @@ -297,6 +354,9 @@ function TOBDSeedKeyRegistry.HasAlgorithm(const Level: Byte): Boolean; end; end; +//------------------------------------------------------------------------------ +// LEVELS +//------------------------------------------------------------------------------ function TOBDSeedKeyRegistry.Levels: TArray; var Pair: TPair>; @@ -317,6 +377,9 @@ function TOBDSeedKeyRegistry.Levels: TArray; end; end; +//------------------------------------------------------------------------------ +// CLEAR +//------------------------------------------------------------------------------ procedure TOBDSeedKeyRegistry.Clear; begin FLock.Enter; @@ -330,6 +393,10 @@ procedure TOBDSeedKeyRegistry.Clear; //============================================================================== // TOBDSeedKeyAlgorithmBase //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDSeedKeyAlgorithmBase.Create( const ADescription, ASource: string; const AVerified: Boolean); begin @@ -339,28 +406,55 @@ constructor TOBDSeedKeyAlgorithmBase.Create( FVerified := AVerified; end; +//------------------------------------------------------------------------------ +// CHECK SEED +//------------------------------------------------------------------------------ procedure TOBDSeedKeyAlgorithmBase.CheckSeed(const Seed: TBytes); begin if Length(Seed) = 0 then raise EOBDSeedKeyError.Create('Seed must not be empty'); end; +//------------------------------------------------------------------------------ +// DESCRIPTION +//------------------------------------------------------------------------------ function TOBDSeedKeyAlgorithmBase.Description: string; -begin Result := FDescription; end; +begin + Result := FDescription; +end; + +//------------------------------------------------------------------------------ +// SOURCE +//------------------------------------------------------------------------------ function TOBDSeedKeyAlgorithmBase.Source: string; -begin Result := FSource; end; +begin + Result := FSource; +end; + +//------------------------------------------------------------------------------ +// VERIFIED +//------------------------------------------------------------------------------ function TOBDSeedKeyAlgorithmBase.Verified: Boolean; -begin Result := FVerified; end; +begin + Result := FVerified; +end; //============================================================================== // TOBDSeedKeyKWP2000TwosComplement //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDSeedKeyKWP2000TwosComplement.Create; begin inherited Create('KWP2000 two''s-complement (NOT seed + 1)', 'iso-14229-1-example', False); end; +//------------------------------------------------------------------------------ +// COMPUTE KEY +//------------------------------------------------------------------------------ function TOBDSeedKeyKWP2000TwosComplement.ComputeKey(const Seed: TBytes; const Level: Byte): TBytes; var @@ -388,6 +482,10 @@ function TOBDSeedKeyKWP2000TwosComplement.ComputeKey(const Seed: TBytes; //============================================================================== // TOBDSeedKeyXorMask //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDSeedKeyXorMask.Create(const Mask: TBytes; const Description, Source: string; const Verified: Boolean); begin @@ -397,6 +495,9 @@ constructor TOBDSeedKeyXorMask.Create(const Mask: TBytes; FMask := Copy(Mask, 0, Length(Mask)); end; +//------------------------------------------------------------------------------ +// COMPUTE KEY +//------------------------------------------------------------------------------ function TOBDSeedKeyXorMask.ComputeKey(const Seed: TBytes; const Level: Byte): TBytes; var @@ -411,6 +512,10 @@ function TOBDSeedKeyXorMask.ComputeKey(const Seed: TBytes; //============================================================================== // TOBDSeedKeyByteRotate //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDSeedKeyByteRotate.Create(const Shift: Integer; const Rotate: Byte; const Mask: TBytes; const Description, Source: string; const Verified: Boolean); @@ -425,6 +530,9 @@ constructor TOBDSeedKeyByteRotate.Create(const Shift: Integer; FMask := Copy(Mask, 0, Length(Mask)); end; +//------------------------------------------------------------------------------ +// COMPUTE KEY +//------------------------------------------------------------------------------ function TOBDSeedKeyByteRotate.ComputeKey(const Seed: TBytes; const Level: Byte): TBytes; var @@ -447,6 +555,10 @@ function TOBDSeedKeyByteRotate.ComputeKey(const Seed: TBytes; //============================================================================== // TOBDSeedKeyConstant //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDSeedKeyConstant.Create(const Key: TBytes; const Description, Source: string; const Verified: Boolean); begin @@ -456,6 +568,9 @@ constructor TOBDSeedKeyConstant.Create(const Key: TBytes; FKey := Copy(Key, 0, Length(Key)); end; +//------------------------------------------------------------------------------ +// COMPUTE KEY +//------------------------------------------------------------------------------ function TOBDSeedKeyConstant.ComputeKey(const Seed: TBytes; const Level: Byte): TBytes; begin @@ -466,6 +581,10 @@ function TOBDSeedKeyConstant.ComputeKey(const Seed: TBytes; //============================================================================== // Frame helpers //============================================================================== + +//------------------------------------------------------------------------------ +// REQUEST SEED FRAME +//------------------------------------------------------------------------------ function RequestSeedFrame(const Level: Byte): TBytes; begin if (Level and 1) = 0 then @@ -474,6 +593,9 @@ function RequestSeedFrame(const Level: Byte): TBytes; Result := TBytes.Create($27, Level); end; +//------------------------------------------------------------------------------ +// SEND KEY FRAME +//------------------------------------------------------------------------------ function SendKeyFrame(const Level: Byte; const Key: TBytes): TBytes; var I: Integer; @@ -490,6 +612,9 @@ function SendKeyFrame(const Level: Byte; const Key: TBytes): TBytes; for I := 0 to High(Key) do Result[2 + I] := Key[I]; end; +//------------------------------------------------------------------------------ +// EXTRACT SEED +//------------------------------------------------------------------------------ function ExtractSeed(const Response: TBytes; const Level: Byte): TBytes; begin if Length(Response) < 2 then diff --git a/src/Services/OBD.OEM.ServiceFunction.pas b/src/Services/OBD.OEM.ServiceFunction.pas index ac12f0bb..fc11e186 100644 --- a/src/Services/OBD.OEM.ServiceFunction.pas +++ b/src/Services/OBD.OEM.ServiceFunction.pas @@ -26,8 +26,10 @@ interface OBD.OEM, OBD.OEM.RoutineControl; type - /// Canonical service-function kinds. The set of routines a - /// dealer-style tool typically exposes as one-tap actions. + /// + /// Canonical service-function kinds. The set of routines a + /// dealer-style tool typically exposes as one-tap actions. + /// TOBDServiceFunctionKind = ( sfUnknown, sfOilLifeReset, // 'reset oil life monitor' @@ -51,22 +53,34 @@ interface sfFuelTrimReset // 'reset adaptive fuel trim' ); - /// Resolved service function — one routine the OEM - /// extension exposes that matches a canonical kind. + /// + /// Resolved service function — one routine the OEM + /// extension exposes that matches a canonical kind. + /// TOBDServiceFunction = record Kind: TOBDServiceFunctionKind; - /// Routine identifier (the RID part of `31 01 RID`). + /// + /// Routine identifier (the RID part of `31 01 RID`). + /// RoutineId: Word; - /// Routine's catalog name (snake_case from the OEM JSON). + /// + /// Routine's catalog name (snake_case from the OEM JSON). + /// RoutineName: string; - /// Human-readable description from the catalog. + /// + /// Human-readable description from the catalog. + /// Description: string; - /// Target ECU (J1939 source address or UDS request CAN-ID). - /// 0 = global / not scoped to a specific ECU. + /// + /// Target ECU (J1939 source address or UDS request CAN-ID). + /// 0 = global / not scoped to a specific ECU. + /// EcuAddress: Word; end; - /// Registry of canonical kind → name-token mappings. + /// + /// Registry of canonical kind → name-token mappings. + /// TOBDServiceFunctionRegistry = class strict private class var FTokens: TObjectDictionary>; @@ -74,40 +88,55 @@ TOBDServiceFunctionRegistry = class class procedure RegisterTokens(const Kind: TOBDServiceFunctionKind; const Names: array of string); public - /// True if Name matches a known token for Kind - /// (case-insensitive substring match). + /// + /// True if Name matches a known token for Kind + /// (case-insensitive substring match). + /// class function NameMatchesKind(const Name: string; const Kind: TOBDServiceFunctionKind): Boolean; - /// Best-guess canonical kind for an arbitrary routine - /// name. Returns sfUnknown when no token matches. + /// + /// Best-guess canonical kind for an arbitrary routine + /// name. Returns sfUnknown when no token matches. + /// class function ClassifyName(const Name: string): TOBDServiceFunctionKind; end; -/// Find the first routine in the OEM extension's catalog -/// that maps to Kind. Returns True + populates -/// Func; returns False when no matching routine exists. +/// +/// Find the first routine in the OEM extension's catalog +/// that maps to Kind. Returns True + populates +/// Func; returns False when no matching routine exists. +/// function FindServiceFunction(const Ext: IOBDOEMExtension; const Kind: TOBDServiceFunctionKind; out Func: TOBDServiceFunction): Boolean; -/// Discover every supported service function on the OEM -/// extension. Useful for populating a "Service" menu in a tool -/// without hard-coding which OEMs support what. +/// +/// Discover every supported service function on the OEM +/// extension. Useful for populating a "Service" menu in a tool +/// without hard-coding which OEMs support what. +/// function ListServiceFunctions( const Ext: IOBDOEMExtension): TArray; -/// Build the start-routine UDS frame for a service function. -/// Caller-supplied InputData is appended past the RID; pass -/// nil for routines that take no arguments (most service -/// functions don't). +/// +/// Build the start-routine UDS frame for a service function. +/// Caller-supplied InputData is appended past the RID; pass +/// nil for routines that take no arguments (most service +/// functions don't). +/// function BuildServiceFunctionFrame(const Func: TOBDServiceFunction; const InputData: TBytes = nil): TBytes; -/// Display label for a service-function kind (used by UI). +/// +/// Display label for a service-function kind (used by UI). +/// function ServiceFunctionKindName(const Kind: TOBDServiceFunctionKind): string; implementation +//------------------------------------------------------------------------------ +// ENSURE INITIALIZED +//------------------------------------------------------------------------------ class procedure TOBDServiceFunctionRegistry.EnsureInitialized; begin if FTokens = nil then @@ -176,6 +205,9 @@ class procedure TOBDServiceFunctionRegistry.EnsureInitialized; end; end; +//------------------------------------------------------------------------------ +// REGISTER TOKENS +//------------------------------------------------------------------------------ class procedure TOBDServiceFunctionRegistry.RegisterTokens( const Kind: TOBDServiceFunctionKind; const Names: array of string); var @@ -187,6 +219,9 @@ class procedure TOBDServiceFunctionRegistry.RegisterTokens( FTokens.AddOrSetValue(Kind, L); end; +//------------------------------------------------------------------------------ +// NAME MATCHES KIND +//------------------------------------------------------------------------------ class function TOBDServiceFunctionRegistry.NameMatchesKind( const Name: string; const Kind: TOBDServiceFunctionKind): Boolean; var @@ -201,6 +236,9 @@ class function TOBDServiceFunctionRegistry.NameMatchesKind( if Pos(Token, Lower) > 0 then Exit(True); end; +//------------------------------------------------------------------------------ +// CLASSIFY NAME +//------------------------------------------------------------------------------ class function TOBDServiceFunctionRegistry.ClassifyName( const Name: string): TOBDServiceFunctionKind; var @@ -216,6 +254,9 @@ class function TOBDServiceFunctionRegistry.ClassifyName( Result := sfUnknown; end; +//------------------------------------------------------------------------------ +// SERVICE FUNCTION KIND NAME +//------------------------------------------------------------------------------ function ServiceFunctionKindName(const Kind: TOBDServiceFunctionKind): string; begin case Kind of @@ -243,6 +284,9 @@ function ServiceFunctionKindName(const Kind: TOBDServiceFunctionKind): string; end; end; +//------------------------------------------------------------------------------ +// FIND SERVICE FUNCTION +//------------------------------------------------------------------------------ function FindServiceFunction(const Ext: IOBDOEMExtension; const Kind: TOBDServiceFunctionKind; out Func: TOBDServiceFunction): Boolean; @@ -264,6 +308,9 @@ function FindServiceFunction(const Ext: IOBDOEMExtension; end; end; +//------------------------------------------------------------------------------ +// LIST SERVICE FUNCTIONS +//------------------------------------------------------------------------------ function ListServiceFunctions( const Ext: IOBDOEMExtension): TArray; var @@ -293,6 +340,9 @@ function ListServiceFunctions( end; end; +//------------------------------------------------------------------------------ +// BUILD SERVICE FUNCTION FRAME +//------------------------------------------------------------------------------ function BuildServiceFunctionFrame(const Func: TOBDServiceFunction; const InputData: TBytes): TBytes; begin diff --git a/src/Services/OBD.OEM.ServiceRoutines.pas b/src/Services/OBD.OEM.ServiceRoutines.pas new file mode 100644 index 00000000..5f564a81 --- /dev/null +++ b/src/Services/OBD.OEM.ServiceRoutines.pas @@ -0,0 +1,451 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.ServiceRoutines.pas +// CONTENTS : Workshop service routines registry +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.ServiceRoutines; + +interface + +uses + System.SysUtils, System.Generics.Collections; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDServiceRoutine = class(Exception); + + TOBDServiceRoutineCategory = ( + srcMaintenance, + srcSteeringBrakes, + srcPowertrain, + srcComfort, + srcBatteryElectrical, + srcTPMS, + srcEmissions + ); + + TOBDServiceRoutineSafety = ( + srsNone, + srsEngineMustBeRunning, + srsEngineMustBeOff, + srsVehicleMustBeStationary, + srsVehicleMayMove, + srsBatteryMin12V5, + srsRequiresWorkshopLogin + ); + + /// + /// One workshop routine description. + /// + TOBDServiceRoutine = record + /// + /// Key. + /// + Key: string; + /// + /// Display name. + /// + DisplayName: string; + /// + /// Category. + /// + Category: TOBDServiceRoutineCategory; + /// + /// Applicability. + /// + Applicability: string; + /// + /// Routine identifier. + /// + RoutineIdentifier: Word; + /// + /// Sub function. + /// + SubFunction: Byte; + /// + /// Option record. + /// + OptionRecord: TBytes; + /// + /// Required session type. + /// + RequiredSessionType: Byte; + /// + /// Safety. + /// + Safety: TOBDServiceRoutineSafety; + /// + /// Pre conditions. + /// + PreConditions: string; + /// + /// Post conditions. + /// + PostConditions: string; + /// + /// Citation. + /// + Citation: string; + end; + +/// +/// Build the UDS 0x31 RoutineControl request frame: +/// 31 SF RID-hi RID-lo [OptionRecord...] +/// +function BuildRoutineControlFrame(const Routine: TOBDServiceRoutine): TBytes; + +type + /// + /// Process-wide routine registry (read-only after init). + /// + TOBDServiceRoutineRegistry = class + private + class var FInstance: TOBDServiceRoutineRegistry; + FRoutines: TList; + FByKey: TDictionary; + /// + /// Load from catalog. + /// + procedure LoadFromCatalog; + public + /// + /// Create. + /// + constructor Create; + /// + /// Destroy. + /// + destructor Destroy; override; + /// + /// Instance. + /// + class function Instance: TOBDServiceRoutineRegistry; + /// + /// Free instance. + /// + class procedure FreeInstance; reintroduce; + + /// + /// Count. + /// + function Count: Integer; + /// + /// Get. + /// + function Get(Index: Integer): TOBDServiceRoutine; + /// + /// Find. + /// + function Find(const Key: string; out Routine: TOBDServiceRoutine): Boolean; + /// + /// Get by category. + /// + procedure GetByCategory(Category: TOBDServiceRoutineCategory; + out Routines: TArray); + /// + /// Get by oem. + /// + procedure GetByOEM(const OEMKey: string; + out Routines: TArray); + end; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +uses + System.Classes, System.JSON, + OBD.Catalog.Path; + +const + CatalogFileName = 'service-routines.json'; + +//------------------------------------------------------------------------------ +// BUILD ROUTINE CONTROL FRAME +//------------------------------------------------------------------------------ +function BuildRoutineControlFrame(const Routine: TOBDServiceRoutine): TBytes; +var + Out_: TBytes; + OptLen: Integer; +begin + if not (Routine.SubFunction in [$01, $02, $03]) then + raise EOBDServiceRoutine.CreateFmt( + 'Invalid sub-function 0x%.2x; expected 0x01/0x02/0x03', + [Routine.SubFunction]); + OptLen := Length(Routine.OptionRecord); + // Allocate Out_ + SetLength(Out_, 4 + OptLen); + Out_[0] := $31; + Out_[1] := Routine.SubFunction; + Out_[2] := Byte(Routine.RoutineIdentifier shr 8); + Out_[3] := Byte(Routine.RoutineIdentifier and $FF); + if OptLen > 0 then + Move(Routine.OptionRecord[0], Out_[4], OptLen); + Result := Out_; +end; + +//------------------------------------------------------------------------------ +// CATEGORY FROM STRING +//------------------------------------------------------------------------------ +function CategoryFromString(const S: string): TOBDServiceRoutineCategory; +begin + if SameText(S, 'maintenance') then Exit(srcMaintenance); + if SameText(S, 'steering_brakes') then Exit(srcSteeringBrakes); + if SameText(S, 'powertrain') then Exit(srcPowertrain); + if SameText(S, 'comfort') then Exit(srcComfort); + if SameText(S, 'battery_electrical') then Exit(srcBatteryElectrical); + if SameText(S, 'tpms') then Exit(srcTPMS); + if SameText(S, 'emissions') then Exit(srcEmissions); + Result := srcMaintenance; +end; + +//------------------------------------------------------------------------------ +// SAFETY FROM STRING +//------------------------------------------------------------------------------ +function SafetyFromString(const S: string): TOBDServiceRoutineSafety; +begin + if SameText(S, 'none') then Exit(srsNone); + if SameText(S, 'engine_must_be_running') then Exit(srsEngineMustBeRunning); + if SameText(S, 'engine_must_be_off') then Exit(srsEngineMustBeOff); + if SameText(S, 'vehicle_must_be_stationary') then Exit(srsVehicleMustBeStationary); + if SameText(S, 'vehicle_may_move') then Exit(srsVehicleMayMove); + if SameText(S, 'battery_min_12v5') then Exit(srsBatteryMin12V5); + if SameText(S, 'requires_workshop_login') then Exit(srsRequiresWorkshopLogin); + Result := srsNone; +end; + +//------------------------------------------------------------------------------ +// PARSE HEX INT +//------------------------------------------------------------------------------ +function ParseHexInt(const S: string; Default_: Integer): Integer; +var + T: string; +begin + T := S; + if T.StartsWith('0x', True) then T := '$' + T.Substring(2); + if not TryStrToInt(T, Result) then Result := Default_; +end; + +//------------------------------------------------------------------------------ +// HEX STRING TO BYTES +//------------------------------------------------------------------------------ +function HexStringToBytes(const S: string): TBytes; +var + Clean: string; + I, B: Integer; +begin + Clean := S.Replace(' ', '').Replace(':', ''); + if Clean.StartsWith('0x', True) then Clean := Clean.Substring(2); + if Odd(Length(Clean)) then Clean := '0' + Clean; + // Allocate Result + SetLength(Result, Length(Clean) div 2); + for I := 0 to High(Result) do + if TryStrToInt('$' + Clean.Substring(I * 2, 2), B) then + Result[I] := Byte(B); +end; + +{ TOBDServiceRoutineRegistry } + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDServiceRoutineRegistry.Create; +begin + // Call the inherited handler + inherited; + // Create FRoutines + FRoutines := TList.Create; + // Create FByKey + FByKey := TDictionary.Create; + LoadFromCatalog; +end; + +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ +destructor TOBDServiceRoutineRegistry.Destroy; +begin + // Free FByKey + FByKey.Free; + // Free FRoutines + FRoutines.Free; + // Call the inherited handler + inherited; +end; + +//------------------------------------------------------------------------------ +// INSTANCE +//------------------------------------------------------------------------------ +class function TOBDServiceRoutineRegistry.Instance: TOBDServiceRoutineRegistry; +begin + if FInstance = nil then + // Create FInstance + FInstance := TOBDServiceRoutineRegistry.Create; + Result := FInstance; +end; + +//------------------------------------------------------------------------------ +// FREE INSTANCE +//------------------------------------------------------------------------------ +class procedure TOBDServiceRoutineRegistry.FreeInstance; +begin + // Free FInstance + FreeAndNil(FInstance); +end; + +//------------------------------------------------------------------------------ +// COUNT +//------------------------------------------------------------------------------ +function TOBDServiceRoutineRegistry.Count: Integer; +begin + Result := FRoutines.Count; +end; + +//------------------------------------------------------------------------------ +// GET +//------------------------------------------------------------------------------ +function TOBDServiceRoutineRegistry.Get(Index: Integer): TOBDServiceRoutine; +begin + Result := FRoutines[Index]; +end; + +//------------------------------------------------------------------------------ +// FIND +//------------------------------------------------------------------------------ +function TOBDServiceRoutineRegistry.Find(const Key: string; + out Routine: TOBDServiceRoutine): Boolean; +var + Idx: Integer; +begin + Result := FByKey.TryGetValue(LowerCase(Key), Idx); + if Result then Routine := FRoutines[Idx]; +end; + +//------------------------------------------------------------------------------ +// GET BY CATEGORY +//------------------------------------------------------------------------------ +procedure TOBDServiceRoutineRegistry.GetByCategory( + Category: TOBDServiceRoutineCategory; + out Routines: TArray); +var + R: TOBDServiceRoutine; + Out_: TList; +begin + // Create Out_ + Out_ := TList.Create; + try + // Loop over FRoutines + for R in FRoutines do + if R.Category = Category then Out_.Add(R); + Routines := Out_.ToArray; + finally + // Free Out_ + Out_.Free; + end; +end; + +//------------------------------------------------------------------------------ +// GET BY OEM +//------------------------------------------------------------------------------ +procedure TOBDServiceRoutineRegistry.GetByOEM(const OEMKey: string; + out Routines: TArray); +var + Needle: string; + R: TOBDServiceRoutine; + Out_: TList; +begin + Needle := ',' + LowerCase(OEMKey) + ','; + // Create Out_ + Out_ := TList.Create; + try + // Loop over FRoutines + for R in FRoutines do + if Pos(Needle, ',' + LowerCase(R.Applicability) + ',') > 0 then + Out_.Add(R); + Routines := Out_.ToArray; + finally + // Free Out_ + Out_.Free; + end; +end; + +//------------------------------------------------------------------------------ +// LOAD FROM CATALOG +//------------------------------------------------------------------------------ +procedure TOBDServiceRoutineRegistry.LoadFromCatalog; +var + Path, Raw: string; + Doc: TJSONValue; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + R: TOBDServiceRoutine; + Stream: TStringStream; +begin + Path := ResolveCatalogPath(CatalogFileName); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + R := Default(TOBDServiceRoutine); + R.Key := LowerCase(Obj.GetValue('key', '')); + if R.Key = '' then Continue; + R.DisplayName := Obj.GetValue('display_name', ''); + R.Category := CategoryFromString(Obj.GetValue('category', '')); + R.Applicability := Obj.GetValue('applicability', ''); + R.RoutineIdentifier := Word(ParseHexInt(Obj.GetValue('routine_identifier', '0'), 0)); + R.SubFunction := Byte(ParseHexInt(Obj.GetValue('sub_function', '0x01'), $01)); + R.OptionRecord := HexStringToBytes(Obj.GetValue('option_record_hex', '')); + R.RequiredSessionType := Byte(ParseHexInt(Obj.GetValue('required_session_type', '0x03'), $03)); + R.Safety := SafetyFromString(Obj.GetValue('safety', 'none')); + R.PreConditions := Obj.GetValue('pre_conditions', ''); + R.PostConditions := Obj.GetValue('post_conditions', ''); + R.Citation := Obj.GetValue('citation', ''); + FByKey.AddOrSetValue(R.Key, FRoutines.Count); + FRoutines.Add(R); + end; + finally + // Free the document + Doc.Free; + end; +end; + +initialization + +finalization + TOBDServiceRoutineRegistry.FreeInstance; + +end. diff --git a/src/Services/OBD.OEM.Session.Runner.pas b/src/Services/OBD.OEM.Session.Runner.pas index c3f40106..55055636 100644 --- a/src/Services/OBD.OEM.Session.Runner.pas +++ b/src/Services/OBD.OEM.Session.Runner.pas @@ -26,24 +26,36 @@ interface type EOBDSessionRunnerError = class(Exception); - /// One row in the audit log produced by the runner. + /// + /// One row in the audit log produced by the runner. + /// TOBDSessionStepResult = record Step: TOBDSessionStep; - /// Raw response text received from the adapter. + /// + /// Raw response text received from the adapter. + /// Response: string; - /// True if the step matched its expected-response prefix - /// (or the prefix was empty and any reply was acceptable). + /// + /// True if the step matched its expected-response prefix + /// (or the prefix was empty and any reply was acceptable). + /// Success: Boolean; - /// Empty when Success is true. + /// + /// Empty when Success is true. + /// ErrorMessage: string; - /// Wall-clock duration of the step (ms). + /// + /// Wall-clock duration of the step (ms). + /// DurationMs: Cardinal; end; TOBDSessionRunResult = record Success: Boolean; Steps: TArray; - /// Captured if any step raised; nil otherwise. + /// + /// Captured if any step raised; nil otherwise. + /// Error: Exception; end; @@ -84,8 +96,10 @@ TOBDSessionRunner = class const Prefix: TBytes): Boolean; public constructor Create(AConnection: TOBDConnectionAsync); - /// Per-step default timeout when the step itself doesn't - /// specify one. Defaults to 5000 ms. + /// + /// Per-step default timeout when the step itself doesn't + /// specify one. Defaults to 5000 ms. + /// property DefaultTimeoutMs: Cardinal read FDefaultTimeoutMs write FDefaultTimeoutMs; /// /// Run Plan's steps in order. Stops on the first failed @@ -111,6 +125,9 @@ implementation const DEFAULT_STEP_TIMEOUT_MS = 5000; +//------------------------------------------------------------------------------ +// BYTES EQUAL PREFIX +//------------------------------------------------------------------------------ function BytesEqualPrefix(const Bytes, Prefix: TBytes): Boolean; var I: Integer; @@ -122,6 +139,9 @@ function BytesEqualPrefix(const Bytes, Prefix: TBytes): Boolean; Result := True; end; +//------------------------------------------------------------------------------ +// HEX CHARS TO BYTES +//------------------------------------------------------------------------------ function HexCharsToBytes(const HexChars: string): TBytes; var Buf: string; @@ -149,6 +169,10 @@ function HexCharsToBytes(const HexChars: string): TBytes; //============================================================================== // TOBDTesterPresentThread //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDTesterPresentThread.Create(AConnection: TOBDConnectionAsync; const ARequest: TBytes; AIntervalMs: Cardinal); begin @@ -161,12 +185,18 @@ constructor TOBDTesterPresentThread.Create(AConnection: TOBDConnectionAsync; FToken := NewCancellationToken; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDTesterPresentThread.Destroy; begin FStopEvent.Free; inherited; end; +//------------------------------------------------------------------------------ +// STOP GRACEFULLY +//------------------------------------------------------------------------------ procedure TOBDTesterPresentThread.StopGracefully; begin Terminate; @@ -177,14 +207,21 @@ procedure TOBDTesterPresentThread.StopGracefully; WaitFor; end; +//------------------------------------------------------------------------------ +// FORMAT HEX BYTES +//------------------------------------------------------------------------------ function TOBDTesterPresentThread.FormatHexBytes(const Bytes: TBytes): string; -var I: Integer; +var + I: Integer; begin Result := ''; for I := 0 to High(Bytes) do Result := Result + IntToHex(Bytes[I], 2); end; +//------------------------------------------------------------------------------ +// EXECUTE +//------------------------------------------------------------------------------ procedure TOBDTesterPresentThread.Execute; var HexCmd: string; @@ -210,6 +247,10 @@ procedure TOBDTesterPresentThread.Execute; //============================================================================== // TOBDSessionRunner //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDSessionRunner.Create(AConnection: TOBDConnectionAsync); begin inherited Create; @@ -220,14 +261,21 @@ constructor TOBDSessionRunner.Create(AConnection: TOBDConnectionAsync); FDefaultTimeoutMs := DEFAULT_STEP_TIMEOUT_MS; end; +//------------------------------------------------------------------------------ +// FORMAT HEX BYTES +//------------------------------------------------------------------------------ function TOBDSessionRunner.FormatHexBytes(const Bytes: TBytes): string; -var I: Integer; +var + I: Integer; begin Result := ''; for I := 0 to High(Bytes) do Result := Result + IntToHex(Bytes[I], 2); end; +//------------------------------------------------------------------------------ +// HEX RESPONSE STARTS WITH +//------------------------------------------------------------------------------ function TOBDSessionRunner.HexResponseStartsWith(const Response: string; const Prefix: TBytes): Boolean; begin @@ -235,6 +283,9 @@ function TOBDSessionRunner.HexResponseStartsWith(const Response: string; Result := BytesEqualPrefix(HexCharsToBytes(Response), Prefix); end; +//------------------------------------------------------------------------------ +// EXECUTE STEP +//------------------------------------------------------------------------------ function TOBDSessionRunner.ExecuteStep( const Step: TOBDSessionStep): TOBDSessionStepResult; var @@ -271,6 +322,9 @@ function TOBDSessionRunner.ExecuteStep( Result.DurationMs := Cardinal(Watch.ElapsedMilliseconds); end; +//------------------------------------------------------------------------------ +// EXECUTE +//------------------------------------------------------------------------------ function TOBDSessionRunner.Execute( const Plan: TOBDSessionPlan): TOBDSessionRunResult; var @@ -296,6 +350,9 @@ function TOBDSessionRunner.Execute( end; end; +//------------------------------------------------------------------------------ +// START TESTER PRESENT +//------------------------------------------------------------------------------ function TOBDSessionRunner.StartTesterPresent( const Plan: TOBDSessionPlan): TOBDTesterPresentThread; begin diff --git a/src/Services/OBD.OEM.Session.pas b/src/Services/OBD.OEM.Session.pas index 4553d93f..68fe7486 100644 --- a/src/Services/OBD.OEM.Session.pas +++ b/src/Services/OBD.OEM.Session.pas @@ -36,23 +36,35 @@ interface sstOEMSpecific2 // second vendor-specific slot ); - /// One step in a session plan: an adapter command (AT/ST) - /// or a raw UDS frame. + /// + /// One step in a session plan: an adapter command (AT/ST) + /// or a raw UDS frame. + /// TOBDSessionStepKind = (sskATCommand, sskUDSRequest); TOBDSessionStep = record Kind: TOBDSessionStepKind; - /// Adapter command text, no leading "AT" prefix - /// (e.g. "SH 7E0", "CRA 7E8"). Only used when Kind = sskATCommand. + /// + /// Adapter command text, no leading "AT" prefix + /// (e.g. "SH 7E0", "CRA 7E8"). Only used when Kind = sskATCommand. + /// AdapterCmd: string; - /// Raw UDS request bytes. Only used when Kind = sskUDSRequest. + /// + /// Raw UDS request bytes. Only used when Kind = sskUDSRequest. + /// UDS: TBytes; - /// Optional expected response prefix; an empty array - /// means "any positive response is acceptable". + /// + /// Optional expected response prefix; an empty array + /// means "any positive response is acceptable". + /// ExpectedResponse: TBytes; - /// Per-step timeout (ms); 0 = use the runner's default. + /// + /// Per-step timeout (ms); 0 = use the runner's default. + /// TimeoutMs: Cardinal; - /// Free-text label for logging / audit trails. + /// + /// Free-text label for logging / audit trails. + /// Description: string; end; @@ -63,12 +75,16 @@ TOBDSessionStep = record /// TOBDSessionPlan = record Steps: TArray; - /// Heartbeat interval after the plan completes; - /// 0 = no heartbeat (default session). + /// + /// Heartbeat interval after the plan completes; + /// 0 = no heartbeat (default session). + /// TesterPresentMs: Cardinal; - /// Bytes sent for tester-present (default ISO 14229: $3E $80 - /// — sub-function "suppressPosRespMsgIndicationBit" set so the ECU - /// doesn't ACK every keep-alive). + /// + /// Bytes sent for tester-present (default ISO 14229: $3E $80 + /// — sub-function "suppressPosRespMsgIndicationBit" set so the ECU + /// doesn't ACK every keep-alive). + /// TesterPresentRequest: TBytes; end; @@ -80,29 +96,39 @@ TOBDSessionPlan = record /// IOBDSessionNegotiator = interface ['{F4D5C8A1-3E7B-4D9F-8C2A-7B1E9F4D6C8A}'] - /// Plan to enter SessionType on ECUAddress - /// (0 = use the connection's currently-active header). + /// + /// Plan to enter SessionType on ECUAddress + /// (0 = use the connection's currently-active header). + /// function BeginSessionPlan(SessionType: TOBDSessionType; const ECUAddress: Word): TOBDSessionPlan; - /// Plan to leave the active session and return to default. - /// Always sends 10 01; OEMs that wrap that with extra teardown - /// (BMW, Mercedes) layer it on top. + /// + /// Plan to leave the active session and return to default. + /// Always sends 10 01; OEMs that wrap that with extra teardown + /// (BMW, Mercedes) layer it on top. + /// function EndSessionPlan(const ECUAddress: Word): TOBDSessionPlan; - /// True if this OEM expects a SecurityAccess (27 xx) - /// exchange immediately after entering the given session — flashing - /// programming sessions almost always do; extended diagnostic - /// sessions may or may not. + /// + /// True if this OEM expects a SecurityAccess (27 xx) + /// exchange immediately after entering the given session — flashing + /// programming sessions almost always do; extended diagnostic + /// sessions may or may not. + /// function RequiresSecurityAccess(SessionType: TOBDSessionType): Boolean; - /// Tester-present interval (ms) the OEM expects for - /// non-default sessions. ISO 14229 default is 2000 ms; some OEMs - /// (Mercedes XENTRY, BMW E-Sys) recommend 1500 ms for older ECUs. + /// + /// Tester-present interval (ms) the OEM expects for + /// non-default sessions. ISO 14229 default is 2000 ms; some OEMs + /// (Mercedes XENTRY, BMW E-Sys) recommend 1500 ms for older ECUs. + /// function DefaultTesterPresentMs: Cardinal; - /// Display label for logs — e.g. "ISO 14229 standard", - /// "VAG TP 2.0", "BMW E-Sys". + /// + /// Display label for logs — e.g. "ISO 14229 standard", + /// "VAG TP 2.0", "BMW E-Sys". + /// function DisplayName: string; end; @@ -114,8 +140,10 @@ TOBDSessionPlan = record /// TOBDStandardSessionNegotiator = class(TInterfacedObject, IOBDSessionNegotiator) protected - /// Helper for subclasses: prepend an "AT SH " step - /// when ECUAddress <> 0. + /// + /// Helper for subclasses: prepend an "AT SH " step + /// when ECUAddress <> 0. + /// function PrependHeaderStep(const Steps: TArray; const ECUAddress: Word): TArray; public @@ -127,24 +155,35 @@ TOBDStandardSessionNegotiator = class(TInterfacedObject, IOBDSessionNegotiator function DisplayName: string; virtual; end; -/// Build an adapter (AT/ST) step. +/// +/// Build an adapter (AT/ST) step. +/// function ATStep(const Cmd, Description: string): TOBDSessionStep; overload; function ATStep(const Cmd, Description: string; TimeoutMs: Cardinal): TOBDSessionStep; overload; -/// Build a UDS step from raw bytes. +/// +/// Build a UDS step from raw bytes. +/// function UDSStep(const Bytes: TBytes; const Description: string): TOBDSessionStep; overload; function UDSStep(const Bytes: TBytes; const ExpectedResponse: TBytes; const Description: string): TOBDSessionStep; overload; -/// Encode SessionType as the byte that follows SID 0x10. +/// +/// Encode SessionType as the byte that follows SID 0x10. +/// function SessionTypeByte(SessionType: TOBDSessionType): Byte; -/// Format a CAN-ID as the 3-hex-digit value AT SH expects. +/// +/// Format a CAN-ID as the 3-hex-digit value AT SH expects. +/// function FormatHeader(const ECUAddress: Word): string; implementation +//------------------------------------------------------------------------------ +// FORMAT HEADER +//------------------------------------------------------------------------------ function FormatHeader(const ECUAddress: Word): string; begin // 11-bit IDs (the OBD-II range 0x000-0x7FF) need 3 hex chars; 29-bit @@ -156,6 +195,9 @@ function FormatHeader(const ECUAddress: Word): string; Result := Format('%.8X', [ECUAddress]); end; +//------------------------------------------------------------------------------ +// SESSION TYPE BYTE +//------------------------------------------------------------------------------ function SessionTypeByte(SessionType: TOBDSessionType): Byte; begin case SessionType of @@ -170,6 +212,9 @@ function SessionTypeByte(SessionType: TOBDSessionType): Byte; end; end; +//------------------------------------------------------------------------------ +// ATSTEP +//------------------------------------------------------------------------------ function ATStep(const Cmd, Description: string): TOBDSessionStep; begin Result := Default(TOBDSessionStep); @@ -178,6 +223,9 @@ function ATStep(const Cmd, Description: string): TOBDSessionStep; Result.Description := Description; end; +//------------------------------------------------------------------------------ +// ATSTEP +//------------------------------------------------------------------------------ function ATStep(const Cmd, Description: string; TimeoutMs: Cardinal): TOBDSessionStep; begin @@ -185,6 +233,9 @@ function ATStep(const Cmd, Description: string; Result.TimeoutMs := TimeoutMs; end; +//------------------------------------------------------------------------------ +// UDSSTEP +//------------------------------------------------------------------------------ function UDSStep(const Bytes: TBytes; const Description: string): TOBDSessionStep; begin Result := Default(TOBDSessionStep); @@ -193,6 +244,9 @@ function UDSStep(const Bytes: TBytes; const Description: string): TOBDSessionSte Result.Description := Description; end; +//------------------------------------------------------------------------------ +// UDSSTEP +//------------------------------------------------------------------------------ function UDSStep(const Bytes: TBytes; const ExpectedResponse: TBytes; const Description: string): TOBDSessionStep; begin @@ -203,6 +257,10 @@ function UDSStep(const Bytes: TBytes; const ExpectedResponse: TBytes; //============================================================================== // TOBDStandardSessionNegotiator //============================================================================== + +//------------------------------------------------------------------------------ +// PREPEND HEADER STEP +//------------------------------------------------------------------------------ function TOBDStandardSessionNegotiator.PrependHeaderStep( const Steps: TArray; const ECUAddress: Word): TArray; @@ -214,6 +272,9 @@ function TOBDStandardSessionNegotiator.PrependHeaderStep( ] + Steps; end; +//------------------------------------------------------------------------------ +// BEGIN SESSION PLAN +//------------------------------------------------------------------------------ function TOBDStandardSessionNegotiator.BeginSessionPlan( SessionType: TOBDSessionType; const ECUAddress: Word): TOBDSessionPlan; @@ -237,6 +298,9 @@ function TOBDStandardSessionNegotiator.BeginSessionPlan( Result.TesterPresentRequest := TBytes.Create($3E, $80); end; +//------------------------------------------------------------------------------ +// END SESSION PLAN +//------------------------------------------------------------------------------ function TOBDStandardSessionNegotiator.EndSessionPlan( const ECUAddress: Word): TOBDSessionPlan; begin @@ -249,6 +313,9 @@ function TOBDStandardSessionNegotiator.EndSessionPlan( Result.TesterPresentRequest := TBytes.Create($3E, $80); end; +//------------------------------------------------------------------------------ +// REQUIRES SECURITY ACCESS +//------------------------------------------------------------------------------ function TOBDStandardSessionNegotiator.RequiresSecurityAccess( SessionType: TOBDSessionType): Boolean; begin @@ -258,11 +325,17 @@ function TOBDStandardSessionNegotiator.RequiresSecurityAccess( Result := SessionType = sstProgramming; end; +//------------------------------------------------------------------------------ +// DEFAULT TESTER PRESENT MS +//------------------------------------------------------------------------------ function TOBDStandardSessionNegotiator.DefaultTesterPresentMs: Cardinal; begin Result := 2000; end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDStandardSessionNegotiator.DisplayName: string; begin Result := 'ISO 14229 standard'; diff --git a/src/Services/OBD.OEM.SessionHelper.pas b/src/Services/OBD.OEM.SessionHelper.pas new file mode 100644 index 00000000..6495b889 --- /dev/null +++ b/src/Services/OBD.OEM.SessionHelper.pas @@ -0,0 +1,325 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.OEM.SessionHelper.pas +// CONTENTS : One-call wrapper for service-routine execution +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.OEM.SessionHelper; + +interface + +uses + System.SysUtils, + + OBD.OEM.ServiceRoutines, + OBD.ECU.Flashing.VoltageGate, + OBD.UDS.NRC; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDOEMSessionHelper = class(Exception); + + /// + /// Stage at which a routine execution finished or aborted. + /// On success, the helper reports reseSessionClose; on failure it + /// reports the stage that failed. + /// + TOBDRoutineExecutionStage = ( + reseNotStarted, + reseSessionOpen, + reseVoltageGate, + reseRoutineStart, + reseRoutineWait, + reseResultRead, + reseSessionClose + ); + + TOBDRoutineExecutionResult = record + /// + /// Success. + /// + Success: Boolean; + /// + /// Routine key. + /// + RoutineKey: string; + /// + /// Abort stage. + /// + AbortStage: TOBDRoutineExecutionStage; + NRC: Byte; // 0 if no NRC was raised + ErrorMessage: string; // populated on failure + MeasuredVolts: Single; // 0 if voltage gate wasn't consulted + ResultBytes: TBytes; // ResultRead payload on success + end; + + /// + /// Open the diagnostic session at the given session-type byte + /// (e.g. 0x03 = Extended). Return True on success; out-parameter NRC + /// carries the negative-response byte on failure (0 if non-NRC error). + /// + TOBDSessionOpenCallback = reference to function(SessionType: Byte; + out NRC: Byte): Boolean; + + /// + /// Send the UDS 0x31 RoutineControl frame for the given + /// routine. Frame is pre-built by BuildRoutineControlFrame. Return + /// True on positive response. + /// + TOBDRoutineStartCallback = reference to function(const Frame: TBytes; + out NRC: Byte): Boolean; + + /// + /// Read the routine result via UDS 0x31 sub-function 0x03. + /// Returns True + ResultBytes on positive response. + /// + TOBDRoutineResultCallback = reference to function(RID: Word; + out ResultBytes: TBytes; out NRC: Byte): Boolean; + + /// + /// Close the diagnostic session (return to default). + /// + TOBDSessionCloseCallback = reference to function: Boolean; + + /// + /// Read the adapter battery voltage. Mirrors the + /// TOBDVoltageReader signature from OBD.ECU.Flashing.VoltageGate so + /// the gate can be reused as-is. + /// + TOBDOEMSessionVoltageReader = TOBDVoltageReader; + + /// + /// Bundle of callbacks the helper needs. Production callers + /// wire each to their TOBDDiagSession; tests inject lambdas. + /// + TOBDOEMSessionCallbacks = record + /// + /// Open session. + /// + OpenSession: TOBDSessionOpenCallback; + /// + /// Start routine. + /// + StartRoutine: TOBDRoutineStartCallback; + /// + /// Read result. + /// + ReadResult: TOBDRoutineResultCallback; + /// + /// Close session. + /// + CloseSession: TOBDSessionCloseCallback; + ReadVoltage: TOBDOEMSessionVoltageReader; // optional; only consulted + // when Routine.Safety = srsBatteryMin12V5 + end; + + TOBDOEMSessionHelper = class + private + FVoltageGate: TOBDProgrammingVoltageGate; + FOwnsGate: Boolean; + /// + /// Apply voltage gate. + /// + function ApplyVoltageGate(const Routine: TOBDServiceRoutine; + const ReadVoltage: TOBDOEMSessionVoltageReader; + var + Res: TOBDRoutineExecutionResult): Boolean; + /// + /// Set failure. + /// + procedure SetFailure(var Res: TOBDRoutineExecutionResult; + Stage: TOBDRoutineExecutionStage; NRC: Byte; const Msg: string); + public + /// + /// Construct with an optional pre-configured voltage gate. + /// Pass nil to let the helper own a default gate (12.5 V threshold). + /// + constructor Create(VoltageGate: TOBDProgrammingVoltageGate = nil); + /// + /// Destroy. + /// + destructor Destroy; override; + + /// + /// Run service routine. + /// + function RunServiceRoutine(const Routine: TOBDServiceRoutine; + const Callbacks: TOBDOEMSessionCallbacks): TOBDRoutineExecutionResult; + + /// + /// Direct access to the configured voltage gate so callers + /// can register OEM-specific thresholds (e.g. tesla -> 13.0 V). + /// + property VoltageGate: TOBDProgrammingVoltageGate read FVoltageGate; + end; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TOBDOEMSessionHelper.Create(VoltageGate: TOBDProgrammingVoltageGate); +begin + // Initialize the inherited class + inherited Create; + if VoltageGate = nil then + begin + // Create FVoltageGate + FVoltageGate := TOBDProgrammingVoltageGate.Create; + FOwnsGate := True; + end + else + begin + FVoltageGate := VoltageGate; + FOwnsGate := False; + end; +end; + +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ +destructor TOBDOEMSessionHelper.Destroy; +begin + if FOwnsGate then FVoltageGate.Free; + // Call the inherited handler + inherited; +end; + +//------------------------------------------------------------------------------ +// SET FAILURE +//------------------------------------------------------------------------------ +procedure TOBDOEMSessionHelper.SetFailure(var Res: TOBDRoutineExecutionResult; + Stage: TOBDRoutineExecutionStage; NRC: Byte; const Msg: string); +begin + Res.Success := False; + Res.AbortStage := Stage; + Res.NRC := NRC; + if NRC <> 0 then + Res.ErrorMessage := Msg + ' [' + FormatNRC(NRC) + ']' + else + Res.ErrorMessage := Msg; +end; + +//------------------------------------------------------------------------------ +// APPLY VOLTAGE GATE +//------------------------------------------------------------------------------ +function TOBDOEMSessionHelper.ApplyVoltageGate( + const Routine: TOBDServiceRoutine; + const ReadVoltage: TOBDOEMSessionVoltageReader; + var + Res: TOBDRoutineExecutionResult): Boolean; +var + GateResult: TOBDVoltageGateResult; +begin + if Routine.Safety <> srsBatteryMin12V5 then + Exit(True); // gate not required for this routine class + if not Assigned(ReadVoltage) then + begin + SetFailure(Res, reseVoltageGate, 0, + Format('routine %s requires voltage check but no ReadVoltage callback supplied', + [Routine.Key])); + Exit(False); + end; + GateResult := FVoltageGate.Check(ReadVoltage, ''); + Res.MeasuredVolts := GateResult.MeasuredVolts; + if not GateResult.Passed then + begin + SetFailure(Res, reseVoltageGate, 0, + 'voltage gate failed: ' + GateResult.Reason); + Exit(False); + end; + // Initialize result + Result := True; +end; + +//------------------------------------------------------------------------------ +// RUN SERVICE ROUTINE +//------------------------------------------------------------------------------ +function TOBDOEMSessionHelper.RunServiceRoutine( + const Routine: TOBDServiceRoutine; + const Callbacks: TOBDOEMSessionCallbacks): TOBDRoutineExecutionResult; +var + Frame: TBytes; + NRC: Byte; + ResultBytes: TBytes; +begin + Result := Default(TOBDRoutineExecutionResult); + Result.RoutineKey := Routine.Key; + Result.AbortStage := reseNotStarted; + + if Routine.Key = '' then + raise EOBDOEMSessionHelper.Create('Routine has empty key'); + if not Assigned(Callbacks.OpenSession) then + raise EOBDOEMSessionHelper.Create('OpenSession callback required'); + if not Assigned(Callbacks.StartRoutine) then + raise EOBDOEMSessionHelper.Create('StartRoutine callback required'); + if not Assigned(Callbacks.CloseSession) then + raise EOBDOEMSessionHelper.Create('CloseSession callback required'); + + // 1. Open the diagnostic session. + NRC := 0; + if not Callbacks.OpenSession(Routine.RequiredSessionType, NRC) then + begin + SetFailure(Result, reseSessionOpen, NRC, + Format('failed to open diagnostic session 0x%.2x', + [Routine.RequiredSessionType])); + Exit; + end; + + try + // 2. Voltage gate (only when the routine demands it). + if not ApplyVoltageGate(Routine, Callbacks.ReadVoltage, Result) then + Exit; + + // 3. Build and send the RoutineControl request. + Frame := BuildRoutineControlFrame(Routine); + NRC := 0; + if not Callbacks.StartRoutine(Frame, NRC) then + begin + SetFailure(Result, reseRoutineStart, NRC, + Format('routine %s start refused', [Routine.Key])); + Exit; + end; + + // 4. Read result. Some routines complete without an explicit + // result-read; ReadResult is optional. + if Assigned(Callbacks.ReadResult) then + begin + NRC := 0; + if not Callbacks.ReadResult(Routine.RoutineIdentifier, + ResultBytes, NRC) then + begin + SetFailure(Result, reseResultRead, NRC, + Format('routine %s result read failed', [Routine.Key])); + Exit; + end; + Result.ResultBytes := ResultBytes; + end; + + Result.Success := True; + Result.AbortStage := reseSessionClose; + finally + // 5. Always attempt to close the session. Close failure does not + // demote a successful run, but is captured if we were already + // failing. + if not Callbacks.CloseSession then + if Result.Success then + begin + Result.Success := False; + Result.AbortStage := reseSessionClose; + Result.ErrorMessage := 'session close failed after successful routine'; + end; + end; +end; + +end. diff --git a/src/Services/OBD.OEM.Smart.pas b/src/Services/OBD.OEM.Smart.pas index 10d9891b..4a477289 100644 --- a/src/Services/OBD.OEM.Smart.pas +++ b/src/Services/OBD.OEM.Smart.pas @@ -27,13 +27,18 @@ interface TOBDOEMExtensionSmart = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -50,21 +55,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionSmart.ManufacturerKey: string; -begin Result := 'SMART'; end; +begin + Result := 'SMART'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionSmart.DisplayName: string; -begin Result := 'smart Automobile Co. (Mercedes-Geely JV)'; end; +begin + Result := 'smart Automobile Co. (Mercedes-Geely JV)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionSmart.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in smart.json. Result := VINMatchesCatalog('smart.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionSmart.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are smart.json // + uds-standard.json. Hardcoded entries removed. @@ -75,16 +100,28 @@ procedure TOBDOEMExtensionSmart.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionSmart.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('smart.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionSmart.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin @@ -94,6 +131,9 @@ procedure TOBDOEMExtensionSmart.SeedDefaultSeedKeyAlgorithms( Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionSmart.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -101,9 +141,17 @@ procedure TOBDOEMExtensionSmart.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionSmart.DtcCatalogFileName: string; -begin Result := 'dtc-smart.json'; end; +begin + Result := 'dtc-smart.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionSmart.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Stellantis.pas b/src/Services/OBD.OEM.Stellantis.pas index e0eb94a7..d56694b4 100644 --- a/src/Services/OBD.OEM.Stellantis.pas +++ b/src/Services/OBD.OEM.Stellantis.pas @@ -40,13 +40,18 @@ TOBDStellantisSessionNegotiator = class(TOBDStandardSessionNegotiator) TOBDOEMExtensionStellantis = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -64,6 +69,9 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// BEGIN SESSION PLAN +//------------------------------------------------------------------------------ function TOBDStellantisSessionNegotiator.BeginSessionPlan( SessionType: TOBDSessionType; const ECUAddress: Word): TOBDSessionPlan; @@ -78,16 +86,25 @@ function TOBDStellantisSessionNegotiator.BeginSessionPlan( ]; end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDStellantisSessionNegotiator.DisplayName: string; begin Result := 'Stellantis DiagBox / wiTech'; end; +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionStellantis.CreateSessionNegotiator: IOBDSessionNegotiator; begin Result := TOBDStellantisSessionNegotiator.Create; end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionStellantis.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin @@ -97,6 +114,9 @@ procedure TOBDOEMExtensionStellantis.SeedDefaultSeedKeyAlgorithms( Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionStellantis.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -104,23 +124,49 @@ procedure TOBDOEMExtensionStellantis.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionStellantis.DtcCatalogFileName: string; -begin Result := 'dtc-stellantis.json'; end; +begin + Result := 'dtc-stellantis.json'; +end; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionStellantis.ManufacturerKey: string; -begin Result := 'STLA'; end; +begin + Result := 'STLA'; +end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionStellantis.DisplayName: string; -begin Result := 'Stellantis (FCA + PSA)'; end; +begin + Result := 'Stellantis (FCA + PSA)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionStellantis.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in stellantis.json. Result := VINMatchesCatalog('stellantis.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionStellantis.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are stellantis.json // + uds-standard.json. Hardcoded entries removed. @@ -131,16 +177,28 @@ procedure TOBDOEMExtensionStellantis.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionStellantis.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('stellantis.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionStellantis.DecodeDID(const DID: Word; const Payload: TBytes): string; var diff --git a/src/Services/OBD.OEM.Subaru.pas b/src/Services/OBD.OEM.Subaru.pas index be28c8de..7ca1c1d3 100644 --- a/src/Services/OBD.OEM.Subaru.pas +++ b/src/Services/OBD.OEM.Subaru.pas @@ -21,13 +21,18 @@ interface TOBDOEMExtensionSubaru = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -44,21 +49,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionSubaru.ManufacturerKey: string; -begin Result := 'SUBARU'; end; +begin + Result := 'SUBARU'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionSubaru.DisplayName: string; -begin Result := 'Subaru Corporation'; end; +begin + Result := 'Subaru Corporation'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionSubaru.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in subaru.json. Result := VINMatchesCatalog('subaru.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionSubaru.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are subaru.json // + uds-standard.json. Hardcoded entries removed. @@ -69,16 +94,28 @@ procedure TOBDOEMExtensionSubaru.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionSubaru.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('subaru.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionSubaru.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); const @@ -95,6 +132,9 @@ procedure TOBDOEMExtensionSubaru.SeedDefaultSeedKeyAlgorithms( 'community-pr', False)); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionSubaru.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -102,9 +142,17 @@ procedure TOBDOEMExtensionSubaru.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionSubaru.DtcCatalogFileName: string; -begin Result := 'dtc-subaru.json'; end; +begin + Result := 'dtc-subaru.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionSubaru.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Suzuki.pas b/src/Services/OBD.OEM.Suzuki.pas index 50e424b9..7a89cb36 100644 --- a/src/Services/OBD.OEM.Suzuki.pas +++ b/src/Services/OBD.OEM.Suzuki.pas @@ -22,13 +22,18 @@ interface TOBDOEMExtensionSuzuki = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -45,21 +50,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionSuzuki.ManufacturerKey: string; -begin Result := 'SUZUKI'; end; +begin + Result := 'SUZUKI'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionSuzuki.DisplayName: string; -begin Result := 'Suzuki Motor Corp. (incl. Maruti Suzuki India)'; end; +begin + Result := 'Suzuki Motor Corp. (incl. Maruti Suzuki India)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionSuzuki.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in suzuki.json. Result := VINMatchesCatalog('suzuki.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionSuzuki.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are suzuki.json // + uds-standard.json. Hardcoded entries removed. @@ -70,22 +95,37 @@ procedure TOBDOEMExtensionSuzuki.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionSuzuki.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('suzuki.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionSuzuki.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionSuzuki.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -93,9 +133,17 @@ procedure TOBDOEMExtensionSuzuki.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionSuzuki.DtcCatalogFileName: string; -begin Result := 'dtc-suzuki.json'; end; +begin + Result := 'dtc-suzuki.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionSuzuki.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Tata.pas b/src/Services/OBD.OEM.Tata.pas index 3ce25077..131148e1 100644 --- a/src/Services/OBD.OEM.Tata.pas +++ b/src/Services/OBD.OEM.Tata.pas @@ -25,13 +25,18 @@ interface TOBDOEMExtensionTata = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -48,21 +53,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionTata.ManufacturerKey: string; -begin Result := 'TATA'; end; +begin + Result := 'TATA'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionTata.DisplayName: string; -begin Result := 'Tata Motors Ltd.'; end; +begin + Result := 'Tata Motors Ltd.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionTata.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in tata.json. Result := VINMatchesCatalog('tata.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionTata.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are tata.json // + uds-standard.json. Hardcoded entries removed. @@ -73,22 +98,37 @@ procedure TOBDOEMExtensionTata.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionTata.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('tata.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionTata.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionTata.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -96,9 +136,17 @@ procedure TOBDOEMExtensionTata.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionTata.DtcCatalogFileName: string; -begin Result := 'dtc-tata.json'; end; +begin + Result := 'dtc-tata.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionTata.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Tesla.pas b/src/Services/OBD.OEM.Tesla.pas index c01885b1..08188a47 100644 --- a/src/Services/OBD.OEM.Tesla.pas +++ b/src/Services/OBD.OEM.Tesla.pas @@ -27,13 +27,18 @@ interface TOBDOEMExtensionTesla = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; function DtcCatalogFileName: string; override; @@ -49,21 +54,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionTesla.ManufacturerKey: string; -begin Result := 'TESLA'; end; +begin + Result := 'TESLA'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionTesla.DisplayName: string; -begin Result := 'Tesla, Inc.'; end; +begin + Result := 'Tesla, Inc.'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionTesla.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in tesla.json. Result := VINMatchesCatalog('tesla.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionTesla.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are tesla.json // + uds-standard.json. Hardcoded entries removed. @@ -74,16 +99,28 @@ procedure TOBDOEMExtensionTesla.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionTesla.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('tesla.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionTesla.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -91,9 +128,17 @@ procedure TOBDOEMExtensionTesla.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionTesla.DtcCatalogFileName: string; -begin Result := 'dtc-tesla.json'; end; +begin + Result := 'dtc-tesla.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionTesla.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Toyota.pas b/src/Services/OBD.OEM.Toyota.pas index 6eb0045c..ca3a7b8e 100644 --- a/src/Services/OBD.OEM.Toyota.pas +++ b/src/Services/OBD.OEM.Toyota.pas @@ -24,13 +24,18 @@ interface TOBDOEMExtensionToyota = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -47,21 +52,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionToyota.ManufacturerKey: string; -begin Result := 'TOYOTA'; end; +begin + Result := 'TOYOTA'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionToyota.DisplayName: string; -begin Result := 'Toyota Motor Corporation'; end; +begin + Result := 'Toyota Motor Corporation'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionToyota.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in toyota.json. Result := VINMatchesCatalog('toyota.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionToyota.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are toyota.json // + uds-standard.json. Hardcoded entries removed. @@ -72,16 +97,28 @@ procedure TOBDOEMExtensionToyota.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionToyota.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('toyota.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionToyota.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin @@ -91,6 +128,9 @@ procedure TOBDOEMExtensionToyota.SeedDefaultSeedKeyAlgorithms( Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionToyota.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -98,9 +138,17 @@ procedure TOBDOEMExtensionToyota.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionToyota.DtcCatalogFileName: string; -begin Result := 'dtc-toyota.json'; end; +begin + Result := 'dtc-toyota.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionToyota.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.UdsClient.Async.pas b/src/Services/OBD.OEM.UdsClient.Async.pas index 93713184..53d2bdf1 100644 --- a/src/Services/OBD.OEM.UdsClient.Async.pas +++ b/src/Services/OBD.OEM.UdsClient.Async.pas @@ -27,26 +27,30 @@ interface OBD.Async, OBD.OEM.UdsClient, OBD.OEM.Catalog.JSON; type - /// Async facet of . Each call - /// returns immediately with an ; the - /// underlying UDS round-trip happens on the client's worker - /// thread. Session-management calls (OpenSession, - /// CloseSession, IsOpen) are synchronous because - /// they must complete before any async call is meaningful. + /// + /// Async facet of . Each call + /// returns immediately with an ; the + /// underlying UDS round-trip happens on the client's worker + /// thread. Session-management calls (OpenSession, + /// CloseSession, IsOpen) are synchronous because + /// they must complete before any async call is meaningful. /// IOBDUdsClientAsync = interface ['{D7E8F9A0-1B2C-3D4E-5F60-718293A4B5C6}'] - /// Synchronously open a session against the supplied - /// catalog/transport pair. Must be called before any of the - /// *Async methods. + /// + /// Synchronously open a session against the supplied + /// catalog/transport pair. Must be called before any of the + /// *Async methods. + /// procedure OpenSession(const Catalog: TOBDOEMJSONCatalog; const Transport: IOBDDiagnosticTransport; ECUAddress: Word); - /// Synchronously close the session. Drains the work - /// queue first — every queued future is signalled cancelled so - /// callers waiting on Await get EOBDOperationCancelled. + /// + /// Synchronously close the session. Drains the work + /// queue first — every queued future is signalled cancelled so + /// callers waiting on Await get EOBDOperationCancelled. /// procedure CloseSession; function IsOpen: Boolean; @@ -66,9 +70,11 @@ interface const Token: IOBDCancellationToken = nil) : IOBDFuture; - /// Result is the (live, owned) TOBDCodingValues - /// instance. Caller must Free it. Mirrors the sync - /// client's contract. + /// + /// Result is the (live, owned) TOBDCodingValues + /// instance. Caller must Free it. Mirrors the sync + /// client's contract. + /// function ReadCodingBlockAsync(const Name: string; const Token: IOBDCancellationToken = nil) : IOBDFuture; @@ -87,21 +93,26 @@ interface const Token: IOBDCancellationToken = nil) : IOBDFuture>; - /// Direct access to the wrapped sync client for - /// streaming live-PID work — streaming already has its own - /// thread and IOBDStreamHandle; wrapping it as a future of - /// "stream handle" would obscure the streaming contract. + /// + /// Direct access to the wrapped sync client for + /// streaming live-PID work — streaming already has its own + /// thread and IOBDStreamHandle; wrapping it as a future of + /// "stream handle" would obscure the streaming contract. /// function Sync: IOBDUdsClient; end; -/// Construct a fresh async client. The client owns one -/// worker thread; CloseSession stops it cleanly. +/// +/// Construct a fresh async client. The client owns one +/// worker thread; CloseSession stops it cleanly. +/// function CreateUdsClientAsync: IOBDUdsClientAsync; -/// Wrap an existing sync client. Useful when callers -/// already hold a configured IOBDUdsClient (for example a -/// test mock). +/// +/// Wrap an existing sync client. Useful when callers +/// already hold a configured IOBDUdsClient (for example a +/// test mock). +/// function WrapAsAsync(const Sync: IOBDUdsClient): IOBDUdsClientAsync; implementation @@ -109,10 +120,12 @@ implementation type TWorkProc = reference to procedure(const Sync: IOBDUdsClient); - /// One queued unit of work. Carries the closure that - /// runs on the worker, the cancellation token to consult before - /// running, and a "drop" callback used during shutdown to settle - /// the corresponding promise as cancelled. + /// + /// One queued unit of work. Carries the closure that + /// runs on the worker, the cancellation token to consult before + /// running, and a "drop" callback used during shutdown to settle + /// the corresponding promise as cancelled. + /// TWorkItem = record Run: TWorkProc; Token: IOBDCancellationToken; @@ -135,8 +148,10 @@ TWorker = class(TThread) constructor Create(const ASync: IOBDUdsClient); destructor Destroy; override; procedure Enqueue(const Item: TWorkItem); - /// Signal stop and drain the queue (settling each - /// promise as cancelled). Joins the thread. + /// + /// Signal stop and drain the queue (settling each + /// promise as cancelled). Joins the thread. + /// procedure StopAndDrain; end; @@ -188,6 +203,10 @@ TUdsClientAsync = class(TInterfacedObject, IOBDUdsClientAsync) //============================================================================== // TWorker //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TWorker.Create(const ASync: IOBDUdsClient); begin inherited Create(True {suspended}); @@ -199,6 +218,9 @@ constructor TWorker.Create(const ASync: IOBDUdsClient); Start; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TWorker.Destroy; begin FSignal.Free; @@ -207,6 +229,9 @@ destructor TWorker.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// ENQUEUE +//------------------------------------------------------------------------------ procedure TWorker.Enqueue(const Item: TWorkItem); var Refused: Boolean; @@ -230,6 +255,9 @@ procedure TWorker.Enqueue(const Item: TWorkItem); FSignal.SetEvent; end; +//------------------------------------------------------------------------------ +// EXECUTE +//------------------------------------------------------------------------------ procedure TWorker.Execute; var Item: TWorkItem; @@ -277,6 +305,9 @@ procedure TWorker.Execute; DrainOnShutdown; end; +//------------------------------------------------------------------------------ +// DRAIN ON SHUTDOWN +//------------------------------------------------------------------------------ procedure TWorker.DrainOnShutdown; var Item: TWorkItem; @@ -294,6 +325,9 @@ procedure TWorker.DrainOnShutdown; end; end; +//------------------------------------------------------------------------------ +// STOP AND DRAIN +//------------------------------------------------------------------------------ procedure TWorker.StopAndDrain; begin FLock.Enter; @@ -310,6 +344,10 @@ procedure TWorker.StopAndDrain; //============================================================================== // TUdsClientAsync //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TUdsClientAsync.Create(const ASync: IOBDUdsClient); begin inherited Create; @@ -319,6 +357,9 @@ constructor TUdsClientAsync.Create(const ASync: IOBDUdsClient); FLock := TCriticalSection.Create; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TUdsClientAsync.Destroy; begin StopWorker; @@ -326,6 +367,9 @@ destructor TUdsClientAsync.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// ENSURE WORKER +//------------------------------------------------------------------------------ procedure TUdsClientAsync.EnsureWorker; begin FLock.Enter; @@ -337,6 +381,9 @@ procedure TUdsClientAsync.EnsureWorker; end; end; +//------------------------------------------------------------------------------ +// STOP WORKER +//------------------------------------------------------------------------------ procedure TUdsClientAsync.StopWorker; var W: TWorker; @@ -355,6 +402,9 @@ procedure TUdsClientAsync.StopWorker; end; end; +//------------------------------------------------------------------------------ +// OPEN SESSION +//------------------------------------------------------------------------------ procedure TUdsClientAsync.OpenSession(const Catalog: TOBDOEMJSONCatalog; const Transport: IOBDDiagnosticTransport; ECUAddress: Word); @@ -363,17 +413,26 @@ procedure TUdsClientAsync.OpenSession(const Catalog: TOBDOEMJSONCatalog; EnsureWorker; end; +//------------------------------------------------------------------------------ +// CLOSE SESSION +//------------------------------------------------------------------------------ procedure TUdsClientAsync.CloseSession; begin StopWorker; FSync.CloseSession; end; +//------------------------------------------------------------------------------ +// IS OPEN +//------------------------------------------------------------------------------ function TUdsClientAsync.IsOpen: Boolean; begin Result := FSync.IsOpen; end; +//------------------------------------------------------------------------------ +// SYNC +//------------------------------------------------------------------------------ function TUdsClientAsync.Sync: IOBDUdsClient; begin Result := FSync; @@ -407,6 +466,9 @@ function TUdsClientAsync.ReadDIDAsync(const NameOrHex: string; Result := Promise; end; +//------------------------------------------------------------------------------ +// WRITE ADAPTATION ASYNC +//------------------------------------------------------------------------------ function TUdsClientAsync.WriteAdaptationAsync(const ChannelOrHex: string; Value: Int64; const Token: IOBDCancellationToken): IOBDFuture; var @@ -434,6 +496,9 @@ function TUdsClientAsync.WriteAdaptationAsync(const ChannelOrHex: string; Result := Promise; end; +//------------------------------------------------------------------------------ +// EXECUTE ROUTINE ASYNC +//------------------------------------------------------------------------------ function TUdsClientAsync.ExecuteRoutineAsync(const NameOrHex: string; const Args: TBytes; RoutineType: Byte; const Token: IOBDCancellationToken): IOBDFuture; @@ -464,6 +529,9 @@ function TUdsClientAsync.ExecuteRoutineAsync(const NameOrHex: string; Result := Promise; end; +//------------------------------------------------------------------------------ +// READ CODING BLOCK ASYNC +//------------------------------------------------------------------------------ function TUdsClientAsync.ReadCodingBlockAsync(const Name: string; const Token: IOBDCancellationToken): IOBDFuture; var @@ -489,6 +557,9 @@ function TUdsClientAsync.ReadCodingBlockAsync(const Name: string; Result := Promise; end; +//------------------------------------------------------------------------------ +// WRITE CODING BLOCK ASYNC +//------------------------------------------------------------------------------ function TUdsClientAsync.WriteCodingBlockAsync(const Name: string; const Values: TOBDCodingValues; const Token: IOBDCancellationToken): IOBDFuture; @@ -522,6 +593,9 @@ function TUdsClientAsync.WriteCodingBlockAsync(const Name: string; Result := Promise; end; +//------------------------------------------------------------------------------ +// RUN ACTUATOR TEST ASYNC +//------------------------------------------------------------------------------ function TUdsClientAsync.RunActuatorTestAsync(const Name: string; AcknowledgeSafetyWarning: Boolean; const Token: IOBDCancellationToken): IOBDFuture; @@ -550,6 +624,9 @@ function TUdsClientAsync.RunActuatorTestAsync(const Name: string; Result := Promise; end; +//------------------------------------------------------------------------------ +// READ DTCS ASYNC +//------------------------------------------------------------------------------ function TUdsClientAsync.ReadDtcsAsync(StatusMask: Byte; const Token: IOBDCancellationToken): IOBDFuture>; var @@ -578,11 +655,18 @@ function TUdsClientAsync.ReadDtcsAsync(StatusMask: Byte; //============================================================================== // Factories //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE UDS CLIENT ASYNC +//------------------------------------------------------------------------------ function CreateUdsClientAsync: IOBDUdsClientAsync; begin Result := TUdsClientAsync.Create(CreateUdsClient); end; +//------------------------------------------------------------------------------ +// WRAP AS ASYNC +//------------------------------------------------------------------------------ function WrapAsAsync(const Sync: IOBDUdsClient): IOBDUdsClientAsync; begin Result := TUdsClientAsync.Create(Sync); diff --git a/src/Services/OBD.OEM.UdsClient.pas b/src/Services/OBD.OEM.UdsClient.pas index c75f7a2e..6a378b9f 100644 --- a/src/Services/OBD.OEM.UdsClient.pas +++ b/src/Services/OBD.OEM.UdsClient.pas @@ -38,14 +38,16 @@ EOBDUdsCatalogMiss = class(EOBDUdsClientError); EOBDUdsNegativeResponse = class(EOBDUdsClientError); EOBDUdsCodingError = class(EOBDUdsClientError); - /// One round-trip diag message: caller passes the - /// service+payload, transport returns the response payload (or - /// raises). Decouples the client from CAN/DoIP wiring. - /// Request begins with the service byte (0x22, 0x2E, 0x31, - /// ...); transport prepends ISO-TP framing. Response begins with - /// the positive-response byte (service+0x40); on a negative - /// response (0x7F xx NRC), the transport raises - /// EOBDUdsNegativeResponse with the NRC byte set. + /// + /// One round-trip diag message: caller passes the + /// service+payload, transport returns the response payload (or + /// raises). Decouples the client from CAN/DoIP wiring. + /// Request begins with the service byte (0x22, 0x2E, 0x31, + /// ...); transport prepends ISO-TP framing. Response begins with + /// the positive-response byte (service+0x40); on a negative + /// response (0x7F xx NRC), the transport raises + /// EOBDUdsNegativeResponse with the NRC byte set. + /// IOBDDiagnosticTransport = interface ['{F1A2B3C4-D5E6-4789-AB12-345678901234}'] function SendReceive(const Request: TBytes; @@ -54,11 +56,13 @@ EOBDUdsCodingError = class(EOBDUdsClientError); function TargetECU: Word; end; - /// Decoded value tagged with its catalog metadata. - /// Numeric kinds populate AsFloat after scale/offset; - /// integer kinds populate AsInteger; text kinds populate - /// AsString. Formatted is always set to a - /// display-ready string ("23.5 °C", "ON", "0xAA BB"). + /// + /// Decoded value tagged with its catalog metadata. + /// Numeric kinds populate AsFloat after scale/offset; + /// integer kinds populate AsInteger; text kinds populate + /// AsString. Formatted is always set to a + /// display-ready string ("23.5 °C", "ON", "0xAA BB"). + /// TOBDDecodedValue = record Name: string; Kind: TOBDOEMDecoderKind; @@ -71,7 +75,9 @@ TOBDDecodedValue = record RawPayload: TBytes; end; - /// One sample emitted by the live-PID streamer. + /// + /// One sample emitted by the live-PID streamer. + /// TOBDLiveSample = record Name: string; Decoded: TOBDDecodedValue; @@ -80,9 +86,11 @@ TOBDLiveSample = record TOBDLiveSampleEvent = reference to procedure(const Sample: TOBDLiveSample); - /// One DTC reported by the ECU. StatusByte is the - /// ISO 14229 §11.4 status byte; Description is filled - /// from the catalog if known. + /// + /// One DTC reported by the ECU. StatusByte is the + /// ISO 14229 §11.4 status byte; Description is filled + /// from the catalog if known. + /// TOBDDtcInstance = record Code: string; StatusByte: Byte; @@ -90,12 +98,14 @@ TOBDDtcInstance = record Severity: TOBDDtcSeverity; end; - /// Coding-block snapshot returned by ReadCodingBlock. - /// Field name → typed value (Int64 for numeric/bit/enum; string - /// for ASCII). Hand back to WriteCodingBlock after edits; - /// the client repacks the modified payload bit-by-bit and writes - /// it via Service 2E. Read-modify-write preserves bits not covered - /// by the catalog block. + /// + /// Coding-block snapshot returned by ReadCodingBlock. + /// Field name → typed value (Int64 for numeric/bit/enum; string + /// for ASCII). Hand back to WriteCodingBlock after edits; + /// the client repacks the modified payload bit-by-bit and writes + /// it via Service 2E. Read-modify-write preserves bits not covered + /// by the catalog block. + /// TOBDCodingValues = class strict private FInts: TDictionary; @@ -111,27 +121,35 @@ TOBDCodingValues = class function HasField(const Name: string): Boolean; function IntFields: TArray>; function StrFields: TArray>; - /// The original payload as read from the ECU. Used by - /// WriteCodingBlock to preserve uncovered bits. + /// + /// The original payload as read from the ECU. Used by + /// WriteCodingBlock to preserve uncovered bits. + /// property Raw: TBytes read FRaw write FRaw; end; - /// Result of an actuator test or routine execution. + /// + /// Result of an actuator test or routine execution. + /// TOBDActuatorResult = record Status: Byte; Message: string; RawResponse: TBytes; end; - /// Stream handle returned by StreamLivePIDs; call - /// Stop to terminate the polling thread. + /// + /// Stream handle returned by StreamLivePIDs; call + /// Stop to terminate the polling thread. + /// IOBDStreamHandle = interface ['{B1C2D3E4-5F60-7890-1234-567890ABCDEF}'] procedure Stop; function IsRunning: Boolean; end; - /// The high-level catalog-driven UDS client. + /// + /// The high-level catalog-driven UDS client. + /// IOBDUdsClient = interface ['{0A1B2C3D-4E5F-6789-ABCD-EF0123456789}'] procedure OpenSession(const Catalog: TOBDOEMJSONCatalog; @@ -163,13 +181,17 @@ TOBDActuatorResult = record : IOBDStreamHandle; end; -/// Construct a fresh UDS client. Call OpenSession -/// before any other method. +/// +/// Construct a fresh UDS client. Call OpenSession +/// before any other method. +/// function CreateUdsClient: IOBDUdsClient; -/// Decode a raw payload using a catalog DID's decoder spec. -/// Exposed for unit tests; production callers go through -/// IOBDUdsClient.ReadDID. +/// +/// Decode a raw payload using a catalog DID's decoder spec. +/// Exposed for unit tests; production callers go through +/// IOBDUdsClient.ReadDID. +/// function DecodePayloadAs(const Catalog: TOBDOEMJSONCatalog; const DID: Word; const Payload: TBytes): TOBDDecodedValue; @@ -183,8 +205,14 @@ implementation // Helpers //============================================================================== -/// Parse "0xABCD" or "abcd" or "43981" -/// (decimal) into a Word. Raises on malformed input. +/// +/// Parse "0xABCD" or "abcd" or "43981" +/// (decimal) into a Word. Raises on malformed input. +/// + +//------------------------------------------------------------------------------ +// PARSE HEX OR DEC WORD +//------------------------------------------------------------------------------ function ParseHexOrDecWord(const S: string): Word; var Stripped: string; @@ -203,12 +231,18 @@ function ParseHexOrDecWord(const S: string): Word; Result := Word(Big); end; -/// Decode a 3-byte UDS DTC representation into the -/// canonical 5-character "P/B/C/U" code per ISO 15031-5 / SAE -/// J2012. Bits 15-14 of the first 16 bits select the system letter; -/// the remaining 14 bits decode as 4 hex digits. The third byte is -/// the failure-mode extension and is typically 0 for stored -/// codes — it isn't part of the displayed code. +/// +/// Decode a 3-byte UDS DTC representation into the +/// canonical 5-character "P/B/C/U" code per ISO 15031-5 / SAE +/// J2012. Bits 15-14 of the first 16 bits select the system letter; +/// the remaining 14 bits decode as 4 hex digits. The third byte is +/// the failure-mode extension and is typically 0 for stored +/// codes — it isn't part of the displayed code. +/// + +//------------------------------------------------------------------------------ +// DECODE DTC CODE +//------------------------------------------------------------------------------ function DecodeDtcCode(const B0, B1, B2: Byte): string; const Letters: array[0..3] of Char = ('P', 'C', 'B', 'U'); @@ -221,9 +255,15 @@ function DecodeDtcCode(const B0, B1, B2: Byte): string; Result := Format('%s%.4X', [Letter, Code14]); end; -/// Look up a DID by its Name (preferred) or hex -/// identifier. Raises EOBDUdsCatalogMiss if neither resolves -/// inside the supplied catalog. +/// +/// Look up a DID by its Name (preferred) or hex +/// identifier. Raises EOBDUdsCatalogMiss if neither resolves +/// inside the supplied catalog. +/// + +//------------------------------------------------------------------------------ +// RESOLVE DID +//------------------------------------------------------------------------------ function ResolveDID(const Catalog: TOBDOEMJSONCatalog; const NameOrHex: string; out Entry: TOBDOEMDIDEntry): Word; @@ -250,7 +290,13 @@ function ResolveDID(const Catalog: TOBDOEMJSONCatalog; 'DID %s not present in catalog', [NameOrHex]); end; -/// Look up an adaptation channel by name or hex. +/// +/// Look up an adaptation channel by name or hex. +/// + +//------------------------------------------------------------------------------ +// RESOLVE ADAPTATION +//------------------------------------------------------------------------------ function ResolveAdaptation(const Catalog: TOBDOEMJSONCatalog; const NameOrHex: string; out Entry: TOBDAdaptationEntry): Word; @@ -283,7 +329,13 @@ function ResolveAdaptation(const Catalog: TOBDOEMJSONCatalog; 'Adaptation channel %s not in catalog', [NameOrHex]); end; -/// Look up an actuator test by name or hex. +/// +/// Look up an actuator test by name or hex. +/// + +//------------------------------------------------------------------------------ +// RESOLVE ACTUATOR +//------------------------------------------------------------------------------ function ResolveActuator(const Catalog: TOBDOEMJSONCatalog; const NameOrHex: string; out Entry: TOBDActuatorTestEntry): Word; @@ -316,9 +368,15 @@ function ResolveActuator(const Catalog: TOBDOEMJSONCatalog; 'Actuator test %s not in catalog', [NameOrHex]); end; -/// Look up a coding block by name. (Coding blocks are -/// always identified by name; the underlying DID can collide across -/// blocks for very-different coding payloads.) +/// +/// Look up a coding block by name. (Coding blocks are +/// always identified by name; the underlying DID can collide across +/// blocks for very-different coding payloads.) +/// + +//------------------------------------------------------------------------------ +// RESOLVE CODING BLOCK +//------------------------------------------------------------------------------ function ResolveCodingBlock(const Catalog: TOBDOEMJSONCatalog; const Name: string; out Entry: TOBDCodingBlockEntry): Boolean; @@ -338,7 +396,13 @@ function ResolveCodingBlock(const Catalog: TOBDOEMJSONCatalog; end; end; -/// Look up a routine by name or hex. +/// +/// Look up a routine by name or hex. +/// + +//------------------------------------------------------------------------------ +// RESOLVE ROUTINE +//------------------------------------------------------------------------------ function ResolveRoutine(const Catalog: TOBDOEMJSONCatalog; const NameOrHex: string; out Entry: TOBDOEMRoutineEntry): Word; @@ -376,11 +440,17 @@ function ResolveRoutine(const Catalog: TOBDOEMJSONCatalog; // don't pull TUDSProtocol because we only need one-shot byte builders.) //============================================================================== +//------------------------------------------------------------------------------ +// BUILD READ DATA BY IDENTIFIER +//------------------------------------------------------------------------------ function BuildReadDataByIdentifier(const DID: Word): TBytes; begin Result := [$22, Byte(DID shr 8), Byte(DID and $FF)]; end; +//------------------------------------------------------------------------------ +// BUILD WRITE DATA BY IDENTIFIER +//------------------------------------------------------------------------------ function BuildWriteDataByIdentifier(const DID: Word; const Data: TBytes): TBytes; var I: Integer; @@ -393,6 +463,9 @@ function BuildWriteDataByIdentifier(const DID: Word; const Data: TBytes): TBytes Result[3 + I] := Data[I]; end; +//------------------------------------------------------------------------------ +// BUILD ROUTINE CONTROL +//------------------------------------------------------------------------------ function BuildRoutineControl(const RoutineType: Byte; const RID: Word; const Args: TBytes): TBytes; var @@ -407,6 +480,9 @@ function BuildRoutineControl(const RoutineType: Byte; const RID: Word; Result[4 + I] := Args[I]; end; +//------------------------------------------------------------------------------ +// BUILD READ DTC BY STATUS MASK +//------------------------------------------------------------------------------ function BuildReadDtcByStatusMask(const StatusMask: Byte): TBytes; begin Result := [$19, $02, StatusMask]; @@ -416,6 +492,9 @@ function BuildReadDtcByStatusMask(const StatusMask: Byte): TBytes; // Decoder dispatch (E.2) //============================================================================== +//------------------------------------------------------------------------------ +// FORMAT NUMERIC +//------------------------------------------------------------------------------ function FormatNumeric(const Value: Double; const Unit_: string): string; begin if Frac(Value) = 0 then @@ -426,16 +505,25 @@ function FormatNumeric(const Value: Double; const Unit_: string): string; Result := Result + ' ' + Unit_; end; +//------------------------------------------------------------------------------ +// READ UINT16 BE +//------------------------------------------------------------------------------ function ReadUInt16BE(const B: TBytes; Offset: Integer): Word; begin Result := (Word(B[Offset]) shl 8) or B[Offset + 1]; end; +//------------------------------------------------------------------------------ +// READ INT16 BE +//------------------------------------------------------------------------------ function ReadInt16BE(const B: TBytes; Offset: Integer): SmallInt; begin Result := SmallInt((Word(B[Offset]) shl 8) or B[Offset + 1]); end; +//------------------------------------------------------------------------------ +// READ UINT32 BE +//------------------------------------------------------------------------------ function ReadUInt32BE(const B: TBytes; Offset: Integer): Cardinal; begin Result := (Cardinal(B[Offset]) shl 24) or @@ -444,11 +532,17 @@ function ReadUInt32BE(const B: TBytes; Offset: Integer): Cardinal; B[Offset + 3]; end; +//------------------------------------------------------------------------------ +// READ INT32 BE +//------------------------------------------------------------------------------ function ReadInt32BE(const B: TBytes; Offset: Integer): Integer; begin Result := Integer(ReadUInt32BE(B, Offset)); end; +//------------------------------------------------------------------------------ +// HEX DUMP +//------------------------------------------------------------------------------ function HexDump(const B: TBytes): string; var I: Integer; @@ -461,6 +555,9 @@ function HexDump(const B: TBytes): string; end; end; +//------------------------------------------------------------------------------ +// DECODE PAYLOAD AS +//------------------------------------------------------------------------------ function DecodePayloadAs(const Catalog: TOBDOEMJSONCatalog; const DID: Word; const Payload: TBytes): TOBDDecodedValue; @@ -600,7 +697,8 @@ function DecodePayloadAs(const Catalog: TOBDOEMJSONCatalog; // If the catalog provides a friendlier formatted string, prefer it. if Result.Formatted = '' then begin - var Friendly: string := Catalog.DecodePayload(DID, Payload); + var + Friendly: string := Catalog.DecodePayload(DID, Payload); if Friendly <> '' then Result.Formatted := Friendly; end; @@ -610,6 +708,9 @@ function DecodePayloadAs(const Catalog: TOBDOEMJSONCatalog; // TOBDCodingValues //============================================================================== +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDCodingValues.Create; begin inherited Create; @@ -617,6 +718,9 @@ constructor TOBDCodingValues.Create; FStrs := TDictionary.Create; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDCodingValues.Destroy; begin FInts.Free; @@ -624,33 +728,51 @@ destructor TOBDCodingValues.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// SET INT +//------------------------------------------------------------------------------ procedure TOBDCodingValues.SetInt(const Name: string; Value: Int64); begin FInts.AddOrSetValue(Name, Value); end; +//------------------------------------------------------------------------------ +// GET INT +//------------------------------------------------------------------------------ function TOBDCodingValues.GetInt(const Name: string): Int64; begin if not FInts.TryGetValue(Name, Result) then raise EOBDUdsCodingError.CreateFmt('coding field not present: %s', [Name]); end; +//------------------------------------------------------------------------------ +// SET STR +//------------------------------------------------------------------------------ procedure TOBDCodingValues.SetStr(const Name: string; const Value: string); begin FStrs.AddOrSetValue(Name, Value); end; +//------------------------------------------------------------------------------ +// GET STR +//------------------------------------------------------------------------------ function TOBDCodingValues.GetStr(const Name: string): string; begin if not FStrs.TryGetValue(Name, Result) then raise EOBDUdsCodingError.CreateFmt('coding field not present: %s', [Name]); end; +//------------------------------------------------------------------------------ +// HAS FIELD +//------------------------------------------------------------------------------ function TOBDCodingValues.HasField(const Name: string): Boolean; begin Result := FInts.ContainsKey(Name) or FStrs.ContainsKey(Name); end; +//------------------------------------------------------------------------------ +// INT FIELDS +//------------------------------------------------------------------------------ function TOBDCodingValues.IntFields: TArray>; var I: Integer; @@ -665,6 +787,9 @@ function TOBDCodingValues.IntFields: TArray>; end; end; +//------------------------------------------------------------------------------ +// STR FIELDS +//------------------------------------------------------------------------------ function TOBDCodingValues.StrFields: TArray>; var I: Integer; @@ -683,6 +808,9 @@ function TOBDCodingValues.StrFields: TArray>; // Coding-block bit-level pack / unpack (E.3) //============================================================================== +//------------------------------------------------------------------------------ +// FIELD BIT WIDTH +//------------------------------------------------------------------------------ function FieldBitWidth(const Field: TOBDCodingFieldEntry): Integer; begin if Field.BitWidth > 0 then @@ -701,9 +829,15 @@ function FieldBitWidth(const Field: TOBDCodingFieldEntry): Integer; end; end; -/// Read BitWidth bits starting at byte -/// ByteOffset, bit BitOffset (LSB-first within the -/// byte, big-endian across bytes for multi-byte fields). +/// +/// Read BitWidth bits starting at byte +/// ByteOffset, bit BitOffset (LSB-first within the +/// byte, big-endian across bytes for multi-byte fields). +/// + +//------------------------------------------------------------------------------ +// UNPACK BITS +//------------------------------------------------------------------------------ function UnpackBits(const Payload: TBytes; ByteOffset, BitOffset, BitWidth: Integer): Int64; var @@ -726,10 +860,16 @@ function UnpackBits(const Payload: TBytes; end; end; -/// Pack Value into BitWidth bits starting at -/// the given offset. Mutates Payload in place. Bits not -/// covered by the field are preserved (read-modify-write -/// safe). +/// +/// Pack Value into BitWidth bits starting at +/// the given offset. Mutates Payload in place. Bits not +/// covered by the field are preserved (read-modify-write +/// safe). +/// + +//------------------------------------------------------------------------------ +// PACK BITS +//------------------------------------------------------------------------------ procedure PackBits(var Payload: TBytes; ByteOffset, BitOffset, BitWidth: Integer; Value: Int64); @@ -788,6 +928,9 @@ TUdsStreamThread = class(TThread) AInterval: Cardinal); end; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TUdsStreamThread.Create(const ATransport: IOBDDiagnosticTransport; const ACatalog: TOBDOEMJSONCatalog; const APids: TArray; @@ -803,6 +946,9 @@ constructor TUdsStreamThread.Create(const ATransport: IOBDDiagnosticTransport; FInterval := AInterval; end; +//------------------------------------------------------------------------------ +// EXECUTE +//------------------------------------------------------------------------------ procedure TUdsStreamThread.Execute; var PID: TOBDOEMLivePIDEntry; @@ -855,18 +1001,27 @@ procedure TUdsStreamThread.Execute; end; end; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TStreamHandle.Create(Thread: TUdsStreamThread); begin inherited Create; FThread := Thread; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TStreamHandle.Destroy; begin Stop; inherited; end; +//------------------------------------------------------------------------------ +// STOP +//------------------------------------------------------------------------------ procedure TStreamHandle.Stop; begin if Assigned(FThread) then @@ -877,6 +1032,9 @@ procedure TStreamHandle.Stop; end; end; +//------------------------------------------------------------------------------ +// IS RUNNING +//------------------------------------------------------------------------------ function TStreamHandle.IsRunning: Boolean; begin Result := Assigned(FThread) and not FThread.Finished; @@ -923,12 +1081,18 @@ TOBDUdsClient = class(TInterfacedObject, IOBDUdsClient) : IOBDStreamHandle; end; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDUdsClient.Create; begin inherited Create; FLock := TCriticalSection.Create; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDUdsClient.Destroy; begin CloseSession; @@ -936,6 +1100,9 @@ destructor TOBDUdsClient.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// OPEN SESSION +//------------------------------------------------------------------------------ procedure TOBDUdsClient.OpenSession(const Catalog: TOBDOEMJSONCatalog; const Transport: IOBDDiagnosticTransport; ECUAddress: Word); @@ -958,6 +1125,9 @@ procedure TOBDUdsClient.OpenSession(const Catalog: TOBDOEMJSONCatalog; end; end; +//------------------------------------------------------------------------------ +// CLOSE SESSION +//------------------------------------------------------------------------------ procedure TOBDUdsClient.CloseSession; begin FLock.Enter; @@ -970,17 +1140,26 @@ procedure TOBDUdsClient.CloseSession; end; end; +//------------------------------------------------------------------------------ +// IS OPEN +//------------------------------------------------------------------------------ function TOBDUdsClient.IsOpen: Boolean; begin Result := FOpen; end; +//------------------------------------------------------------------------------ +// ENSURE OPEN +//------------------------------------------------------------------------------ procedure TOBDUdsClient.EnsureOpen; begin if not FOpen then raise EOBDUdsNoSession.Create('OpenSession must be called first'); end; +//------------------------------------------------------------------------------ +// STRIP DIDECHO +//------------------------------------------------------------------------------ function TOBDUdsClient.StripDIDEcho(const Resp: TBytes; DID: Word): TBytes; var I: Integer; @@ -1000,6 +1179,9 @@ function TOBDUdsClient.StripDIDEcho(const Resp: TBytes; DID: Word): TBytes; Result[I] := Resp[3 + I]; end; +//------------------------------------------------------------------------------ +// READ DID +//------------------------------------------------------------------------------ function TOBDUdsClient.ReadDID(const NameOrHex: string): TOBDDecodedValue; var Entry: TOBDOEMDIDEntry; @@ -1021,6 +1203,9 @@ function TOBDUdsClient.ReadDID(const NameOrHex: string): TOBDDecodedValue; end; end; +//------------------------------------------------------------------------------ +// WRITE ADAPTATION +//------------------------------------------------------------------------------ function TOBDUdsClient.WriteAdaptation(const ChannelOrHex: string; Value: Int64): Boolean; var @@ -1067,6 +1252,9 @@ function TOBDUdsClient.WriteAdaptation(const ChannelOrHex: string; end; end; +//------------------------------------------------------------------------------ +// EXECUTE ROUTINE +//------------------------------------------------------------------------------ function TOBDUdsClient.ExecuteRoutine(const NameOrHex: string; const Args: TBytes; RoutineType: Byte = $01): TOBDActuatorResult; @@ -1100,6 +1288,9 @@ function TOBDUdsClient.ExecuteRoutine(const NameOrHex: string; end; end; +//------------------------------------------------------------------------------ +// READ CODING BLOCK +//------------------------------------------------------------------------------ function TOBDUdsClient.ReadCodingBlock(const Name: string): TOBDCodingValues; var Block: TOBDCodingBlockEntry; @@ -1131,7 +1322,8 @@ function TOBDUdsClient.ReadCodingBlock(const Name: string): TOBDCodingValues; Kind := ParseCodingFieldKind(Field.KindStr); if Kind = cfkAscii then begin - var S: string := ''; + var + S: string := ''; for var J := 0 to Field.BitWidth - 1 do if (Field.ByteOffset + J) < Length(Payload) then S := S + Char(Payload[Field.ByteOffset + J]); @@ -1150,6 +1342,9 @@ function TOBDUdsClient.ReadCodingBlock(const Name: string): TOBDCodingValues; end; end; +//------------------------------------------------------------------------------ +// WRITE CODING BLOCK +//------------------------------------------------------------------------------ procedure TOBDUdsClient.WriteCodingBlock(const Name: string; const Values: TOBDCodingValues); var @@ -1192,7 +1387,8 @@ procedure TOBDUdsClient.WriteCodingBlock(const Name: string; begin Width := FieldBitWidth(Field); // Validate min/max if specified. - var V: Int64 := Values.GetInt(Field.Name); + var + V: Int64 := Values.GetInt(Field.Name); // Always validate. The JSON loader populates absent min/max // with Low(Int64)/High(Int64), so unbounded fields are // no-ops while explicit min=max=0 (e.g. a fixed bit) is @@ -1219,6 +1415,9 @@ procedure TOBDUdsClient.WriteCodingBlock(const Name: string; end; end; +//------------------------------------------------------------------------------ +// RUN ACTUATOR TEST +//------------------------------------------------------------------------------ function TOBDUdsClient.RunActuatorTest(const Name: string; AcknowledgeSafetyWarning: Boolean): TOBDActuatorResult; var @@ -1262,6 +1461,9 @@ function TOBDUdsClient.RunActuatorTest(const Name: string; end; end; +//------------------------------------------------------------------------------ +// READ DTCS +//------------------------------------------------------------------------------ function TOBDUdsClient.ReadDtcs(StatusMask: Byte): TArray; var Req, Resp: TBytes; @@ -1291,6 +1493,9 @@ function TOBDUdsClient.ReadDtcs(StatusMask: Byte): TArray; end; end; +//------------------------------------------------------------------------------ +// STREAM LIVE PIDS +//------------------------------------------------------------------------------ function TOBDUdsClient.StreamLivePIDs(const Names: array of string; const OnSample: TOBDLiveSampleEvent; PollIntervalMs: Cardinal): IOBDStreamHandle; @@ -1328,6 +1533,9 @@ function TOBDUdsClient.StreamLivePIDs(const Names: array of string; Result := TStreamHandle.Create(Thread); end; +//------------------------------------------------------------------------------ +// CREATE UDS CLIENT +//------------------------------------------------------------------------------ function CreateUdsClient: IOBDUdsClient; begin Result := TOBDUdsClient.Create; diff --git a/src/Services/OBD.OEM.VW.pas b/src/Services/OBD.OEM.VW.pas index d238a7f0..5d092f16 100644 --- a/src/Services/OBD.OEM.VW.pas +++ b/src/Services/OBD.OEM.VW.pas @@ -20,12 +20,14 @@ interface OBD.OEM.DTC; type - /// VAG-specific session negotiator. Adds the - /// AT SH <ECU> + AT CRA <ECU+8> handshake - /// before the UDS request because VCDS and ODIS have always set - /// both, and some MQB-platform ECUs reject the session-control - /// request if CRA is wrong. Tester-present interval is 2000 ms - /// (matches ODIS service mode 2). + /// + /// VAG-specific session negotiator. Adds the + /// AT SH <ECU> + AT CRA <ECU+8> handshake + /// before the UDS request because VCDS and ODIS have always set + /// both, and some MQB-platform ECUs reject the session-control + /// request if CRA is wrong. Tester-present interval is 2000 ms + /// (matches ODIS service mode 2). + /// TOBDVWSessionNegotiator = class(TOBDStandardSessionNegotiator) public function BeginSessionPlan(SessionType: TOBDSessionType; @@ -36,13 +38,18 @@ TOBDVWSessionNegotiator = class(TOBDStandardSessionNegotiator) TOBDOEMExtensionVW = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -60,6 +67,9 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// BEGIN SESSION PLAN +//------------------------------------------------------------------------------ function TOBDVWSessionNegotiator.BeginSessionPlan( SessionType: TOBDSessionType; const ECUAddress: Word): TOBDSessionPlan; @@ -96,16 +106,25 @@ function TOBDVWSessionNegotiator.BeginSessionPlan( Result.TesterPresentRequest := TBytes.Create($3E, $80); end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDVWSessionNegotiator.DisplayName: string; begin Result := 'VAG (ODIS / VCDS)'; end; +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionVW.CreateSessionNegotiator: IOBDSessionNegotiator; begin Result := TOBDVWSessionNegotiator.Create; end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionVW.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin @@ -116,6 +135,9 @@ procedure TOBDOEMExtensionVW.SeedDefaultSeedKeyAlgorithms( Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionVW.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -123,12 +145,27 @@ procedure TOBDOEMExtensionVW.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionVW.DtcCatalogFileName: string; -begin Result := 'dtc-vw.json'; end; +begin + Result := 'dtc-vw.json'; +end; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionVW.ManufacturerKey: string; begin Result := 'VAG'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionVW.DisplayName: string; begin Result := 'Volkswagen Audi Group'; end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionVW.ApplicableToVIN(const VIN: string): Boolean; begin // v3.31 — JSON-only. The applicable_wmis list lives in vw.json, @@ -136,9 +173,14 @@ function TOBDOEMExtensionVW.ApplicableToVIN(const VIN: string): Boolean; Result := VINMatchesCatalog('vw.json', VIN); end; +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionVW.BuildCatalog(var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + Routines: TArray; + var + ECUs: TArray); begin // v3.31 — JSON-only. No hardcoded ECU / DID / Routine arrays in // Pascal. The vw.json catalog is the sole source of truth so @@ -148,12 +190,20 @@ procedure TOBDOEMExtensionVW.BuildCatalog(var DIDs: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin // v3.31 — coding blocks, adaptations, actuator tests, live PIDs // and DTC extended-data records all live in vw.json. @@ -161,6 +211,9 @@ procedure TOBDOEMExtensionVW.BuildExtendedCatalog( CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionVW.DecodeDID(const DID: Word; const Payload: TBytes): string; var diff --git a/src/Services/OBD.OEM.Volvo.pas b/src/Services/OBD.OEM.Volvo.pas index 932b738e..13ed75f4 100644 --- a/src/Services/OBD.OEM.Volvo.pas +++ b/src/Services/OBD.OEM.Volvo.pas @@ -24,9 +24,11 @@ interface System.SysUtils, OBD.OEM, OBD.OEM.Session, OBD.OEM.SeedKey, OBD.OEM.DTC; type - /// VIDA-style negotiator. Tester present interval is - /// 5000 ms — longer than the ISO default — to match VIDA's - /// extended-session heartbeat. + /// + /// VIDA-style negotiator. Tester present interval is + /// 5000 ms — longer than the ISO default — to match VIDA's + /// extended-session heartbeat. + /// TOBDVolvoSessionNegotiator = class(TOBDStandardSessionNegotiator) public function DefaultTesterPresentMs: Cardinal; override; @@ -36,13 +38,18 @@ TOBDVolvoSessionNegotiator = class(TOBDStandardSessionNegotiator) TOBDOEMExtensionVolvo = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -60,27 +67,57 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// DEFAULT TESTER PRESENT MS +//------------------------------------------------------------------------------ function TOBDVolvoSessionNegotiator.DefaultTesterPresentMs: Cardinal; -begin Result := 5000; end; +begin + Result := 5000; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDVolvoSessionNegotiator.DisplayName: string; -begin Result := 'Volvo VIDA / DiCE'; end; +begin + Result := 'Volvo VIDA / DiCE'; +end; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionVolvo.ManufacturerKey: string; -begin Result := 'VOLVO'; end; +begin + Result := 'VOLVO'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionVolvo.DisplayName: string; -begin Result := 'Volvo Cars (Geely)'; end; +begin + Result := 'Volvo Cars (Geely)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionVolvo.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in volvo.json. Result := VINMatchesCatalog('volvo.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionVolvo.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are volvo.json // + uds-standard.json. Hardcoded entries removed. @@ -91,25 +128,45 @@ procedure TOBDOEMExtensionVolvo.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionVolvo.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('volvo.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionVolvo.CreateSessionNegotiator: IOBDSessionNegotiator; -begin Result := TOBDVolvoSessionNegotiator.Create; end; +begin + Result := TOBDVolvoSessionNegotiator.Create; +end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionVolvo.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionVolvo.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -117,9 +174,17 @@ procedure TOBDOEMExtensionVolvo.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionVolvo.DtcCatalogFileName: string; -begin Result := 'dtc-volvo.json'; end; +begin + Result := 'dtc-volvo.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionVolvo.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.VolvoTrucks.pas b/src/Services/OBD.OEM.VolvoTrucks.pas index 78efbba7..1c7f34bf 100644 --- a/src/Services/OBD.OEM.VolvoTrucks.pas +++ b/src/Services/OBD.OEM.VolvoTrucks.pas @@ -28,13 +28,18 @@ interface TOBDOEMExtensionVolvoTrucks = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; function CreateSessionNegotiator: IOBDSessionNegotiator; override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; @@ -52,21 +57,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionVolvoTrucks.ManufacturerKey: string; -begin Result := 'VOLVOTR'; end; +begin + Result := 'VOLVOTR'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionVolvoTrucks.DisplayName: string; -begin Result := 'Volvo Group (Volvo Trucks / Mack / Renault Trucks)'; end; +begin + Result := 'Volvo Group (Volvo Trucks / Mack / Renault Trucks)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionVolvoTrucks.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in volvotrucks.json. Result := VINMatchesCatalog('volvotrucks.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionVolvoTrucks.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are volvotrucks.json // + uds-standard.json. Hardcoded entries removed. @@ -77,25 +102,45 @@ procedure TOBDOEMExtensionVolvoTrucks.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionVolvoTrucks.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('volvotrucks.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionVolvoTrucks.CreateSessionNegotiator: IOBDSessionNegotiator; -begin Result := TOBDHDSessionNegotiator.Create; end; +begin + Result := TOBDHDSessionNegotiator.Create; +end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionVolvoTrucks.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionVolvoTrucks.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -103,9 +148,17 @@ procedure TOBDOEMExtensionVolvoTrucks.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog) MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionVolvoTrucks.DtcCatalogFileName: string; -begin Result := 'dtc-volvotrucks.json'; end; +begin + Result := 'dtc-volvotrucks.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionVolvoTrucks.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.Xpeng.pas b/src/Services/OBD.OEM.Xpeng.pas index d7a1e307..f7dc2d70 100644 --- a/src/Services/OBD.OEM.Xpeng.pas +++ b/src/Services/OBD.OEM.Xpeng.pas @@ -22,13 +22,18 @@ interface TOBDOEMExtensionXpeng = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); override; procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); override; procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); override; @@ -45,21 +50,41 @@ implementation uses OBD.OEM.Helpers, OBD.OEM.Catalog.Loader, OBD.OEM.DTC.Loader; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TOBDOEMExtensionXpeng.ManufacturerKey: string; -begin Result := 'XPENG'; end; +begin + Result := 'XPENG'; +end; +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionXpeng.DisplayName: string; -begin Result := 'Xpeng Motors (XPeng Inc.)'; end; +begin + Result := 'Xpeng Motors (XPeng Inc.)'; +end; +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TOBDOEMExtensionXpeng.ApplicableToVIN(const VIN: string): Boolean; begin // JSON-only: applicable_wmis lives in xpeng.json. Result := VINMatchesCatalog('xpeng.json', VIN); end; + +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionXpeng.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); begin // JSON-only — sole sources of truth are xpeng.json // + uds-standard.json. Hardcoded entries removed. @@ -70,22 +95,37 @@ procedure TOBDOEMExtensionXpeng.BuildCatalog( end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionXpeng.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin MergeExtendedCatalogJSON('xpeng.json', CodingBlocks, Adaptations, ActuatorTests, LivePIDs, DtcExtended); end; + +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionXpeng.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin Reg.RegisterAlgorithm($01, TOBDSeedKeyKWP2000TwosComplement.Create); end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionXpeng.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin inherited; @@ -93,9 +133,17 @@ procedure TOBDOEMExtensionXpeng.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); MergeDtcCatalog(DtcCatalogFileName, Cat); end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionXpeng.DtcCatalogFileName: string; -begin Result := 'dtc-xpeng.json'; end; +begin + Result := 'dtc-xpeng.json'; +end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionXpeng.DecodeDID(const DID: Word; const Payload: TBytes): string; begin diff --git a/src/Services/OBD.OEM.pas b/src/Services/OBD.OEM.pas index f26583ca..1caa06b3 100644 --- a/src/Services/OBD.OEM.pas +++ b/src/Services/OBD.OEM.pas @@ -25,22 +25,30 @@ interface OBD.OEM.Session, OBD.OEM.SeedKey, OBD.OEM.DTC; type - /// One entry in an OEM's Data Identifier (DID) catalog. + /// + /// One entry in an OEM's Data Identifier (DID) catalog. + /// TOBDOEMDataIdentifier = record DID: Word; Name: string; // human-readable ("battery_voltage") Description: string; // longer prose - /// UDS request address that owns this DID. 0 = global / - /// applies across all ECUs (the v3.3 flat-catalog default). + /// + /// UDS request address that owns this DID. 0 = global / + /// applies across all ECUs (the v3.3 flat-catalog default). + /// EcuAddress: Word; end; - /// One entry in an OEM's RoutineControl (SID $31) catalog. + /// + /// One entry in an OEM's RoutineControl (SID $31) catalog. + /// TOBDOEMRoutine = record Identifier: Word; Name: string; Description: string; - /// UDS request address that owns this routine. 0 = global. + /// + /// UDS request address that owns this routine. 0 = global. + /// EcuAddress: Word; end; @@ -73,26 +81,32 @@ TOBDOEMSubCatalog = record // v3.29 Phase A — extended catalog schema //---------------------------------------------------------------------------- - /// Decoder kinds shared between live-PID and DTC extended-data - /// records. Mirrors the JSON loader's TOBDDecoderKind so the - /// extended schema can express its own decoders without pulling the - /// JSON-layer unit into OBD.OEM. + /// + /// Decoder kinds shared between live-PID and DTC extended-data + /// records. Mirrors the JSON loader's TOBDDecoderKind so the + /// extended schema can express its own decoders without pulling the + /// JSON-layer unit into OBD.OEM. + /// TOBDOEMDecoderKind = ( dkUnknown, dkAscii, dkHex, dkUInt8, dkUInt16BE, dkUInt32BE, dkInt16BE, dkInt32BE, dkBcdDate, dkEnum, dkBitmask, dkSeconds); - /// Field-type tag inside a writeable coding block. Sub-byte - /// fields use cfkBit (one bit) or cfkEnum / - /// cfkBitmask with BitWidth. + /// + /// Field-type tag inside a writeable coding block. Sub-byte + /// fields use cfkBit (one bit) or cfkEnum / + /// cfkBitmask with BitWidth. + /// TOBDCodingFieldKind = ( cfkUnknown, cfkBit, cfkUInt8, cfkUInt16BE, cfkUInt32BE, cfkInt16BE, cfkInt32BE, cfkAscii, cfkEnum, cfkBitmask); - /// One field inside a writeable coding block. ByteOffset - /// is the byte position from the start of the block payload; - /// BitOffset + BitWidth are used for sub-byte fields. UI - /// renders bit/bool fields as checkboxes, enum as combo, numeric as - /// spinner, ASCII as text input. + /// + /// One field inside a writeable coding block. ByteOffset + /// is the byte position from the start of the block payload; + /// BitOffset + BitWidth are used for sub-byte fields. UI + /// renders bit/bool fields as checkboxes, enum as combo, numeric as + /// spinner, ASCII as text input. + /// TOBDCodingField = record Name: string; Label_: string; // human-readable for UI @@ -108,10 +122,12 @@ TOBDCodingField = record EnumValues: TArray>; end; - /// Writeable DID with a known bit-field structure. Coding - /// tools render this as a form: read the current payload, surface the - /// fields, capture edits, write the modified payload back via - /// 2E DID-hi DID-lo …. + /// + /// Writeable DID with a known bit-field structure. Coding + /// tools render this as a form: read the current payload, surface the + /// fields, capture edits, write the modified payload back via + /// 2E DID-hi DID-lo …. + /// TOBDOEMCodingBlock = record DataIdentifier: Word; Name: string; @@ -125,10 +141,12 @@ TOBDOEMCodingBlock = record adkUnknown, adkUInt8, adkUInt16BE, adkUInt32BE, adkInt16BE, adkInt32BE, adkEnum); - /// One numbered adaptation channel (VAG-style). Read with - /// SID 0x22, write with SID 0x2E. MinValue / MaxValue / - /// DefaultValue let a coding tool clamp inputs and offer a - /// "reset to factory" affordance. + /// + /// One numbered adaptation channel (VAG-style). Read with + /// SID 0x22, write with SID 0x2E. MinValue / MaxValue / + /// DefaultValue let a coding tool clamp inputs and offer a + /// "reset to factory" affordance. + /// TOBDOEMAdaptation = record Channel: Word; Name: string; @@ -145,10 +163,12 @@ TOBDOEMAdaptation = record TOBDActuatorResponseKind = ( arkNone, arkBoolean, arkUInt8, arkUInt16BE, arkAscii); - /// Forced-output actuation step ("cycle the cooling fan", - /// "fire injector 3 once"). Most OEMs bind these to RoutineControl - /// (SID 0x31) — Identifier is then the RID. SafetyWarning - /// surfaces in the UI before the tool fires the actuation. + /// + /// Forced-output actuation step ("cycle the cooling fan", + /// "fire injector 3 once"). Most OEMs bind these to RoutineControl + /// (SID 0x31) — Identifier is then the RID. SafetyWarning + /// surfaces in the UI before the tool fires the actuation. + /// TOBDOEMActuatorTest = record Identifier: Word; Name: string; @@ -162,11 +182,13 @@ TOBDOEMActuatorTest = record TOBDLivePIDMode = (lpmUnknown, lpmService01, lpmService22); - /// One streamable PID. Service01 PIDs follow J1979 / - /// ISO 15031-5 framing (01 PID); Service22 PIDs are - /// 16-bit OEM PIDs (22 PID-hi PID-lo) typical for OBD-II - /// extended modes. FrameOffset is the byte offset into the - /// response payload at which this signal starts. + /// + /// One streamable PID. Service01 PIDs follow J1979 / + /// ISO 15031-5 framing (01 PID); Service22 PIDs are + /// 16-bit OEM PIDs (22 PID-hi PID-lo) typical for OBD-II + /// extended modes. FrameOffset is the byte offset into the + /// response payload at which this signal starts. + /// TOBDOEMLivePID = record Mode: TOBDLivePIDMode; PID: Word; @@ -185,10 +207,12 @@ TOBDOEMLivePID = record xdkMilesSinceCleared, xdkFreezeFrameTemplate, xdkOemStatusByte, xdkEnvironmentalData); - /// One extended-data record attached to a DTC. UDS service - /// 0x19 sub-function 0x06 retrieves these on demand. The catalog - /// describes the layout so a tool can render the record after - /// reading it. + /// + /// One extended-data record attached to a DTC. UDS service + /// 0x19 sub-function 0x06 retrieves these on demand. The catalog + /// describes the layout so a tool can render the record after + /// reading it. + /// TOBDDtcExtendedDataRecord = record DtcCode: string; RecordNumber: Byte; // sub-record number for SID 19 06 @@ -200,11 +224,13 @@ TOBDDtcExtendedDataRecord = record Unit_: string; end; - /// Companion to IOBDOEMExtension. Adds accessors for - /// the v3.29 extended catalog: coding blocks, adaptations, actuator - /// tests, live PIDs, DTC extended-data records. Implemented by - /// TOBDOEMExtensionBase on every OEM extension; tooling - /// queries via Supports(Ext, IOBDOEMExtensionV2, V2). + /// + /// Companion to IOBDOEMExtension. Adds accessors for + /// the v3.29 extended catalog: coding blocks, adaptations, actuator + /// tests, live PIDs, DTC extended-data records. Implemented by + /// TOBDOEMExtensionBase on every OEM extension; tooling + /// queries via Supports(Ext, IOBDOEMExtensionV2, V2). + /// IOBDOEMExtensionV2 = interface ['{2C8B6F0E-7A3D-4C5E-8B9A-1F4E6D2A8C90}'] function CodingBlocks: TArray; @@ -224,9 +250,13 @@ TOBDDtcExtendedDataRecord = record /// IOBDOEMExtension = interface ['{A2C5F4C6-4D71-4E8F-9C5B-3E4A8B1D6C2F}'] - /// Manufacturer key — short ASCII tag, e.g. "VAG", "BMW". + /// + /// Manufacturer key — short ASCII tag, e.g. "VAG", "BMW". + /// function ManufacturerKey: string; - /// Display-friendly name, e.g. "Volkswagen Audi Group". + /// + /// Display-friendly name, e.g. "Volkswagen Audi Group". + /// function DisplayName: string; /// @@ -248,9 +278,13 @@ TOBDDtcExtendedDataRecord = record /// function ApplicableToECUSupplier(const SupplierID: string): Boolean; - /// Catalog of DIDs the extension knows how to interpret. + /// + /// Catalog of DIDs the extension knows how to interpret. + /// function DataIdentifiers: TArray; - /// Catalog of RoutineControl identifiers. + /// + /// Catalog of RoutineControl identifiers. + /// function Routines: TArray; /// @@ -260,39 +294,53 @@ TOBDDtcExtendedDataRecord = record /// function DecodeDID(const DID: Word; const Payload: TBytes): string; - /// Lookup helpers — return False if the entry isn't catalogued. + /// + /// Lookup helpers — return False if the entry isn't catalogued. + /// function FindDID(const DID: Word; out Entry: TOBDOEMDataIdentifier): Boolean; function FindRoutine(const Id: Word; out Entry: TOBDOEMRoutine): Boolean; - /// The ECUs this manufacturer's diagnostics target. May be - /// empty for OEMs that haven't been ECU-mapped yet — callers then - /// fall back to the flat catalog. + /// + /// The ECUs this manufacturer's diagnostics target. May be + /// empty for OEMs that haven't been ECU-mapped yet — callers then + /// fall back to the flat catalog. + /// function ECUs: TArray; - /// Catalog filtered to one ECU. Includes globally-scoped - /// entries (EcuAddress=0 in the flat catalog) plus entries that - /// match Address exactly. + /// + /// Catalog filtered to one ECU. Includes globally-scoped + /// entries (EcuAddress=0 in the flat catalog) plus entries that + /// match Address exactly. + /// function CatalogForECU(const Address: Word): TOBDOEMSubCatalog; - /// The session-negotiation choreography this OEM expects. - /// The default is TOBDStandardSessionNegotiator (plain ISO - /// 14229); OEMs that diverge return their own implementation. + /// + /// The session-negotiation choreography this OEM expects. + /// The default is TOBDStandardSessionNegotiator (plain ISO + /// 14229); OEMs that diverge return their own implementation. + /// function SessionNegotiator: IOBDSessionNegotiator; - /// Per-OEM seed-key algorithm registry keyed by - /// SecurityAccess level (the odd byte in 27 LL). Production - /// users replace the default starter algorithm with their NDA- - /// protected real one via RegisterAlgorithm. + /// + /// Per-OEM seed-key algorithm registry keyed by + /// SecurityAccess level (the odd byte in 27 LL). Production + /// users replace the default starter algorithm with their NDA- + /// protected real one via RegisterAlgorithm. + /// function SeedKeyRegistry: TOBDSeedKeyRegistry; - /// Per-OEM DTC catalog. The standard SAE J2012 / ISO - /// 15031-6 P0xxx range is loaded as a baseline overlay; OEM - /// units add their P1xxx / B / C / U entries. Returns the same - /// catalog instance across calls — callers can register - /// additional entries at runtime. + /// + /// Per-OEM DTC catalog. The standard SAE J2012 / ISO + /// 15031-6 P0xxx range is loaded as a baseline overlay; OEM + /// units add their P1xxx / B / C / U entries. Returns the same + /// catalog instance across calls — callers can register + /// additional entries at runtime. + /// function DtcCatalog: TOBDDtcCatalog; - /// Convenience: look up Code in DtcCatalog. + /// + /// Convenience: look up Code in DtcCatalog. + /// function DescribeDTC(const Code: string; out Entry: TOBDDtcCatalogEntry): Boolean; end; @@ -313,11 +361,13 @@ TOBDOEMRegistry = class class procedure UnregisterExtension(const Ext: IOBDOEMExtension); static; class function FindByVIN(const VIN: string): IOBDOEMExtension; static; class function FindByKey(const ManufacturerKey: string): IOBDOEMExtension; static; - /// Probe every extension's ApplicableToECUSupplier - /// (J1939 PGN 65259 'Make' / ISO 14229 DID 0xF18A). Returns the - /// first claimant or nil. Use this when the chassis VIN doesn't - /// identify the ECU manufacturer (engine OEMs in mixed fleets, - /// supplier modules in OEM chassis). + /// + /// Probe every extension's ApplicableToECUSupplier + /// (J1939 PGN 65259 'Make' / ISO 14229 DID 0xF18A). Returns the + /// first claimant or nil. Use this when the chassis VIN doesn't + /// identify the ECU manufacturer (engine OEMs in mixed fleets, + /// supplier modules in OEM chassis). + /// class function FindByECUSupplier(const SupplierID: string): IOBDOEMExtension; static; class function All: TArray; static; class function Count: Integer; static; @@ -361,18 +411,25 @@ TOBDOEMExtensionBase = class(TInterfacedObject, IOBDOEMExtension, IOBDOEMExten /// untouched (default nil). /// procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); virtual; abstract; - /// v3.29 Phase A — override-point for the extended - /// catalog. Default is a no-op so the 46 v3.28-era OEM extensions - /// continue to compile unchanged. Subclasses that opt in populate - /// the arrays from JSON via MergeExtendedCatalogJSON in - /// OBD.OEM.Catalog.Loader. + /// + /// v3.29 Phase A — override-point for the extended + /// catalog. Default is a no-op so the 46 v3.28-era OEM extensions + /// continue to compile unchanged. Subclasses that opt in populate + /// the arrays from JSON via MergeExtendedCatalogJSON in + /// OBD.OEM.Catalog.Loader. + /// procedure BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; var DtcExtended: TArray); virtual; public constructor Create; @@ -400,26 +457,36 @@ TOBDOEMExtensionBase = class(TInterfacedObject, IOBDOEMExtension, IOBDOEMExten function LivePIDs: TArray; virtual; function DtcExtendedDataRecords: TArray; virtual; protected - /// Override-point: subclasses return their OEM-specific - /// negotiator. Default returns a fresh - /// TOBDStandardSessionNegotiator. + /// + /// Override-point: subclasses return their OEM-specific + /// negotiator. Default returns a fresh + /// TOBDStandardSessionNegotiator. + /// function CreateSessionNegotiator: IOBDSessionNegotiator; virtual; - /// Override-point: subclasses populate Reg with - /// their default starter algorithms. Called once on first access - /// to SeedKeyRegistry. Default is a no-op (empty registry). + /// + /// Override-point: subclasses populate Reg with + /// their default starter algorithms. Called once on first access + /// to SeedKeyRegistry. Default is a no-op (empty registry). + /// procedure SeedDefaultSeedKeyAlgorithms(Reg: TOBDSeedKeyRegistry); virtual; - /// Override-point: subclasses load their per-OEM DTC - /// catalog into Cat. Default loads the universal SAE J2012 - /// / ISO 15031-6 baseline (catalogs/dtc-iso-15031.json); - /// OEM units chain to inherited and append their own. + /// + /// Override-point: subclasses load their per-OEM DTC + /// catalog into Cat. Default loads the universal SAE J2012 + /// / ISO 15031-6 baseline (catalogs/dtc-iso-15031.json); + /// OEM units chain to inherited and append their own. + /// procedure SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); virtual; - /// The catalog filename loaded by the default - /// SeedDefaultDtcCatalog; OEMs override to point at their - /// own per-OEM file (e.g. 'dtc-vw.json'). + /// + /// The catalog filename loaded by the default + /// SeedDefaultDtcCatalog; OEMs override to point at their + /// own per-OEM file (e.g. 'dtc-vw.json'). + /// function DtcCatalogFileName: string; virtual; end; -/// Builder helper used by JSON catalog readers. +/// +/// Builder helper used by JSON catalog readers. +/// function MakeOEMECU(const Address: Word; const Name, CommonName: string): TOBDOEMECU; implementation @@ -427,6 +494,10 @@ implementation //============================================================================== // TOBDOEMRegistry //============================================================================== + +//------------------------------------------------------------------------------ +// ENSURE INITIALIZED +//------------------------------------------------------------------------------ class procedure TOBDOEMRegistry.EnsureInitialized; begin if FLock = nil then @@ -435,6 +506,9 @@ class procedure TOBDOEMRegistry.EnsureInitialized; FExtensions := TList.Create; end; +//------------------------------------------------------------------------------ +// REGISTER EXTENSION +//------------------------------------------------------------------------------ class procedure TOBDOEMRegistry.RegisterExtension(const Ext: IOBDOEMExtension); begin if not Assigned(Ext) then Exit; @@ -448,6 +522,9 @@ class procedure TOBDOEMRegistry.RegisterExtension(const Ext: IOBDOEMExtension); end; end; +//------------------------------------------------------------------------------ +// UNREGISTER EXTENSION +//------------------------------------------------------------------------------ class procedure TOBDOEMRegistry.UnregisterExtension(const Ext: IOBDOEMExtension); begin if not Assigned(Ext) then Exit; @@ -456,6 +533,9 @@ class procedure TOBDOEMRegistry.UnregisterExtension(const Ext: IOBDOEMExtension) try FExtensions.Remove(Ext); finally FLock.Leave; end; end; +//------------------------------------------------------------------------------ +// FIND BY VIN +//------------------------------------------------------------------------------ class function TOBDOEMRegistry.FindByVIN(const VIN: string): IOBDOEMExtension; var Snapshot: TArray; @@ -471,6 +551,9 @@ class function TOBDOEMRegistry.FindByVIN(const VIN: string): IOBDOEMExtension; if Ext.ApplicableToVIN(VIN) then Exit(Ext); end; +//------------------------------------------------------------------------------ +// FIND BY KEY +//------------------------------------------------------------------------------ class function TOBDOEMRegistry.FindByKey( const ManufacturerKey: string): IOBDOEMExtension; var @@ -485,6 +568,9 @@ class function TOBDOEMRegistry.FindByKey( if SameText(Ext.ManufacturerKey, ManufacturerKey) then Exit(Ext); end; +//------------------------------------------------------------------------------ +// FIND BY ECUSUPPLIER +//------------------------------------------------------------------------------ class function TOBDOEMRegistry.FindByECUSupplier( const SupplierID: string): IOBDOEMExtension; var @@ -500,6 +586,9 @@ class function TOBDOEMRegistry.FindByECUSupplier( if Ext.ApplicableToECUSupplier(SupplierID) then Exit(Ext); end; +//------------------------------------------------------------------------------ +// ALL +//------------------------------------------------------------------------------ class function TOBDOEMRegistry.All: TArray; begin EnsureInitialized; @@ -507,6 +596,9 @@ class function TOBDOEMRegistry.All: TArray; try Result := FExtensions.ToArray; finally FLock.Leave; end; end; +//------------------------------------------------------------------------------ +// COUNT +//------------------------------------------------------------------------------ class function TOBDOEMRegistry.Count: Integer; begin EnsureInitialized; @@ -514,6 +606,9 @@ class function TOBDOEMRegistry.Count: Integer; try Result := FExtensions.Count; finally FLock.Leave; end; end; +//------------------------------------------------------------------------------ +// CLEAR +//------------------------------------------------------------------------------ class procedure TOBDOEMRegistry.Clear; begin EnsureInitialized; @@ -524,6 +619,10 @@ class procedure TOBDOEMRegistry.Clear; //============================================================================== // TOBDOEMExtensionBase //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDOEMExtensionBase.Create; begin inherited Create; @@ -533,6 +632,9 @@ constructor TOBDOEMExtensionBase.Create; FDtcLock := TCriticalSection.Create; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDOEMExtensionBase.Destroy; begin FSessionNegotiator := nil; @@ -545,6 +647,9 @@ destructor TOBDOEMExtensionBase.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// SEED DEFAULT SEED KEY ALGORITHMS +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBase.SeedDefaultSeedKeyAlgorithms( Reg: TOBDSeedKeyRegistry); begin @@ -552,6 +657,9 @@ procedure TOBDOEMExtensionBase.SeedDefaultSeedKeyAlgorithms( // starter set; production users call RegisterAlgorithm after. end; +//------------------------------------------------------------------------------ +// ENSURE SEED KEY REGISTRY +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBase.EnsureSeedKeyRegistry; begin FSeedKeyLock.Enter; @@ -566,12 +674,18 @@ procedure TOBDOEMExtensionBase.EnsureSeedKeyRegistry; end; end; +//------------------------------------------------------------------------------ +// SEED KEY REGISTRY +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.SeedKeyRegistry: TOBDSeedKeyRegistry; begin EnsureSeedKeyRegistry; Result := FSeedKeyRegistry; end; +//------------------------------------------------------------------------------ +// DTC CATALOG FILE NAME +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.DtcCatalogFileName: string; begin // Override in OEM subclasses to point at the per-OEM DTC catalog; @@ -579,6 +693,9 @@ function TOBDOEMExtensionBase.DtcCatalogFileName: string; Result := ''; end; +//------------------------------------------------------------------------------ +// SEED DEFAULT DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBase.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); begin // Subclasses chain to inherited and append their own entries; the @@ -587,6 +704,9 @@ procedure TOBDOEMExtensionBase.SeedDefaultDtcCatalog(Cat: TOBDDtcCatalog); // dependency-free. end; +//------------------------------------------------------------------------------ +// ENSURE DTC CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBase.EnsureDtcCatalog; begin FDtcLock.Enter; @@ -601,23 +721,35 @@ procedure TOBDOEMExtensionBase.EnsureDtcCatalog; end; end; +//------------------------------------------------------------------------------ +// DTC CATALOG +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.DtcCatalog: TOBDDtcCatalog; begin EnsureDtcCatalog; Result := FDtcCatalog; end; +//------------------------------------------------------------------------------ +// DESCRIBE DTC +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.DescribeDTC(const Code: string; out Entry: TOBDDtcCatalogEntry): Boolean; begin Result := DtcCatalog.FindByCode(Code, Entry); end; +//------------------------------------------------------------------------------ +// CREATE SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.CreateSessionNegotiator: IOBDSessionNegotiator; begin Result := TOBDStandardSessionNegotiator.Create; end; +//------------------------------------------------------------------------------ +// SESSION NEGOTIATOR +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.SessionNegotiator: IOBDSessionNegotiator; begin // Lazy + cached. Negotiators are immutable and cheap, but caching @@ -633,6 +765,9 @@ function TOBDOEMExtensionBase.SessionNegotiator: IOBDSessionNegotiator; end; end; +//------------------------------------------------------------------------------ +// ENSURE CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBase.EnsureCatalog; begin FCatalogLock.Enter; @@ -647,54 +782,83 @@ procedure TOBDOEMExtensionBase.EnsureCatalog; end; end; +//------------------------------------------------------------------------------ +// BUILD EXTENDED CATALOG +//------------------------------------------------------------------------------ procedure TOBDOEMExtensionBase.BuildExtendedCatalog( - var CodingBlocks: TArray; - var Adaptations: TArray; - var ActuatorTests: TArray; - var LivePIDs: TArray; - var DtcExtended: TArray); + var + CodingBlocks: TArray; + var + Adaptations: TArray; + var + ActuatorTests: TArray; + var + LivePIDs: TArray; + var + DtcExtended: TArray); begin // Default: no-op. Subclasses (v3.29 Phase B onward) populate by // calling MergeExtendedCatalogJSON('xxx.json', ...) — same shape as // BuildCatalog's MergeCatalogJSON pattern. end; +//------------------------------------------------------------------------------ +// CODING BLOCKS +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.CodingBlocks: TArray; begin EnsureCatalog; Result := FCodingBlocks; end; +//------------------------------------------------------------------------------ +// ADAPTATIONS +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.Adaptations: TArray; begin EnsureCatalog; Result := FAdaptations; end; +//------------------------------------------------------------------------------ +// ACTUATOR TESTS +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.ActuatorTests: TArray; begin EnsureCatalog; Result := FActuatorTests; end; +//------------------------------------------------------------------------------ +// LIVE PIDS +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.LivePIDs: TArray; begin EnsureCatalog; Result := FLivePIDs; end; +//------------------------------------------------------------------------------ +// DTC EXTENDED DATA RECORDS +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.DtcExtendedDataRecords: TArray; begin EnsureCatalog; Result := FDtcExtended; end; +//------------------------------------------------------------------------------ +// ECUS +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.ECUs: TArray; begin EnsureCatalog; Result := FECUs; end; +//------------------------------------------------------------------------------ +// CATALOG FOR ECU +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.CatalogForECU( const Address: Word): TOBDOEMSubCatalog; var @@ -715,6 +879,9 @@ function TOBDOEMExtensionBase.CatalogForECU( Result.Routines := Result.Routines + [R]; end; +//------------------------------------------------------------------------------ +// MAKE OEMECU +//------------------------------------------------------------------------------ function MakeOEMECU(const Address: Word; const Name, CommonName: string): TOBDOEMECU; begin Result.Address := Address; @@ -722,6 +889,9 @@ function MakeOEMECU(const Address: Word; const Name, CommonName: string): TOBDOE Result.CommonName := CommonName; end; +//------------------------------------------------------------------------------ +// APPLICABLE TO ECUSUPPLIER +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.ApplicableToECUSupplier( const SupplierID: string): Boolean; begin @@ -732,18 +902,27 @@ function TOBDOEMExtensionBase.ApplicableToECUSupplier( Result := False; end; +//------------------------------------------------------------------------------ +// DATA IDENTIFIERS +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.DataIdentifiers: TArray; begin EnsureCatalog; Result := FDIDs; end; +//------------------------------------------------------------------------------ +// ROUTINES +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.Routines: TArray; begin EnsureCatalog; Result := FRoutines; end; +//------------------------------------------------------------------------------ +// DECODE DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.DecodeDID(const DID: Word; const Payload: TBytes): string; var @@ -769,6 +948,9 @@ function TOBDOEMExtensionBase.DecodeDID(const DID: Word; end; end; +//------------------------------------------------------------------------------ +// FIND DID +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.FindDID(const DID: Word; out Entry: TOBDOEMDataIdentifier): Boolean; var @@ -784,6 +966,9 @@ function TOBDOEMExtensionBase.FindDID(const DID: Word; Result := False; end; +//------------------------------------------------------------------------------ +// FIND ROUTINE +//------------------------------------------------------------------------------ function TOBDOEMExtensionBase.FindRoutine(const Id: Word; out Entry: TOBDOEMRoutine): Boolean; var diff --git a/src/Services/OBD.ReadinessMonitor.pas b/src/Services/OBD.ReadinessMonitor.pas index 2882acd1..e630247b 100644 --- a/src/Services/OBD.ReadinessMonitor.pas +++ b/src/Services/OBD.ReadinessMonitor.pas @@ -29,10 +29,12 @@ interface type EOBDReadinessError = class(Exception); - /// SAE J1979 / ISO 15031-5 monitor types. The - /// continuous three are common to all engines; the - /// non-continuous set diverges between spark-ignition (gasoline / - /// flex / hybrid) and compression-ignition (diesel). + /// + /// SAE J1979 / ISO 15031-5 monitor types. The + /// continuous three are common to all engines; the + /// non-continuous set diverges between spark-ignition (gasoline / + /// flex / hybrid) and compression-ignition (diesel). + /// TOBDMonitorKind = ( monMisfire, monFuelSystem, @@ -62,35 +64,53 @@ TOBDMonitorStatus = record State: TOBDMonitorState; end; - /// Decoded result of PID 0x01. + /// + /// Decoded result of PID 0x01. + /// TOBDReadinessReport = record - /// True if the malfunction indicator lamp is illuminated. + /// + /// True if the malfunction indicator lamp is illuminated. + /// MILOn: Boolean; - /// Number of confirmed DTCs the ECU is currently holding. + /// + /// Number of confirmed DTCs the ECU is currently holding. + /// DtcCount: Byte; - /// True for compression-ignition (diesel) layout; false - /// for spark-ignition. Determined from byte B bit 3. + /// + /// True for compression-ignition (diesel) layout; false + /// for spark-ignition. Determined from byte B bit 3. + /// IsDiesel: Boolean; - /// Per-monitor readiness state. Always 11 entries for - /// SI engines (3 continuous + 8 non-continuous) and 11 entries - /// for CI engines (3 continuous + 8 non-continuous diesel set). - /// Monitors that the ECU doesn't support report - /// msNotSupported. + /// + /// Per-monitor readiness state. Always 11 entries for + /// SI engines (3 continuous + 8 non-continuous) and 11 entries + /// for CI engines (3 continuous + 8 non-continuous diesel set). + /// Monitors that the ECU doesn't support report + /// msNotSupported. + /// Monitors: TArray; end; -/// Decode the 4-byte PID 0x01 payload. Throws -/// EOBDReadinessError on a payload shorter than 4 bytes. +/// +/// Decode the 4-byte PID 0x01 payload. Throws +/// EOBDReadinessError on a payload shorter than 4 bytes. +/// function DecodeReadinessReport(const Bytes: TBytes): TOBDReadinessReport; -/// Render a one-line summary suitable for logs / status -/// bars: "MIL off, 0 DTCs, 5/8 readiness monitors complete". +/// +/// Render a one-line summary suitable for logs / status +/// bars: "MIL off, 0 DTCs, 5/8 readiness monitors complete". +/// function FormatReadinessSummary(const Report: TOBDReadinessReport): string; -/// Human-readable name for a monitor kind. +/// +/// Human-readable name for a monitor kind. +/// function MonitorKindName(const Kind: TOBDMonitorKind): string; -/// Human-readable name for a monitor state. +/// +/// Human-readable name for a monitor state. +/// function MonitorStateName(const State: TOBDMonitorState): string; implementation @@ -119,6 +139,9 @@ implementation BIT_PM_FILTER = 6; BIT_EGR_VVT_DIESEL = 7; +//------------------------------------------------------------------------------ +// MONITOR KIND NAME +//------------------------------------------------------------------------------ function MonitorKindName(const Kind: TOBDMonitorKind): string; begin case Kind of @@ -144,6 +167,9 @@ function MonitorKindName(const Kind: TOBDMonitorKind): string; end; end; +//------------------------------------------------------------------------------ +// MONITOR STATE NAME +//------------------------------------------------------------------------------ function MonitorStateName(const State: TOBDMonitorState): string; begin case State of @@ -155,6 +181,9 @@ function MonitorStateName(const State: TOBDMonitorState): string; end; end; +//------------------------------------------------------------------------------ +// STATE FROM BITS +//------------------------------------------------------------------------------ function StateFromBits(const SupportSet, NotCompleteSet: Boolean): TOBDMonitorState; begin if not SupportSet then Exit(msNotSupported); @@ -163,6 +192,9 @@ function StateFromBits(const SupportSet, NotCompleteSet: Boolean): TOBDMonitorSt if NotCompleteSet then Result := msNotReady else Result := msReady; end; +//------------------------------------------------------------------------------ +// ADD MONITOR +//------------------------------------------------------------------------------ procedure AddMonitor(var Arr: TArray; const Kind: TOBDMonitorKind; const State: TOBDMonitorState); var @@ -174,6 +206,9 @@ procedure AddMonitor(var Arr: TArray; Arr[N].State := State; end; +//------------------------------------------------------------------------------ +// DECODE READINESS REPORT +//------------------------------------------------------------------------------ function DecodeReadinessReport(const Bytes: TBytes): TOBDReadinessReport; var A, B, C, D: Byte; @@ -258,6 +293,9 @@ function DecodeReadinessReport(const Bytes: TBytes): TOBDReadinessReport; i := 0; if i = 0 then ; // suppress unused-var warning end; +//------------------------------------------------------------------------------ +// FORMAT READINESS SUMMARY +//------------------------------------------------------------------------------ function FormatReadinessSummary(const Report: TOBDReadinessReport): string; var M: TOBDMonitorStatus; diff --git a/src/Services/OBD.Service.Recorder.pas b/src/Services/OBD.Service.Recorder.pas index 22cff0b8..1e44c6f3 100644 --- a/src/Services/OBD.Service.Recorder.pas +++ b/src/Services/OBD.Service.Recorder.pas @@ -74,9 +74,13 @@ TOBDReplayer = class public constructor Create; - /// Load entries from a `.obdlog` file. + /// + /// Load entries from a `.obdlog` file. + /// procedure LoadFromFile(const FilePath: string); - /// Load from memory. + /// + /// Load from memory. + /// procedure LoadEntries(const AEntries: TArray); /// @@ -89,9 +93,13 @@ TOBDReplayer = class function Count: Integer; function Entries: TArray; - /// Replay speed multiplier. 1.0 = real time. 0 = no delays. + /// + /// Replay speed multiplier. 1.0 = real time. 0 = no delays. + /// property Speed: Single read FSpeed write FSpeed; - /// Fired for each entry as the replay progresses. + /// + /// Fired for each entry as the replay progresses. + /// property OnEntry: TOBDReplayEvent read FOnEntry write FOnEntry; end; @@ -100,6 +108,9 @@ implementation uses WinApi.Windows; +//------------------------------------------------------------------------------ +// DIRECTION LETTER +//------------------------------------------------------------------------------ function DirectionLetter(D: TOBDRecorderDirection): Char; begin case D of @@ -111,6 +122,9 @@ function DirectionLetter(D: TOBDRecorderDirection): Char; end; end; +//------------------------------------------------------------------------------ +// LETTER TO DIRECTION +//------------------------------------------------------------------------------ function LetterToDirection(C: Char): TOBDRecorderDirection; begin case UpCase(C) of @@ -122,6 +136,9 @@ function LetterToDirection(C: Char): TOBDRecorderDirection; end; end; +//------------------------------------------------------------------------------ +// ESCAPE TEXT +//------------------------------------------------------------------------------ function EscapeText(const S: string): string; begin // Tabs and newlines would corrupt the field-delimited line format. @@ -132,6 +149,9 @@ function EscapeText(const S: string): string; Result := StringReplace(Result, #10, '\n', [rfReplaceAll]); end; +//------------------------------------------------------------------------------ +// UNESCAPE TEXT +//------------------------------------------------------------------------------ function UnescapeText(const S: string): string; var I: Integer; @@ -170,6 +190,10 @@ function UnescapeText(const S: string): string; //============================================================================== // TOBDRecorder //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDRecorder.Create; begin inherited Create; @@ -177,6 +201,9 @@ constructor TOBDRecorder.Create; FEntries := TList.Create; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDRecorder.Destroy; begin FEntries.Free; @@ -184,6 +211,9 @@ destructor TOBDRecorder.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// START +//------------------------------------------------------------------------------ procedure TOBDRecorder.Start; begin FLock.Enter; @@ -195,6 +225,9 @@ procedure TOBDRecorder.Start; end; end; +//------------------------------------------------------------------------------ +// STOP +//------------------------------------------------------------------------------ procedure TOBDRecorder.Stop; begin FLock.Enter; @@ -205,12 +238,18 @@ procedure TOBDRecorder.Stop; end; end; +//------------------------------------------------------------------------------ +// IS RUNNING +//------------------------------------------------------------------------------ function TOBDRecorder.IsRunning: Boolean; begin FLock.Enter; try Result := FStopwatch.IsRunning; finally FLock.Leave; end; end; +//------------------------------------------------------------------------------ +// APPEND ENTRY +//------------------------------------------------------------------------------ procedure TOBDRecorder.AppendEntry(Direction: TOBDRecorderDirection; const Text: string); var @@ -227,23 +266,47 @@ procedure TOBDRecorder.AppendEntry(Direction: TOBDRecorderDirection; end; end; +//------------------------------------------------------------------------------ +// RECORD SENT +//------------------------------------------------------------------------------ procedure TOBDRecorder.RecordSent (const Text: string); begin AppendEntry(rdSent, Text); end; + +//------------------------------------------------------------------------------ +// RECORD RECEIVED +//------------------------------------------------------------------------------ procedure TOBDRecorder.RecordReceived(const Text: string); begin AppendEntry(rdReceived, Text); end; + +//------------------------------------------------------------------------------ +// RECORD INFO +//------------------------------------------------------------------------------ procedure TOBDRecorder.RecordInfo (const Text: string); begin AppendEntry(rdInfo, Text); end; + +//------------------------------------------------------------------------------ +// RECORD ERROR +//------------------------------------------------------------------------------ procedure TOBDRecorder.RecordError (const Text: string); begin AppendEntry(rdError, Text); end; +//------------------------------------------------------------------------------ +// SNAPSHOT +//------------------------------------------------------------------------------ function TOBDRecorder.Snapshot: TArray; begin FLock.Enter; try Result := FEntries.ToArray; finally FLock.Leave; end; end; +//------------------------------------------------------------------------------ +// COUNT +//------------------------------------------------------------------------------ function TOBDRecorder.Count: Integer; begin FLock.Enter; try Result := FEntries.Count; finally FLock.Leave; end; end; +//------------------------------------------------------------------------------ +// SAVE TO FILE +//------------------------------------------------------------------------------ procedure TOBDRecorder.SaveToFile(const FilePath: string); var Output: TStringList; @@ -269,12 +332,19 @@ procedure TOBDRecorder.SaveToFile(const FilePath: string); //============================================================================== // TOBDReplayer //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDReplayer.Create; begin inherited Create; FSpeed := 1.0; end; +//------------------------------------------------------------------------------ +// LOAD FROM FILE +//------------------------------------------------------------------------------ procedure TOBDReplayer.LoadFromFile(const FilePath: string); var Lines: TStringList; @@ -307,11 +377,17 @@ procedure TOBDReplayer.LoadFromFile(const FilePath: string); end; end; +//------------------------------------------------------------------------------ +// LOAD ENTRIES +//------------------------------------------------------------------------------ procedure TOBDReplayer.LoadEntries(const AEntries: TArray); begin FEntries := AEntries; end; +//------------------------------------------------------------------------------ +// RUN +//------------------------------------------------------------------------------ procedure TOBDReplayer.Run; var I: Integer; @@ -333,10 +409,20 @@ procedure TOBDReplayer.Run; end; end; +//------------------------------------------------------------------------------ +// COUNT +//------------------------------------------------------------------------------ function TOBDReplayer.Count: Integer; -begin Result := Length(FEntries); end; +begin + Result := Length(FEntries); +end; +//------------------------------------------------------------------------------ +// ENTRIES +//------------------------------------------------------------------------------ function TOBDReplayer.Entries: TArray; -begin Result := FEntries; end; +begin + Result := FEntries; +end; end. diff --git a/src/Services/OBD.Service01.pas b/src/Services/OBD.Service01.pas index f47ea286..65f65499 100644 --- a/src/Services/OBD.Service01.pas +++ b/src/Services/OBD.Service01.pas @@ -2555,7 +2555,8 @@ procedure TOBDService01.ParseResponse(const Response: TBytes); begin if Length(Data) < 2 then Exit; // - var DTCFirstChar, DTCSecondChar: AnsiChar; + var + DTCFirstChar, DTCSecondChar: AnsiChar; // Decode the first character based on the most significant nibble of the first byte case (Data[0] shr 4) of 0: DTCFirstChar := 'P'; diff --git a/src/Services/OBD.Service06.Mode06.pas b/src/Services/OBD.Service06.Mode06.pas new file mode 100644 index 00000000..47c5ca04 --- /dev/null +++ b/src/Services/OBD.Service06.Mode06.pas @@ -0,0 +1,380 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.Service06.Mode06.pas +// CONTENTS : OBD-II Service 06 on-board monitoring (ISO 15031-5) +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.Service06.Mode06; + +interface + +uses + System.SysUtils, System.Generics.Collections; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDMode06 = class(Exception); + + /// + /// One Mode 06 test record. ISO 15031-5 §6.5.1. + /// + TOBDMode06TestRecord = record + OBDMID: Byte; // On-Board Diagnostic Monitor ID + TestId: Byte; // What was measured (TID) + UnitsAndScalingId: Byte; // How to interpret the value (UCSID) + /// + /// Test value. + /// + TestValue: Word; + /// + /// Min limit. + /// + MinLimit: Word; + /// + /// Max limit. + /// + MaxLimit: Word; + /// + /// Passed test. + /// + function PassedTest: Boolean; // Min <= TestValue <= Max + /// + /// Scale factor. + /// + function ScaleFactor: Single; // multiplier from UCSID + /// + /// Unit name. + /// + function UnitName: string; // 'V', 'mA', '%', etc. + end; + + TOBDMode06Response = record + /// + /// Obdmid. + /// + OBDMID: Byte; + /// + /// Records. + /// + Records: TArray; + end; + + TOBDMode06UnitInfo = record + /// + /// Ucsid. + /// + UCSID: Byte; + /// + /// Scale. + /// + Scale: Single; + /// + /// Unit name. + /// + UnitName: string; + /// + /// Description. + /// + Description: string; + end; + +/// +/// Build the Mode 06 request: 46 OBDMID. +/// +function BuildMode06Request(OBDMID: Byte): TBytes; + +/// +/// Decode a Mode 06 response into one or more test records. +/// Each record is 9 bytes: TID UCSID Value-MSB Value-LSB Min-MSB +/// Min-LSB Max-MSB Max-LSB. Caller-side note: the leading 46 + +/// OBDMID echo (2 bytes) must be present. +/// +function ParseMode06Response(const Bytes: TBytes): TOBDMode06Response; + +/// +/// Look up a Unit-and-Scaling-ID. Returns a default +/// "Unknown UCSID" entry for anything not in the table; never raises. +/// +function FindMode06Unit(UCSID: Byte): TOBDMode06UnitInfo; + +/// +/// Look up a standardised Test ID (ISO 15031-5 Table B.2). +/// +function FindMode06TestIdName(TID: Byte): string; + +/// +/// Look up a standardised Component ID / OBDMID for the +/// well-known monitors (catalyst bank 1/2, EGR, EVAP, O2 sensors, +/// etc.) per Table B.4. +/// +function FindMode06OBDMIDName(OBDMID: Byte): string; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +uses + System.Classes, System.JSON, + OBD.Catalog.Path; + +//------------------------------------------------------------------------------ +// CONSTANTS +//------------------------------------------------------------------------------ +const + // TID(1) + UCSID(1) + TestValue(2) + MinLimit(2) + MaxLimit(2) + TEST_RECORD_BYTES = 8; + +var + GTIDs: TDictionary = nil; + GOBDMIDs: TDictionary = nil; + GUCSIDs: TDictionary = nil; + +//------------------------------------------------------------------------------ +// PARSE HEX BYTE OR ZERO +//------------------------------------------------------------------------------ +function ParseHexByteOrZero(const S: string): Integer; +var + T: string; +begin + T := S; + if T.StartsWith('0x', True) then T := '$' + T.Substring(2); + if not TryStrToInt(T, Result) then Result := 0; +end; + +//------------------------------------------------------------------------------ +// LOAD STRING MAP +//------------------------------------------------------------------------------ +procedure LoadStringMap(const FileName, KeyField: string; + Map: TDictionary); +var + Path, Raw: string; + Stream: TStringStream; + Doc: TJSONValue; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + V: Integer; +begin + Path := ResolveCatalogPath(FileName); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + V := ParseHexByteOrZero(Obj.GetValue(KeyField, '')); + if (V < 0) or (V > 255) then Continue; + Map.AddOrSetValue(Byte(V), Obj.GetValue('name', '')); + end; + finally + // Free the document + Doc.Free; + end; +end; + +//------------------------------------------------------------------------------ +// LOAD UCSIDCATALOG +//------------------------------------------------------------------------------ +procedure LoadUCSIDCatalog; +var + Path, Raw: string; + Stream: TStringStream; + Doc: TJSONValue; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + Info: TOBDMode06UnitInfo; + V: Integer; +begin + // Resolve catalog path + Path := ResolveCatalogPath('mode06-units.json'); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + V := ParseHexByteOrZero(Obj.GetValue('ucsid', '')); + if (V < 0) or (V > 255) then Continue; + Info.UCSID := Byte(V); + Info.Scale := Single(Obj.GetValue('scale', 1.0)); + Info.UnitName := Obj.GetValue('unit', ''); + Info.Description := Obj.GetValue('description', ''); + GUCSIDs.AddOrSetValue(Info.UCSID, Info); + end; + finally + // Free the document + Doc.Free; + end; +end; + +//------------------------------------------------------------------------------ +// FIND MODE06 TEST ID NAME +//------------------------------------------------------------------------------ +function FindMode06TestIdName(TID: Byte): string; +begin + if (GTIDs <> nil) and GTIDs.TryGetValue(TID, Result) and (Result <> '') then Exit; + Result := Format('TID 0x%.2X', [TID]); +end; + +//------------------------------------------------------------------------------ +// FIND MODE06 OBDMIDNAME +//------------------------------------------------------------------------------ +function FindMode06OBDMIDName(OBDMID: Byte): string; +begin + if (GOBDMIDs <> nil) and GOBDMIDs.TryGetValue(OBDMID, Result) and (Result <> '') then Exit; + Result := Format('OBDMID 0x%.2X', [OBDMID]); +end; + +//------------------------------------------------------------------------------ +// FIND MODE06 UNIT +//------------------------------------------------------------------------------ +function FindMode06Unit(UCSID: Byte): TOBDMode06UnitInfo; +begin + if (GUCSIDs <> nil) and GUCSIDs.TryGetValue(UCSID, Result) then Exit; + Result.UCSID := UCSID; + Result.Scale := 1.0; + Result.UnitName := ''; + Result.Description := Format('Unknown UCSID 0x%.2X', [UCSID]); +end; + +{ TOBDMode06TestRecord } + +//------------------------------------------------------------------------------ +// PASSED TEST +//------------------------------------------------------------------------------ +function TOBDMode06TestRecord.PassedTest: Boolean; +begin + Result := (TestValue >= MinLimit) and (TestValue <= MaxLimit); +end; + +//------------------------------------------------------------------------------ +// SCALE FACTOR +//------------------------------------------------------------------------------ +function TOBDMode06TestRecord.ScaleFactor: Single; +begin + Result := FindMode06Unit(UnitsAndScalingId).Scale; +end; + +//------------------------------------------------------------------------------ +// UNIT NAME +//------------------------------------------------------------------------------ +function TOBDMode06TestRecord.UnitName: string; +begin + Result := FindMode06Unit(UnitsAndScalingId).UnitName; +end; + +//------------------------------------------------------------------------------ +// BUILD MODE06 REQUEST +//------------------------------------------------------------------------------ +function BuildMode06Request(OBDMID: Byte): TBytes; +begin + // Allocate Result + SetLength(Result, 2); + Result[0] := $46; // Service identifier per ISO 15031-5 + Result[1] := OBDMID; +end; + +//------------------------------------------------------------------------------ +// PARSE MODE06 RESPONSE +//------------------------------------------------------------------------------ +function ParseMode06Response(const Bytes: TBytes): TOBDMode06Response; +var + Cursor, RecordsRoom: Integer; + Rec: TOBDMode06TestRecord; + RecordList: TArray; +begin + if Length(Bytes) < 2 then + raise EOBDMode06.Create('Mode 06 response shorter than 2 bytes'); + if Bytes[0] <> $46 then + raise EOBDMode06.CreateFmt( + 'Mode 06 response service id 0x%.2x (expected 0x46)', [Bytes[0]]); + Result.OBDMID := Bytes[1]; + Cursor := 2; + RecordsRoom := (Length(Bytes) - Cursor) div TEST_RECORD_BYTES; + if (Length(Bytes) - Cursor) mod TEST_RECORD_BYTES <> 0 then + raise EOBDMode06.CreateFmt( + 'Mode 06 response payload not a multiple of %d bytes', + [TEST_RECORD_BYTES]); + // Allocate RecordList + SetLength(RecordList, RecordsRoom); + while Cursor + TEST_RECORD_BYTES <= Length(Bytes) do + begin + Rec.OBDMID := Result.OBDMID; + Rec.TestId := Bytes[Cursor]; + Rec.UnitsAndScalingId := Bytes[Cursor + 1]; + Rec.TestValue := (UInt16(Bytes[Cursor + 2]) shl 8) or Bytes[Cursor + 3]; + Rec.MinLimit := (UInt16(Bytes[Cursor + 4]) shl 8) or Bytes[Cursor + 5]; + Rec.MaxLimit := (UInt16(Bytes[Cursor + 6]) shl 8) or Bytes[Cursor + 7]; + RecordList[(Cursor - 2) div TEST_RECORD_BYTES] := Rec; + Inc(Cursor, TEST_RECORD_BYTES); + end; + Result.Records := RecordList; +end; + +initialization + GTIDs := TDictionary.Create; + GOBDMIDs := TDictionary.Create; + GUCSIDs := TDictionary.Create; + LoadStringMap('mode06-tids.json', 'tid', GTIDs); + LoadStringMap('mode06-obdmids.json', 'obdmid', GOBDMIDs); + LoadUCSIDCatalog; + +finalization + GUCSIDs.Free; + GOBDMIDs.Free; + GTIDs.Free; + +end. diff --git a/src/Services/OBD.Service09.Calibration.pas b/src/Services/OBD.Service09.Calibration.pas new file mode 100644 index 00000000..5a5c73ea --- /dev/null +++ b/src/Services/OBD.Service09.Calibration.pas @@ -0,0 +1,237 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.Service09.Calibration.pas +// CONTENTS : OBD-II Service 09 CalibrationID and CVN sweep +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.Service09.Calibration; + +interface + +uses + System.SysUtils; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDCalibration = class(Exception); + + TOBDCalibrationID = record + CalID: string; // 16 ASCII chars per block (trimmed of trailing nulls) + SourceECU: Word; // optional, set by sweep + end; + + TOBDCalibrationVerification = record + CVN: UInt32; // 4 raw bytes interpreted as big-endian + /// + /// Source ecu. + /// + SourceECU: Word; + end; + + /// + /// One ECU's pair after a sweep. CalID and CVN are + /// returned in the same order the ECU emitted them; ISO 15031-5 + /// guarantees positional correspondence. + /// + TOBDCalibrationPair = record + /// + /// Source ecu. + /// + SourceECU: Word; + /// + /// Cal id. + /// + CalID: string; + /// + /// Cvn. + /// + CVN: UInt32; + end; + +/// +/// Build the request bytes for Service 09 PID $04. +/// +function EncodeCalIDRequest: TBytes; +/// +/// Build the request bytes for Service 09 PID $06. +/// +function EncodeCVNRequest: TBytes; + +/// +/// Decode a 49 04 response into one or more CalIDs. +/// +function DecodeCalIDResponse(const Bytes: TBytes): TArray; +/// +/// Decode a 49 06 response into one or more CVNs. +/// +function DecodeCVNResponse(const Bytes: TBytes): TArray; + +/// +/// Format a CVN as the 8-character upper-case hex +/// representation that every scan tool displays. +/// +function FormatCVN(const CVN: UInt32): string; + +/// +/// Pair a CalID array with a CVN array positionally. +/// Lengths must match per ISO 15031-5 §8.6.6. +/// +function PairCalIDsAndCVNs(const IDs: TArray; + const VNs: TArray): TArray; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// CONSTANTS +//------------------------------------------------------------------------------ +const + CALID_BLOCK_BYTES = 16; + CVN_BLOCK_BYTES = 4; + +//------------------------------------------------------------------------------ +// ENCODE CAL IDREQUEST +//------------------------------------------------------------------------------ +function EncodeCalIDRequest: TBytes; +begin + // Allocate Result + SetLength(Result, 2); + Result[0] := $09; + Result[1] := $04; +end; + +//------------------------------------------------------------------------------ +// ENCODE CVNREQUEST +//------------------------------------------------------------------------------ +function EncodeCVNRequest: TBytes; +begin + // Allocate Result + SetLength(Result, 2); + Result[0] := $09; + Result[1] := $06; +end; + +//------------------------------------------------------------------------------ +// STRIP TRAILING NULLS +//------------------------------------------------------------------------------ +function StripTrailingNulls(const S: string): string; +var + N: Integer; +begin + N := Length(S); + while (N > 0) and ((S[N] = #0) or (S[N] = ' ')) do + Dec(N); + Result := Copy(S, 1, N); +end; + +//------------------------------------------------------------------------------ +// DECODE CAL IDRESPONSE +//------------------------------------------------------------------------------ +function DecodeCalIDResponse(const Bytes: TBytes): TArray; +var + Cursor, Count, I, J: Integer; + S: string; +begin + if Length(Bytes) < 3 then + raise EOBDCalibration.Create('CalID response shorter than 3 bytes'); + if Bytes[0] <> $49 then + raise EOBDCalibration.CreateFmt( + 'CalID response service id 0x%.2x (expected 0x49)', [Bytes[0]]); + if Bytes[1] <> $04 then + raise EOBDCalibration.CreateFmt( + 'CalID response PID 0x%.2x (expected 0x04)', [Bytes[1]]); + Count := Bytes[2]; + if 3 + Count * CALID_BLOCK_BYTES > Length(Bytes) then + raise EOBDCalibration.CreateFmt( + 'CalID response truncated: declared %d blocks of %d bytes', + [Count, CALID_BLOCK_BYTES]); + // Allocate Result + SetLength(Result, Count); + Cursor := 3; + for I := 0 to Count - 1 do + begin + // Allocate S + SetLength(S, CALID_BLOCK_BYTES); + for J := 0 to CALID_BLOCK_BYTES - 1 do + S[J + 1] := Char(Bytes[Cursor + J]); + Result[I].CalID := StripTrailingNulls(S); + Inc(Cursor, CALID_BLOCK_BYTES); + end; +end; + +//------------------------------------------------------------------------------ +// DECODE CVNRESPONSE +//------------------------------------------------------------------------------ +function DecodeCVNResponse(const Bytes: TBytes): TArray; +var + Cursor, Count, I: Integer; +begin + if Length(Bytes) < 3 then + raise EOBDCalibration.Create('CVN response shorter than 3 bytes'); + if Bytes[0] <> $49 then + raise EOBDCalibration.CreateFmt( + 'CVN response service id 0x%.2x (expected 0x49)', [Bytes[0]]); + if Bytes[1] <> $06 then + raise EOBDCalibration.CreateFmt( + 'CVN response PID 0x%.2x (expected 0x06)', [Bytes[1]]); + Count := Bytes[2]; + if 3 + Count * CVN_BLOCK_BYTES > Length(Bytes) then + raise EOBDCalibration.CreateFmt( + 'CVN response truncated: declared %d blocks of %d bytes', + [Count, CVN_BLOCK_BYTES]); + // Allocate Result + SetLength(Result, Count); + Cursor := 3; + for I := 0 to Count - 1 do + begin + Result[I].CVN := (UInt32(Bytes[Cursor]) shl 24) + or (UInt32(Bytes[Cursor + 1]) shl 16) + or (UInt32(Bytes[Cursor + 2]) shl 8) + or UInt32(Bytes[Cursor + 3]); + Inc(Cursor, CVN_BLOCK_BYTES); + end; +end; + +//------------------------------------------------------------------------------ +// FORMAT CVN +//------------------------------------------------------------------------------ +function FormatCVN(const CVN: UInt32): string; +begin + Result := Format('%.8X', [CVN]); +end; + +//------------------------------------------------------------------------------ +// PAIR CAL IDS AND CVNS +//------------------------------------------------------------------------------ +function PairCalIDsAndCVNs(const IDs: TArray; + const VNs: TArray): TArray; +var + I: Integer; +begin + if Length(IDs) <> Length(VNs) then + raise EOBDCalibration.CreateFmt( + 'CalID count %d != CVN count %d (ISO 15031-5 requires positional pairing)', + [Length(IDs), Length(VNs)]); + // Allocate Result + SetLength(Result, Length(IDs)); + for I := 0 to High(IDs) do + begin + Result[I].CalID := IDs[I].CalID; + Result[I].CVN := VNs[I].CVN; + if IDs[I].SourceECU <> 0 then + Result[I].SourceECU := IDs[I].SourceECU + else + Result[I].SourceECU := VNs[I].SourceECU; + end; +end; + +end. diff --git a/src/Services/OBD.Tachograph.Signature.pas b/src/Services/OBD.Tachograph.Signature.pas new file mode 100644 index 00000000..e8ab755c --- /dev/null +++ b/src/Services/OBD.Tachograph.Signature.pas @@ -0,0 +1,277 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.Tachograph.Signature.pas +// CONTENTS : EU tachograph DDD signature chain verification +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 08/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.Tachograph.Signature; + +interface + +uses + System.SysUtils, System.Classes, System.IOUtils, System.Generics.Collections, + + OBD.ECU.Signature; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDTachographSignature = class(Exception); + + TDDDBlockKind = ( + dbkUnknown, + dbkOverview, + dbkActivities, + dbkEvents, + dbkFaults, + dbkVehicleUnit, + dbkSpeeds, + dbkTechnicalData, + dbkCardChip, + dbkSignatureBlock // contains a signed digest over the prior block + ); + + TDDDBlock = record + /// + /// Kind. + /// + Kind: TDDDBlockKind; + Tag: Word; // raw 2-byte TLV tag from the file + /// + /// Length. + /// + Length: Integer; + Offset: Integer; // byte offset within the file + /// + /// Data. + /// + Data: TBytes; + end; + + TDDDChainResult = record + /// + /// Verified. + /// + Verified: Boolean; + /// + /// Blocks parsed. + /// + BlocksParsed: Integer; + /// + /// Signatures verified. + /// + SignaturesVerified: Integer; + FirstFailureBlockIndex: Integer; // -1 on success + /// + /// Reason. + /// + Reason: string; + end; + + TOBDTachographSignatureChecker = class + private + FVerifierForCard: IFirmwareSignatureVerifier; + FVerifierForVU: IFirmwareSignatureVerifier; + /// + /// Classify tag. + /// + function ClassifyTag(Tag: Word): TDDDBlockKind; + public + /// + /// Set the verifier used for the card-side signature + /// block. Production code wires an OpenSSL ECDSA verifier here; + /// unit tests can pass TOBDPermissiveSignatureVerifier. + /// + procedure SetCardVerifier(const V: IFirmwareSignatureVerifier); + /// + /// Set the verifier used for the vehicle-unit signature + /// block. Same wiring story as the card verifier. + /// + procedure SetVUVerifier(const V: IFirmwareSignatureVerifier); + + /// + /// Parse a DDD file into its TLV blocks. Doesn't verify. + /// + function ParseBlocks(const Bytes: TBytes): TArray; + + /// + /// Verify the signature chain across the parsed blocks. + /// Each data block must be immediately followed by a signature + /// block whose body, when fed to the configured verifier + /// alongside the data block bytes, returns True. + /// + function VerifyChain(const Bytes: TBytes): TDDDChainResult; + end; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// CONSTANTS +//------------------------------------------------------------------------------ +const + // Tags seen in the wild on Gen1 / Gen2 driver cards. Source: EU + // 2016/799 Annex 1C Appendix 7. Values are spec-stable and + // documented in publicly downloadable tooling (e.g. JRC reference + // implementation, libtacho). + TAG_OVERVIEW = $0501; + TAG_ACTIVITIES = $0504; + TAG_EVENTS = $0502; + TAG_FAULTS = $0503; + TAG_VEHICLE_UNIT = $0521; + TAG_SPEEDS = $0506; + TAG_TECHNICAL_DATA = $0505; + TAG_CARD_CHIP = $0508; + TAG_SIGNATURE = $050E; + +//------------------------------------------------------------------------------ +// SET CARD VERIFIER +//------------------------------------------------------------------------------ +procedure TOBDTachographSignatureChecker.SetCardVerifier( + const V: IFirmwareSignatureVerifier); +begin + FVerifierForCard := V; +end; + +//------------------------------------------------------------------------------ +// SET VUVERIFIER +//------------------------------------------------------------------------------ +procedure TOBDTachographSignatureChecker.SetVUVerifier( + const V: IFirmwareSignatureVerifier); +begin + FVerifierForVU := V; +end; + +//------------------------------------------------------------------------------ +// CLASSIFY TAG +//------------------------------------------------------------------------------ +function TOBDTachographSignatureChecker.ClassifyTag(Tag: Word): TDDDBlockKind; +begin + case Tag of + TAG_OVERVIEW: Result := dbkOverview; + TAG_ACTIVITIES: Result := dbkActivities; + TAG_EVENTS: Result := dbkEvents; + TAG_FAULTS: Result := dbkFaults; + TAG_VEHICLE_UNIT: Result := dbkVehicleUnit; + TAG_SPEEDS: Result := dbkSpeeds; + TAG_TECHNICAL_DATA: Result := dbkTechnicalData; + TAG_CARD_CHIP: Result := dbkCardChip; + TAG_SIGNATURE: Result := dbkSignatureBlock; + else + Result := dbkUnknown; + end; +end; + +//------------------------------------------------------------------------------ +// PARSE BLOCKS +//------------------------------------------------------------------------------ +function TOBDTachographSignatureChecker.ParseBlocks( + const Bytes: TBytes): TArray; +var + Cursor: Integer; + Block: TDDDBlock; + List: TList; + TagWord: Word; + Len: Integer; +begin + // Create List + List := TList.Create; + try + Cursor := 0; + while Cursor + 4 <= Length(Bytes) do + begin + TagWord := (UInt32(Bytes[Cursor]) shl 8) or Bytes[Cursor + 1]; + Len := (UInt32(Bytes[Cursor + 2]) shl 8) or Bytes[Cursor + 3]; + if Cursor + 4 + Len > Length(Bytes) then + raise EOBDTachographSignature.CreateFmt( + 'DDD truncated at offset %d: declared %d data bytes', + [Cursor, Len]); + Block := Default(TDDDBlock); + Block.Tag := TagWord; + Block.Kind := ClassifyTag(TagWord); + Block.Length := Len; + Block.Offset := Cursor; + // Allocate Block.Data + SetLength(Block.Data, Len); + if Len > 0 then + Move(Bytes[Cursor + 4], Block.Data[0], Len); + List.Add(Block); + Inc(Cursor, 4 + Len); + end; + Result := List.ToArray; + finally + // Free List + List.Free; + end; +end; + +//------------------------------------------------------------------------------ +// VERIFY CHAIN +//------------------------------------------------------------------------------ +function TOBDTachographSignatureChecker.VerifyChain( + const Bytes: TBytes): TDDDChainResult; +var + Blocks: TArray; + I: Integer; + Verifier: IFirmwareSignatureVerifier; + IsCardSide: Boolean; +begin + Result := Default(TDDDChainResult); + Result.FirstFailureBlockIndex := -1; + Blocks := ParseBlocks(Bytes); + Result.BlocksParsed := Length(Blocks); + + I := 0; + while I < High(Blocks) do + begin + if Blocks[I].Kind in [dbkUnknown, dbkSignatureBlock] then + begin + Inc(I); + Continue; + end; + // Expect the next block to be a signature over the current one. + if Blocks[I + 1].Kind <> dbkSignatureBlock then + begin + Result.Reason := Format( + 'Block %d (kind=%d) not followed by a signature block', + [I, Ord(Blocks[I].Kind)]); + Result.FirstFailureBlockIndex := I; + Exit; + end; + IsCardSide := Blocks[I].Kind in [dbkCardChip, dbkOverview, dbkActivities, + dbkEvents, dbkFaults, dbkSpeeds, dbkTechnicalData]; + if IsCardSide then + Verifier := FVerifierForCard + else + Verifier := FVerifierForVU; + if Verifier = nil then + begin + Result.Reason := Format( + 'No verifier configured for %s block (index %d)', + [BoolToStr(IsCardSide, True), I]); + Result.FirstFailureBlockIndex := I; + Exit; + end; + if not Verifier.Verify(Blocks[I].Data, Blocks[I + 1].Data) then + begin + Result.Reason := Format( + 'Signature for block %d (offset 0x%x) failed verification with %s', + [I, Blocks[I].Offset, Verifier.AlgorithmName]); + Result.FirstFailureBlockIndex := I; + Exit; + end; + Inc(Result.SignaturesVerified); + Inc(I, 2); + end; + Result.Verified := True; +end; + +end. diff --git a/src/Services/OBD.Tachograph.Workshop.pas b/src/Services/OBD.Tachograph.Workshop.pas new file mode 100644 index 00000000..60a5258a --- /dev/null +++ b/src/Services/OBD.Tachograph.Workshop.pas @@ -0,0 +1,371 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.Tachograph.Workshop.pas +// CONTENTS : EU tachograph workshop-card calibration ops +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.Tachograph.Workshop; + +interface + +uses + System.SysUtils, System.DateUtils; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + EOBDTachoWorkshop = class(Exception); + + /// + /// UTC time set/sync record. The VU clock is monotonic + /// during sealed operation; only a workshop card can step it. + /// + TTachoUTCSync = record + /// + /// Seconds since 1970-01-01 00:00:00 UTC, big-endian + /// uint32 on the wire (Annex 1C TimeReal). + /// + UTCTimestamp: UInt32; + WorkshopCardId: TBytes; // 16 bytes — extracted from the card cert + end; + + /// + /// Speed-source coefficients. K is the canonical figure + /// the workshop technician adjusts; L and W are derived from the + /// vehicle's drivetrain. + /// + TTachoKLWFactors = record + K: UInt16; // pulses/km — VU input scaling, 4000..25000 typical + L: UInt16; // tyre circumference in mm/rev * 100 (e.g. 200000 = 2000 mm) + W: UInt16; // characteristic coefficient (pulses/km of the gearbox) + end; + + TTachoTyreSize = record + /// + /// Tyre rolling circumference in millimetres. + /// + CircumferenceMm: UInt16; + end; + + TTachoVINUpdate = record + VIN: string; // 17 ASCII chars (must validate) + end; + + TTachoVRPlate = record + PlateText: string; // up to 13 ASCII chars per Annex 1C + NationalSymbol: Byte; // EU country code (Annex 1C Appendix 1B) + end; + + TTachoSpeedSource = record + /// + /// Pulses per revolution. + /// + PulsesPerRevolution: UInt16; + end; + + TTachoSealedActivation = record + UTCTimestamp: UInt32; // seal time + WorkshopCardId: TBytes; // 16 bytes + PostSealNote: string; // optional free-form notes + end; + +/// +/// Encode UTCSync to the wire form: 4 bytes (timestamp BE) +/// followed by 16 bytes (workshop card id). +/// +function EncodeUTCSync(const Op: TTachoUTCSync): TBytes; +function DecodeUTCSync(const Bytes: TBytes): TTachoUTCSync; + +/// +/// Encode K/L/W as 6 bytes, three big-endian uint16 values. +/// +function EncodeKLW(const Op: TTachoKLWFactors): TBytes; +function DecodeKLW(const Bytes: TBytes): TTachoKLWFactors; + +/// +/// Encode tyre circumference as 2 BE bytes. +/// +function EncodeTyreSize(const Op: TTachoTyreSize): TBytes; +function DecodeTyreSize(const Bytes: TBytes): TTachoTyreSize; + +/// +/// Encode VIN as 17 ASCII bytes. Validates length. +/// +function EncodeVIN(const Op: TTachoVINUpdate): TBytes; +function DecodeVIN(const Bytes: TBytes): TTachoVINUpdate; + +/// +/// Encode VRPlate as length-prefixed ASCII + 1 byte symbol. +/// +function EncodeVRPlate(const Op: TTachoVRPlate): TBytes; +function DecodeVRPlate(const Bytes: TBytes): TTachoVRPlate; + +/// +/// Encode pulses-per-revolution as 2 BE bytes. +/// +function EncodeSpeedSource(const Op: TTachoSpeedSource): TBytes; + +/// +/// Encode sealed-state activation: 4 bytes timestamp, +/// 16 bytes card id, length-prefixed UTF-8 note. +/// +function EncodeSealedActivation(const Op: TTachoSealedActivation): TBytes; + +/// +/// Convert a Delphi TDateTime to the Annex 1C TimeReal +/// uint32 (seconds since UNIX epoch). +/// +function DateTimeToTimeReal(const DT: TDateTime): UInt32; +function TimeRealToDateTime(const T: UInt32): TDateTime; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +//------------------------------------------------------------------------------ +// DATE TIME TO TIME REAL +//------------------------------------------------------------------------------ +function DateTimeToTimeReal(const DT: TDateTime): UInt32; +begin + Result := UInt32(SecondsBetween(EncodeDate(1970, 1, 1), DT)); +end; + +//------------------------------------------------------------------------------ +// TIME REAL TO DATE TIME +//------------------------------------------------------------------------------ +function TimeRealToDateTime(const T: UInt32): TDateTime; +begin + Result := IncSecond(EncodeDate(1970, 1, 1), Integer(T)); +end; + +//------------------------------------------------------------------------------ +// WRITE UINT16 BE +//------------------------------------------------------------------------------ +function WriteUInt16BE(Out_: TBytes; Cursor: Integer; V: UInt16): Integer; +begin + Out_[Cursor] := Byte(V shr 8); + Out_[Cursor + 1] := Byte(V and $FF); + Result := Cursor + 2; +end; + +//------------------------------------------------------------------------------ +// WRITE UINT32 BE +//------------------------------------------------------------------------------ +function WriteUInt32BE(Out_: TBytes; Cursor: Integer; V: UInt32): Integer; +begin + Out_[Cursor] := Byte(V shr 24); + Out_[Cursor + 1] := Byte(V shr 16); + Out_[Cursor + 2] := Byte(V shr 8); + Out_[Cursor + 3] := Byte(V and $FF); + Result := Cursor + 4; +end; + +//------------------------------------------------------------------------------ +// READ UINT16 BE +//------------------------------------------------------------------------------ +function ReadUInt16BE(const B: TBytes; Off: Integer): UInt16; +begin + Result := (UInt16(B[Off]) shl 8) or B[Off + 1]; +end; + +//------------------------------------------------------------------------------ +// READ UINT32 BE +//------------------------------------------------------------------------------ +function ReadUInt32BE(const B: TBytes; Off: Integer): UInt32; +begin + Result := (UInt32(B[Off]) shl 24) + or (UInt32(B[Off + 1]) shl 16) + or (UInt32(B[Off + 2]) shl 8) + or UInt32(B[Off + 3]); +end; + +//------------------------------------------------------------------------------ +// ENCODE UTCSYNC +//------------------------------------------------------------------------------ +function EncodeUTCSync(const Op: TTachoUTCSync): TBytes; +begin + if Length(Op.WorkshopCardId) <> 16 then + raise EOBDTachoWorkshop.CreateFmt( + 'WorkshopCardId must be 16 bytes (got %d)', [Length(Op.WorkshopCardId)]); + // Allocate Result + SetLength(Result, 4 + 16); + WriteUInt32BE(Result, 0, Op.UTCTimestamp); + Move(Op.WorkshopCardId[0], Result[4], 16); +end; + +//------------------------------------------------------------------------------ +// DECODE UTCSYNC +//------------------------------------------------------------------------------ +function DecodeUTCSync(const Bytes: TBytes): TTachoUTCSync; +begin + if Length(Bytes) <> 20 then + raise EOBDTachoWorkshop.Create('UTCSync expects 20 bytes'); + Result.UTCTimestamp := ReadUInt32BE(Bytes, 0); + // Allocate Result.WorkshopCardId + SetLength(Result.WorkshopCardId, 16); + Move(Bytes[4], Result.WorkshopCardId[0], 16); +end; + +//------------------------------------------------------------------------------ +// ENCODE KLW +//------------------------------------------------------------------------------ +function EncodeKLW(const Op: TTachoKLWFactors): TBytes; +begin + if (Op.K < 4000) or (Op.K > 25000) then + raise EOBDTachoWorkshop.CreateFmt( + 'K must be 4000..25000 pulses/km (got %d)', [Op.K]); + // Allocate Result + SetLength(Result, 6); + WriteUInt16BE(Result, 0, Op.K); + WriteUInt16BE(Result, 2, Op.L); + WriteUInt16BE(Result, 4, Op.W); +end; + +//------------------------------------------------------------------------------ +// DECODE KLW +//------------------------------------------------------------------------------ +function DecodeKLW(const Bytes: TBytes): TTachoKLWFactors; +begin + if Length(Bytes) <> 6 then + raise EOBDTachoWorkshop.Create('K/L/W expects 6 bytes'); + Result.K := ReadUInt16BE(Bytes, 0); + Result.L := ReadUInt16BE(Bytes, 2); + Result.W := ReadUInt16BE(Bytes, 4); +end; + +//------------------------------------------------------------------------------ +// ENCODE TYRE SIZE +//------------------------------------------------------------------------------ +function EncodeTyreSize(const Op: TTachoTyreSize): TBytes; +begin + if (Op.CircumferenceMm < 1500) or (Op.CircumferenceMm > 4500) then + raise EOBDTachoWorkshop.CreateFmt( + 'Tyre circumference must be 1500..4500 mm (got %d)', + [Op.CircumferenceMm]); + // Allocate Result + SetLength(Result, 2); + WriteUInt16BE(Result, 0, Op.CircumferenceMm); +end; + +//------------------------------------------------------------------------------ +// DECODE TYRE SIZE +//------------------------------------------------------------------------------ +function DecodeTyreSize(const Bytes: TBytes): TTachoTyreSize; +begin + if Length(Bytes) <> 2 then + raise EOBDTachoWorkshop.Create('TyreSize expects 2 bytes'); + Result.CircumferenceMm := ReadUInt16BE(Bytes, 0); +end; + +//------------------------------------------------------------------------------ +// ENCODE VIN +//------------------------------------------------------------------------------ +function EncodeVIN(const Op: TTachoVINUpdate): TBytes; +var + I: Integer; +begin + if Length(Op.VIN) <> 17 then + raise EOBDTachoWorkshop.CreateFmt( + 'VIN must be 17 chars (got %d)', [Length(Op.VIN)]); + // Allocate Result + SetLength(Result, 17); + for I := 0 to 16 do Result[I] := Byte(Ord(Op.VIN[I + 1])); +end; + +//------------------------------------------------------------------------------ +// DECODE VIN +//------------------------------------------------------------------------------ +function DecodeVIN(const Bytes: TBytes): TTachoVINUpdate; +var + I: Integer; +begin + if Length(Bytes) <> 17 then + raise EOBDTachoWorkshop.Create('VIN expects 17 bytes'); + // Allocate Result.VIN + SetLength(Result.VIN, 17); + for I := 0 to 16 do Result.VIN[I + 1] := Char(Bytes[I]); +end; + +//------------------------------------------------------------------------------ +// ENCODE VRPLATE +//------------------------------------------------------------------------------ +function EncodeVRPlate(const Op: TTachoVRPlate): TBytes; +var + Plate: TBytes; + I: Integer; +begin + if Length(Op.PlateText) > 13 then + raise EOBDTachoWorkshop.Create('VRPlate text exceeds 13 ASCII chars'); + // Allocate Plate + SetLength(Plate, Length(Op.PlateText)); + for I := 0 to High(Plate) do Plate[I] := Byte(Ord(Op.PlateText[I + 1])); + // Allocate Result + SetLength(Result, 1 + Length(Plate) + 1); + Result[0] := Byte(Length(Plate)); + if Length(Plate) > 0 then Move(Plate[0], Result[1], Length(Plate)); + Result[High(Result)] := Op.NationalSymbol; +end; + +//------------------------------------------------------------------------------ +// DECODE VRPLATE +//------------------------------------------------------------------------------ +function DecodeVRPlate(const Bytes: TBytes): TTachoVRPlate; +var + N, I: Integer; +begin + if Length(Bytes) < 2 then + raise EOBDTachoWorkshop.Create('VRPlate too short'); + N := Bytes[0]; + if 1 + N + 1 <> Length(Bytes) then + raise EOBDTachoWorkshop.Create('VRPlate length mismatch'); + // Allocate Result.PlateText + SetLength(Result.PlateText, N); + for I := 0 to N - 1 do Result.PlateText[I + 1] := Char(Bytes[1 + I]); + Result.NationalSymbol := Bytes[1 + N]; +end; + +//------------------------------------------------------------------------------ +// ENCODE SPEED SOURCE +//------------------------------------------------------------------------------ +function EncodeSpeedSource(const Op: TTachoSpeedSource): TBytes; +begin + if Op.PulsesPerRevolution = 0 then + raise EOBDTachoWorkshop.Create('PulsesPerRevolution must be > 0'); + // Allocate Result + SetLength(Result, 2); + WriteUInt16BE(Result, 0, Op.PulsesPerRevolution); +end; + +//------------------------------------------------------------------------------ +// ENCODE SEALED ACTIVATION +//------------------------------------------------------------------------------ +function EncodeSealedActivation(const Op: TTachoSealedActivation): TBytes; +var + Note: TBytes; + Cursor: Integer; +begin + if Length(Op.WorkshopCardId) <> 16 then + raise EOBDTachoWorkshop.Create('WorkshopCardId must be 16 bytes'); + Note := TEncoding.UTF8.GetBytes(Op.PostSealNote); + if Length(Note) > 255 then + raise EOBDTachoWorkshop.Create('PostSealNote exceeds 255 bytes'); + // Allocate Result + SetLength(Result, 4 + 16 + 1 + Length(Note)); + Cursor := 0; + Cursor := WriteUInt32BE(Result, Cursor, Op.UTCTimestamp); + Move(Op.WorkshopCardId[0], Result[Cursor], 16); + Inc(Cursor, 16); + Result[Cursor] := Byte(Length(Note)); + Inc(Cursor); + if Length(Note) > 0 then + Move(Note[0], Result[Cursor], Length(Note)); +end; + +end. diff --git a/src/Services/OBD.UDS.NRC.pas b/src/Services/OBD.UDS.NRC.pas new file mode 100644 index 00000000..d6df1ed9 --- /dev/null +++ b/src/Services/OBD.UDS.NRC.pas @@ -0,0 +1,221 @@ +//------------------------------------------------------------------------------ +// UNIT : OBD.UDS.NRC.pas +// CONTENTS : ISO 14229-1 UDS Negative Response Code catalog +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit OBD.UDS.NRC; + +interface + +uses + System.SysUtils, System.Generics.Collections; + +//------------------------------------------------------------------------------ +// TYPES +//------------------------------------------------------------------------------ +type + TOBDUDSNrcCategory = ( + nrcGeneral, + nrcSecurity, + nrcRequestData, + nrcCondition, + nrcServer, + nrcReserved + ); + + TOBDUDSNrcInfo = record + /// + /// Code. + /// + Code: Byte; + /// + /// Short name. + /// + ShortName: string; + /// + /// Description. + /// + Description: string; + /// + /// Category. + /// + Category: TOBDUDSNrcCategory; + end; + +/// +/// Look up an NRC. Unknown / reserved codes return a record +/// with category=nrcReserved and a synthetic description; never raises. +/// +function DescribeNRC(NRC: Byte): TOBDUDSNrcInfo; + +/// +/// One-line formatter convenient for log lines and exception +/// messages: "NRC 0x33 (SAD: securityAccessDenied)". +/// +function FormatNRC(NRC: Byte): string; + +/// +/// True if the byte is in a category clients should retry +/// (busy / repeat-request, conditions-not-correct). +/// +function IsTransientNRC(NRC: Byte): Boolean; + +/// +/// Total entries loaded from the catalog (excludes synthetic). +/// +function NRCCatalogCount: Integer; + +//------------------------------------------------------------------------------ +// IMPLEMENTATION +//------------------------------------------------------------------------------ +implementation + +uses + System.Classes, System.JSON, + OBD.Catalog.Path; + +const + CatalogFileName = 'uds-nrc.json'; + +var + GMap: TDictionary = nil; + +//------------------------------------------------------------------------------ +// CATEGORY FROM STRING +//------------------------------------------------------------------------------ +function CategoryFromString(const S: string): TOBDUDSNrcCategory; +begin + if SameText(S, 'general') then Exit(nrcGeneral); + if SameText(S, 'security') then Exit(nrcSecurity); + if SameText(S, 'request_data') then Exit(nrcRequestData); + if SameText(S, 'condition') then Exit(nrcCondition); + if SameText(S, 'server') then Exit(nrcServer); + Result := nrcReserved; +end; + +//------------------------------------------------------------------------------ +// PARSE HEX BYTE +//------------------------------------------------------------------------------ +function ParseHexByte(const S: string; out B: Byte): Boolean; +var + V: Integer; + T: string; +begin + T := S; + if T.StartsWith('$') then + T := T // already Delphi hex + else if T.StartsWith('0x', True) then + T := '$' + T.Substring(2); + Result := TryStrToInt(T, V) and (V >= 0) and (V <= 255); + if Result then B := Byte(V); +end; + +//------------------------------------------------------------------------------ +// LOAD CATALOG +//------------------------------------------------------------------------------ +procedure LoadCatalog; +var + Path, Raw: string; + Doc: TJSONValue; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + Info: TOBDUDSNrcInfo; + Code: Byte; + Stream: TStringStream; +begin + Path := ResolveCatalogPath(CatalogFileName); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if not (Doc is TJSONObject) then + begin + Doc.Free; + Exit; + end; + try + Arr := (Doc as TJSONObject).GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + if not ParseHexByte(Obj.GetValue('code', ''), Code) then Continue; + Info.Code := Code; + Info.ShortName := Obj.GetValue('short', ''); + Info.Description := Obj.GetValue('description', ''); + Info.Category := CategoryFromString(Obj.GetValue('category', 'reserved')); + GMap.AddOrSetValue(Code, Info); + end; + finally + // Free the document + Doc.Free; + end; +end; + +//------------------------------------------------------------------------------ +// DESCRIBE NRC +//------------------------------------------------------------------------------ +function DescribeNRC(NRC: Byte): TOBDUDSNrcInfo; +begin + if (GMap <> nil) and GMap.TryGetValue(NRC, Result) then Exit; + Result.Code := NRC; + Result.ShortName := Format('NRC_0x%.2x', [NRC]); + Result.Description := Format('reserved or manufacturer-specific NRC 0x%.2x', [NRC]); + Result.Category := nrcReserved; +end; + +//------------------------------------------------------------------------------ +// FORMAT NRC +//------------------------------------------------------------------------------ +function FormatNRC(NRC: Byte): string; +var + Info: TOBDUDSNrcInfo; +begin + Info := DescribeNRC(NRC); + Result := Format('NRC 0x%.2x (%s: %s)', [NRC, Info.ShortName, Info.Description]); +end; + +//------------------------------------------------------------------------------ +// IS TRANSIENT NRC +//------------------------------------------------------------------------------ +function IsTransientNRC(NRC: Byte): Boolean; +begin + Result := (NRC = $21) or (NRC = $22) or (NRC = $78) or (NRC = $94); +end; + +//------------------------------------------------------------------------------ +// NRCCATALOG COUNT +//------------------------------------------------------------------------------ +function NRCCatalogCount: Integer; +begin + if GMap = nil then Result := 0 else Result := GMap.Count; +end; + +initialization + GMap := TDictionary.Create; + LoadCatalog; + +finalization + GMap.Free; + +end. diff --git a/src/Services/OBD.VehicleHealth.pas b/src/Services/OBD.VehicleHealth.pas index 3304afe3..b16e9d4c 100644 --- a/src/Services/OBD.VehicleHealth.pas +++ b/src/Services/OBD.VehicleHealth.pas @@ -29,7 +29,9 @@ interface type EOBDHealthError = class(Exception); - /// One DTC + its catalog metadata. + /// + /// One DTC + its catalog metadata. + /// TOBDHealthDTC = record Code: string; Catalogued: Boolean; @@ -37,29 +39,45 @@ TOBDHealthDTC = record Severity: TOBDDtcSeverity; end; - /// Aggregated diagnostic snapshot. Every field that - /// couldn't be read carries an error string; callers display - /// what's present and surface the missing pieces. + /// + /// Aggregated diagnostic snapshot. Every field that + /// couldn't be read carries an error string; callers display + /// what's present and surface the missing pieces. + /// TOBDHealthSnapshot = record - /// Wall-clock timestamp when the snapshot was taken. + /// + /// Wall-clock timestamp when the snapshot was taken. + /// Timestamp: TDateTime; - /// Vehicle identification number (Service 09 PID 02). + /// + /// Vehicle identification number (Service 09 PID 02). + /// VIN: string; VINError: string; - /// Resolved OEM extension (or nil if no extension matched). + /// + /// Resolved OEM extension (or nil if no extension matched). + /// OEM: IOBDOEMExtension; OEMDisplayName: string; - /// Active DTCs (Service 03) annotated with catalog metadata. + /// + /// Active DTCs (Service 03) annotated with catalog metadata. + /// ActiveDTCs: TArray; DTCError: string; - /// Pending DTCs (Service 07) annotated with catalog metadata. + /// + /// Pending DTCs (Service 07) annotated with catalog metadata. + /// PendingDTCs: TArray; PendingError: string; - /// Readiness monitor status (Service 01 PID 01). + /// + /// Readiness monitor status (Service 01 PID 01). + /// Readiness: TOBDReadinessReport; ReadinessKnown: Boolean; ReadinessError: string; - /// Key live values pulled from Service 01. + /// + /// Key live values pulled from Service 01. + /// BatteryVoltage: Double; BatteryVoltageKnown: Boolean; EngineRPM: Word; @@ -70,18 +88,24 @@ TOBDHealthSnapshot = record CoolantTemperatureKnown: Boolean; EngineLoad: Double; EngineLoadKnown: Boolean; - /// Computed health score 0..100 — 100 = healthy, 0 = - /// every catalogued DTC is critical with the MIL on. The - /// scoring rubric is documented in ComputeHealthScore. + /// + /// Computed health score 0..100 — 100 = healthy, 0 = + /// every catalogued DTC is critical with the MIL on. The + /// scoring rubric is documented in ComputeHealthScore. + /// HealthScore: Byte; - /// One-line summary suitable for status bars. + /// + /// One-line summary suitable for status bars. + /// SummaryLine: string; end; - /// Snapshot capture orchestrator. Wraps an async connection - /// + auto-resolves the OEM extension by VIN. Each public method - /// is best-effort — failures land in the snapshot's *Error - /// fields rather than raising. + /// + /// Snapshot capture orchestrator. Wraps an async connection + /// + auto-resolves the OEM extension by VIN. Each public method + /// is best-effort — failures land in the snapshot's *Error + /// fields rather than raising. + /// TOBDHealthCapture = class strict private FConnection: TOBDConnectionAsync; @@ -100,10 +124,12 @@ TOBDHealthCapture = class public constructor Create(const Conn: TOBDConnectionAsync); - /// One-shot capture. Walks: VIN → OEM resolution → - /// active DTCs → pending DTCs → readiness → live values → - /// health score + summary line. Returns the populated - /// snapshot regardless of partial failures. + /// + /// One-shot capture. Walks: VIN → OEM resolution → + /// active DTCs → pending DTCs → readiness → live values → + /// health score + summary line. Returns the populated + /// snapshot regardless of partial failures. + /// function Capture: TOBDHealthSnapshot; end; @@ -113,6 +139,9 @@ implementation System.StrUtils, OBD.Async, OBD.OEM.Coding; +//------------------------------------------------------------------------------ +// IF THEN STR +//------------------------------------------------------------------------------ function IfThenStr(const Cond: Boolean; const A, B: string): string; begin if Cond then Result := A else Result := B; @@ -126,6 +155,9 @@ function IfThenStr(const Cond: Boolean; const A, B: string): string; PID_VEHICLE_SPEED = $0D; PID_BATTERY_VOLTAGE = $42; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDHealthCapture.Create(const Conn: TOBDConnectionAsync); begin if Conn = nil then @@ -134,6 +166,9 @@ constructor TOBDHealthCapture.Create(const Conn: TOBDConnectionAsync); FConnection := Conn; end; +//------------------------------------------------------------------------------ +// READ OBDPID +//------------------------------------------------------------------------------ function TOBDHealthCapture.ReadOBDPid(const Pid: Word; out Bytes: TBytes; const TimeoutMs: Cardinal): Boolean; var @@ -151,6 +186,9 @@ function TOBDHealthCapture.ReadOBDPid(const Pid: Word; out Bytes: TBytes; end; end; +//------------------------------------------------------------------------------ +// READ VIN +//------------------------------------------------------------------------------ function TOBDHealthCapture.ReadVIN(out VIN: string): Boolean; var Future: IOBDFuture; @@ -177,6 +215,9 @@ function TOBDHealthCapture.ReadVIN(out VIN: string): Boolean; end; end; +//------------------------------------------------------------------------------ +// READ DTCS SERVICE +//------------------------------------------------------------------------------ function TOBDHealthCapture.ReadDTCsService(const Service: Byte; out Codes: TArray): Boolean; var @@ -218,6 +259,9 @@ function TOBDHealthCapture.ReadDTCsService(const Service: Byte; end; end; +//------------------------------------------------------------------------------ +// FILL DTCMETADATA +//------------------------------------------------------------------------------ procedure TOBDHealthCapture.FillDTCMetadata(const OEM: IOBDOEMExtension; const Codes: TArray; out Annotated: TArray); var @@ -245,6 +289,9 @@ procedure TOBDHealthCapture.FillDTCMetadata(const OEM: IOBDOEMExtension; end; end; +//------------------------------------------------------------------------------ +// READ READINESS +//------------------------------------------------------------------------------ procedure TOBDHealthCapture.ReadReadiness(var Snap: TOBDHealthSnapshot); var Bytes, Payload: TBytes; @@ -274,6 +321,9 @@ procedure TOBDHealthCapture.ReadReadiness(var Snap: TOBDHealthSnapshot); end; end; +//------------------------------------------------------------------------------ +// READ LIVE VALUES +//------------------------------------------------------------------------------ procedure TOBDHealthCapture.ReadLiveValues(var Snap: TOBDHealthSnapshot); var Bytes: TBytes; @@ -309,6 +359,9 @@ procedure TOBDHealthCapture.ReadLiveValues(var Snap: TOBDHealthSnapshot); Snap.EngineLoad := Bytes[2] * (100 / 255); end; +//------------------------------------------------------------------------------ +// COMPUTE HEALTH SCORE +//------------------------------------------------------------------------------ procedure TOBDHealthCapture.ComputeHealthScore(var Snap: TOBDHealthSnapshot); var Penalty: Integer; @@ -345,6 +398,9 @@ procedure TOBDHealthCapture.ComputeHealthScore(var Snap: TOBDHealthSnapshot); else Snap.HealthScore := Byte(100 - Penalty); end; +//------------------------------------------------------------------------------ +// COMPOSE SUMMARY +//------------------------------------------------------------------------------ procedure TOBDHealthCapture.ComposeSummary(var Snap: TOBDHealthSnapshot); var Buf: TStringBuilder; @@ -367,6 +423,9 @@ procedure TOBDHealthCapture.ComposeSummary(var Snap: TOBDHealthSnapshot); end; end; +//------------------------------------------------------------------------------ +// CAPTURE +//------------------------------------------------------------------------------ function TOBDHealthCapture.Capture: TOBDHealthSnapshot; var Codes: TArray; diff --git a/src/Utilities/OBD.Async.pas b/src/Utilities/OBD.Async.pas index c4df926b..7dfadfbd 100644 --- a/src/Utilities/OBD.Async.pas +++ b/src/Utilities/OBD.Async.pas @@ -33,9 +33,13 @@ interface /// IOBDCancellationToken = interface ['{A98A5DA0-3C20-4F44-8DE7-5C7C5F5B0C7A}'] - /// Cancel the operation. Idempotent. + /// + /// Cancel the operation. Idempotent. + /// procedure Cancel; - /// True after Cancel has been called. + /// + /// True after Cancel has been called. + /// function IsCancelled: Boolean; end; @@ -64,7 +68,9 @@ TOBDCancellationToken = class(TInterfacedObject, IOBDCancellationToken) /// EOperationCancelled if it was cancelled. /// function Await(TimeoutMs: Cardinal = INFINITE): T; - /// True if settled (regardless of outcome). + /// + /// True if settled (regardless of outcome). + /// function IsCompleted: Boolean; function IsFaulted: Boolean; function IsCancelled: Boolean; @@ -82,11 +88,17 @@ TOBDCancellationToken = class(TInterfacedObject, IOBDCancellationToken) /// Producer-side handle. Can also be passed around as IOBDFuture<T>. /// IOBDPromise = interface(IOBDFuture) - /// Settle the future with a value. Call once. + /// + /// Settle the future with a value. Call once. + /// procedure SetResult(const Value: T); - /// Settle the future with an error. Takes ownership of E. + /// + /// Settle the future with an error. Takes ownership of E. + /// procedure SetError(E: Exception); - /// Settle the future as cancelled. + /// + /// Settle the future as cancelled. + /// procedure SignalCancelled; end; @@ -124,9 +136,13 @@ TOBDPromise = class(TInterfacedObject, IOBDFuture, IOBDPromise) // EXCEPTIONS //------------------------------------------------------------------------------ type - /// Raised by Await when the future was cancelled. + /// + /// Raised by Await when the future was cancelled. + /// EOBDOperationCancelled = class(Exception); - /// Raised by Await when the timeout elapses. + /// + /// Raised by Await when the timeout elapses. + /// EOBDFutureTimeout = class(Exception); //------------------------------------------------------------------------------ @@ -135,9 +151,13 @@ EOBDFutureTimeout = class(Exception); function NewCancellationToken: IOBDCancellationToken; function NewPromise(const Token: IOBDCancellationToken = nil): IOBDPromise; -/// Future that's already completed with the given value. +/// +/// Future that's already completed with the given value. +/// function FromResult(const Value: T): IOBDFuture; -/// Future that's already faulted. +/// +/// Future that's already faulted. +/// function FromError(E: Exception): IOBDFuture; implementation @@ -145,11 +165,18 @@ implementation //============================================================================== // TOBDCancellationToken //============================================================================== + +//------------------------------------------------------------------------------ +// CANCEL +//------------------------------------------------------------------------------ procedure TOBDCancellationToken.Cancel; begin TInterlocked.Exchange(FCancelled, 1); end; +//------------------------------------------------------------------------------ +// IS CANCELLED +//------------------------------------------------------------------------------ function TOBDCancellationToken.IsCancelled: Boolean; begin Result := TInterlocked.CompareExchange(FCancelled, 0, 0) <> 0; @@ -158,6 +185,10 @@ function TOBDCancellationToken.IsCancelled: Boolean; //============================================================================== // TOBDPromise //============================================================================== + +//------------------------------------------------------------------------------ +// TOBDPROMISE +//------------------------------------------------------------------------------ constructor TOBDPromise.Create(const AToken: IOBDCancellationToken); begin inherited Create; @@ -168,6 +199,9 @@ constructor TOBDPromise.Create(const AToken: IOBDCancellationToken); FToken := AToken; end; +//------------------------------------------------------------------------------ +// TOBDPROMISE +//------------------------------------------------------------------------------ destructor TOBDPromise.Destroy; begin FHandlers.Free; @@ -177,24 +211,50 @@ destructor TOBDPromise.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// TOBDPROMISE +//------------------------------------------------------------------------------ function TOBDPromise.GetState: TOBDFutureState; begin FLock.Enter; try Result := FState; finally FLock.Leave; end; end; +//------------------------------------------------------------------------------ +// TOBDPROMISE +//------------------------------------------------------------------------------ function TOBDPromise.IsCompleted: Boolean; -begin Result := GetState <> fsPending; end; +begin + Result := GetState <> fsPending; +end; +//------------------------------------------------------------------------------ +// TOBDPROMISE +//------------------------------------------------------------------------------ function TOBDPromise.IsFaulted: Boolean; -begin Result := GetState = fsFaulted; end; +begin + Result := GetState = fsFaulted; +end; +//------------------------------------------------------------------------------ +// TOBDPROMISE +//------------------------------------------------------------------------------ function TOBDPromise.IsCancelled: Boolean; -begin Result := GetState = fsCancelled; end; +begin + Result := GetState = fsCancelled; +end; +//------------------------------------------------------------------------------ +// TOBDPROMISE +//------------------------------------------------------------------------------ function TOBDPromise.CancellationToken: IOBDCancellationToken; -begin Result := FToken; end; +begin + Result := FToken; +end; +//------------------------------------------------------------------------------ +// TOBDPROMISE +//------------------------------------------------------------------------------ procedure TOBDPromise.FireHandlers; var Snapshot: TArray>>; @@ -215,6 +275,9 @@ procedure TOBDPromise.FireHandlers; try H(Self_); except {swallow handler errors so one bad listener can't kill the others} end; end; +//------------------------------------------------------------------------------ +// TOBDPROMISE +//------------------------------------------------------------------------------ procedure TOBDPromise.SetResult(const Value: T); begin FLock.Enter; @@ -229,6 +292,9 @@ procedure TOBDPromise.SetResult(const Value: T); FireHandlers; end; +//------------------------------------------------------------------------------ +// TOBDPROMISE +//------------------------------------------------------------------------------ procedure TOBDPromise.SetError(E: Exception); begin FLock.Enter; @@ -249,6 +315,9 @@ procedure TOBDPromise.SetError(E: Exception); FireHandlers; end; +//------------------------------------------------------------------------------ +// TOBDPROMISE +//------------------------------------------------------------------------------ procedure TOBDPromise.SignalCancelled; begin FLock.Enter; @@ -262,6 +331,9 @@ procedure TOBDPromise.SignalCancelled; FireHandlers; end; +//------------------------------------------------------------------------------ +// TOBDPROMISE +//------------------------------------------------------------------------------ function TOBDPromise.Await(TimeoutMs: Cardinal): T; var WaitRes: TWaitResult; @@ -294,6 +366,9 @@ function TOBDPromise.Await(TimeoutMs: Cardinal): T; end; end; +//------------------------------------------------------------------------------ +// TOBDPROMISE +//------------------------------------------------------------------------------ function TOBDPromise.OnComplete( const Handler: TProc>): IOBDFuture; var @@ -321,16 +396,26 @@ function TOBDPromise.OnComplete( //============================================================================== // FACTORIES //============================================================================== + +//------------------------------------------------------------------------------ +// NEW CANCELLATION TOKEN +//------------------------------------------------------------------------------ function NewCancellationToken: IOBDCancellationToken; begin Result := TOBDCancellationToken.Create; end; +//------------------------------------------------------------------------------ +// NEW PROMISE +//------------------------------------------------------------------------------ function NewPromise(const Token: IOBDCancellationToken): IOBDPromise; begin Result := TOBDPromise.Create(Token); end; +//------------------------------------------------------------------------------ +// FROM RESULT +//------------------------------------------------------------------------------ function FromResult(const Value: T): IOBDFuture; var Promise: IOBDPromise; @@ -340,6 +425,9 @@ function FromResult(const Value: T): IOBDFuture; Result := Promise; end; +//------------------------------------------------------------------------------ +// FROM ERROR +//------------------------------------------------------------------------------ function FromError(E: Exception): IOBDFuture; var Promise: IOBDPromise; diff --git a/src/Utilities/OBD.Audit.pas b/src/Utilities/OBD.Audit.pas index 17a8e64f..da1d3cc1 100644 --- a/src/Utilities/OBD.Audit.pas +++ b/src/Utilities/OBD.Audit.pas @@ -44,22 +44,33 @@ TOBDAuditRecorder = class public constructor Create(ALogger: TOBDLogger); - /// Record an audit event at the appropriate log level. + /// + /// Record an audit event at the appropriate log level. + /// procedure RecordEvent(const Event: TOBDAuditEvent); - /// Convenience: record a success. + /// + /// Convenience: record a success. + /// procedure Success(const Actor, Action, Resource: string; const Detail: string = ''); - /// Convenience: record a failure. + /// + /// Convenience: record a failure. + /// procedure Failure(const Actor, Action, Resource: string; const Detail: string = ''); - /// Convenience: record a denied (policy violation). + /// + /// Convenience: record a denied (policy violation). + /// procedure Denied(const Actor, Action, Resource: string; const Detail: string = ''); end; implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDAuditRecorder.Create(ALogger: TOBDLogger); begin inherited Create; @@ -68,6 +79,9 @@ constructor TOBDAuditRecorder.Create(ALogger: TOBDLogger); FLogger := ALogger; end; +//------------------------------------------------------------------------------ +// OUTCOME NAME +//------------------------------------------------------------------------------ function TOBDAuditRecorder.OutcomeName(O: TOBDAuditOutcome): string; begin case O of @@ -78,6 +92,9 @@ function TOBDAuditRecorder.OutcomeName(O: TOBDAuditOutcome): string; end; end; +//------------------------------------------------------------------------------ +// SERIALIZE +//------------------------------------------------------------------------------ function TOBDAuditRecorder.Serialize(const Event: TOBDAuditEvent): string; var Obj: TJSONObject; @@ -97,6 +114,9 @@ function TOBDAuditRecorder.Serialize(const Event: TOBDAuditEvent): string; end; end; +//------------------------------------------------------------------------------ +// RECORD EVENT +//------------------------------------------------------------------------------ procedure TOBDAuditRecorder.RecordEvent(const Event: TOBDAuditEvent); var PreviousTag: string; @@ -119,24 +139,36 @@ procedure TOBDAuditRecorder.RecordEvent(const Event: TOBDAuditEvent); end; end; +//------------------------------------------------------------------------------ +// SUCCESS +//------------------------------------------------------------------------------ procedure TOBDAuditRecorder.Success(const Actor, Action, Resource, Detail: string); -var E: TOBDAuditEvent; +var + E: TOBDAuditEvent; begin E.Actor := Actor; E.Action := Action; E.Resource := Resource; E.Outcome := aoSuccess; E.Detail := Detail; RecordEvent(E); end; +//------------------------------------------------------------------------------ +// FAILURE +//------------------------------------------------------------------------------ procedure TOBDAuditRecorder.Failure(const Actor, Action, Resource, Detail: string); -var E: TOBDAuditEvent; +var + E: TOBDAuditEvent; begin E.Actor := Actor; E.Action := Action; E.Resource := Resource; E.Outcome := aoFailure; E.Detail := Detail; RecordEvent(E); end; +//------------------------------------------------------------------------------ +// DENIED +//------------------------------------------------------------------------------ procedure TOBDAuditRecorder.Denied(const Actor, Action, Resource, Detail: string); -var E: TOBDAuditEvent; +var + E: TOBDAuditEvent; begin E.Actor := Actor; E.Action := Action; E.Resource := Resource; E.Outcome := aoDenied; E.Detail := Detail; diff --git a/src/Utilities/OBD.Logger.Sinks.pas b/src/Utilities/OBD.Logger.Sinks.pas index 1d5623c5..b56300bf 100644 --- a/src/Utilities/OBD.Logger.Sinks.pas +++ b/src/Utilities/OBD.Logger.Sinks.pas @@ -140,7 +140,9 @@ TInMemorySink = class(TInterfacedObject, IOBDLogSink) destructor Destroy; override; procedure Write(const Event: TOBDLogEvent); procedure Flush; - /// Snapshot the buffer (oldest first). + /// + /// Snapshot the buffer (oldest first). + /// function Snapshot: TArray; procedure ClearEvents; property Capacity: Integer read FCapacity; @@ -158,6 +160,9 @@ function LogLevelName(L: TOBDLogLevel): string; implementation +//------------------------------------------------------------------------------ +// LOG LEVEL NAME +//------------------------------------------------------------------------------ function LogLevelName(L: TOBDLogLevel): string; begin case L of @@ -170,6 +175,9 @@ function LogLevelName(L: TOBDLogLevel): string; end; end; +//------------------------------------------------------------------------------ +// FORMAT LINE +//------------------------------------------------------------------------------ function FormatLine(const Event: TOBDLogEvent): string; begin if Event.Source <> '' then @@ -182,6 +190,9 @@ function FormatLine(const Event: TOBDLogEvent): string; LogLevelName(Event.Level), Event.Message]); end; +//------------------------------------------------------------------------------ +// APPEND UTF8 +//------------------------------------------------------------------------------ procedure AppendUtf8(const FilePath, Line: string); var Stream: TFileStream; @@ -206,6 +217,10 @@ procedure AppendUtf8(const FilePath, Line: string); //============================================================================== // TFileRotationSink //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TFileRotationSink.Create(const AFilePath: string; AMaxBytes: Int64; AMaxBackups: Integer); begin @@ -216,12 +231,18 @@ constructor TFileRotationSink.Create(const AFilePath: string; FMaxBackups := AMaxBackups; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TFileRotationSink.Destroy; begin FLock.Free; inherited; end; +//------------------------------------------------------------------------------ +// ROTATE IF NEEDED +//------------------------------------------------------------------------------ procedure TFileRotationSink.RotateIfNeeded; var I: Integer; @@ -243,11 +264,17 @@ procedure TFileRotationSink.RotateIfNeeded; TFile.Move(FFilePath, FFilePath + '.1'); end; +//------------------------------------------------------------------------------ +// APPEND +//------------------------------------------------------------------------------ procedure TFileRotationSink.Append(const S: string); begin AppendUtf8(FFilePath, S); end; +//------------------------------------------------------------------------------ +// WRITE +//------------------------------------------------------------------------------ procedure TFileRotationSink.Write(const Event: TOBDLogEvent); begin FLock.Enter; @@ -259,6 +286,9 @@ procedure TFileRotationSink.Write(const Event: TOBDLogEvent); end; end; +//------------------------------------------------------------------------------ +// FLUSH +//------------------------------------------------------------------------------ procedure TFileRotationSink.Flush; begin // Each Write closes the stream so no buffered data exists. @@ -267,6 +297,10 @@ procedure TFileRotationSink.Flush; //============================================================================== // TDailyRotationSink //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TDailyRotationSink.Create(const ADirectory, ABaseName, AExtension: string); begin @@ -278,18 +312,27 @@ constructor TDailyRotationSink.Create(const ADirectory, ABaseName, ForceDirectories(FDirectory); end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TDailyRotationSink.Destroy; begin FLock.Free; inherited; end; +//------------------------------------------------------------------------------ +// FILE FOR +//------------------------------------------------------------------------------ function TDailyRotationSink.FileFor(const D: TDateTime): string; begin Result := TPath.Combine(FDirectory, FBaseName + '-' + FormatDateTime('yyyymmdd', D) + FExtension); end; +//------------------------------------------------------------------------------ +// WRITE +//------------------------------------------------------------------------------ procedure TDailyRotationSink.Write(const Event: TOBDLogEvent); begin FLock.Enter; @@ -300,6 +343,9 @@ procedure TDailyRotationSink.Write(const Event: TOBDLogEvent); end; end; +//------------------------------------------------------------------------------ +// FLUSH +//------------------------------------------------------------------------------ procedure TDailyRotationSink.Flush; begin end; @@ -307,6 +353,10 @@ procedure TDailyRotationSink.Flush; //============================================================================== // TJsonLineSink //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TJsonLineSink.Create(const AFilePath: string); begin inherited Create; @@ -314,12 +364,18 @@ constructor TJsonLineSink.Create(const AFilePath: string); FFilePath := AFilePath; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TJsonLineSink.Destroy; begin FLock.Free; inherited; end; +//------------------------------------------------------------------------------ +// EVENT TO JSON +//------------------------------------------------------------------------------ function TJsonLineSink.EventToJson(const Event: TOBDLogEvent): string; var Obj: TJSONObject; @@ -337,6 +393,9 @@ function TJsonLineSink.EventToJson(const Event: TOBDLogEvent): string; end; end; +//------------------------------------------------------------------------------ +// WRITE +//------------------------------------------------------------------------------ procedure TJsonLineSink.Write(const Event: TOBDLogEvent); begin FLock.Enter; @@ -347,6 +406,9 @@ procedure TJsonLineSink.Write(const Event: TOBDLogEvent); end; end; +//------------------------------------------------------------------------------ +// FLUSH +//------------------------------------------------------------------------------ procedure TJsonLineSink.Flush; begin end; @@ -354,6 +416,10 @@ procedure TJsonLineSink.Flush; //============================================================================== // TConsoleSink //============================================================================== + +//------------------------------------------------------------------------------ +// WRITE +//------------------------------------------------------------------------------ procedure TConsoleSink.Write(const Event: TOBDLogEvent); begin // IsConsole is set by the runtime when {APPTYPE CONSOLE} is on; guard @@ -363,6 +429,9 @@ procedure TConsoleSink.Write(const Event: TOBDLogEvent); System.Writeln(FormatLine(Event)); end; +//------------------------------------------------------------------------------ +// FLUSH +//------------------------------------------------------------------------------ procedure TConsoleSink.Flush; begin end; @@ -370,6 +439,10 @@ procedure TConsoleSink.Flush; //============================================================================== // TInMemorySink //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TInMemorySink.Create(ACapacity: Integer); begin inherited Create; @@ -379,6 +452,9 @@ constructor TInMemorySink.Create(ACapacity: Integer); FEvents := TList.Create; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TInMemorySink.Destroy; begin FEvents.Free; @@ -386,6 +462,9 @@ destructor TInMemorySink.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// WRITE +//------------------------------------------------------------------------------ procedure TInMemorySink.Write(const Event: TOBDLogEvent); var Cb: TProc; @@ -402,16 +481,25 @@ procedure TInMemorySink.Write(const Event: TOBDLogEvent); try Cb(Event); except end; end; +//------------------------------------------------------------------------------ +// FLUSH +//------------------------------------------------------------------------------ procedure TInMemorySink.Flush; begin end; +//------------------------------------------------------------------------------ +// SNAPSHOT +//------------------------------------------------------------------------------ function TInMemorySink.Snapshot: TArray; begin FLock.Enter; try Result := FEvents.ToArray; finally FLock.Leave; end; end; +//------------------------------------------------------------------------------ +// CLEAR EVENTS +//------------------------------------------------------------------------------ procedure TInMemorySink.ClearEvents; begin FLock.Enter; diff --git a/src/Utilities/OBD.Logger.pas b/src/Utilities/OBD.Logger.pas index 55f101fb..1081033b 100644 --- a/src/Utilities/OBD.Logger.pas +++ b/src/Utilities/OBD.Logger.pas @@ -178,11 +178,17 @@ TOBDLogger = class /// property SourceTag: string read FSourceTag write FSourceTag; - /// Add an extra sink that receives every event. + /// + /// Add an extra sink that receives every event. + /// procedure RegisterSink(const Sink: IOBDLogSink); - /// Remove a previously-registered sink. + /// + /// Remove a previously-registered sink. + /// procedure UnregisterSink(const Sink: IOBDLogSink); - /// Number of registered sinks. + /// + /// Number of registered sinks. + /// function SinkCount: Integer; end; @@ -243,18 +249,27 @@ procedure TOBDLogger.RegisterSink(const Sink: IOBDLogSink); end; end; +//------------------------------------------------------------------------------ +// UNREGISTER SINK +//------------------------------------------------------------------------------ procedure TOBDLogger.UnregisterSink(const Sink: IOBDLogSink); begin FCriticalSection.Enter; try FSinks.Remove(Sink); finally FCriticalSection.Leave; end; end; +//------------------------------------------------------------------------------ +// SINK COUNT +//------------------------------------------------------------------------------ function TOBDLogger.SinkCount: Integer; begin FCriticalSection.Enter; try Result := FSinks.Count; finally FCriticalSection.Leave; end; end; +//------------------------------------------------------------------------------ +// MAP LEVEL +//------------------------------------------------------------------------------ function TOBDLogger.MapLevel(const Level: TLogLevel): TOBDLogLevel; begin // The legacy enum order matches TOBDLogLevel one-to-one, but explicit @@ -269,6 +284,9 @@ function TOBDLogger.MapLevel(const Level: TLogLevel): TOBDLogLevel; end; end; +//------------------------------------------------------------------------------ +// DISPATCH TO SINKS +//------------------------------------------------------------------------------ procedure TOBDLogger.DispatchToSinks(const Level: TLogLevel; const Message: string); var Snapshot: TArray; @@ -441,6 +459,9 @@ procedure TOBDLogger.Debug(const Message: string); Log(llDebug, Message); end; +//------------------------------------------------------------------------------ +// DEBUG +//------------------------------------------------------------------------------ procedure TOBDLogger.Debug(const Format: string; const Args: array of const); begin Log(llDebug, Format, Args); @@ -454,6 +475,9 @@ procedure TOBDLogger.Info(const Message: string); Log(llInfo, Message); end; +//------------------------------------------------------------------------------ +// INFO +//------------------------------------------------------------------------------ procedure TOBDLogger.Info(const Format: string; const Args: array of const); begin Log(llInfo, Format, Args); @@ -467,6 +491,9 @@ procedure TOBDLogger.Warning(const Message: string); Log(llWarning, Message); end; +//------------------------------------------------------------------------------ +// WARNING +//------------------------------------------------------------------------------ procedure TOBDLogger.Warning(const Format: string; const Args: array of const); begin Log(llWarning, Format, Args); @@ -480,6 +507,9 @@ procedure TOBDLogger.Error(const Message: string); Log(llError, Message); end; +//------------------------------------------------------------------------------ +// ERROR +//------------------------------------------------------------------------------ procedure TOBDLogger.Error(const Format: string; const Args: array of const); begin Log(llError, Format, Args); @@ -493,6 +523,9 @@ procedure TOBDLogger.Critical(const Message: string); Log(llCritical, Message); end; +//------------------------------------------------------------------------------ +// CRITICAL +//------------------------------------------------------------------------------ procedure TOBDLogger.Critical(const Format: string; const Args: array of const); begin Log(llCritical, Format, Args); diff --git a/src/Utilities/OBD.SecureSettings.pas b/src/Utilities/OBD.SecureSettings.pas index ab836745..075d8221 100644 --- a/src/Utilities/OBD.SecureSettings.pas +++ b/src/Utilities/OBD.SecureSettings.pas @@ -22,7 +22,9 @@ interface WinApi.Windows; type - /// Raised when DPAPI returns an error. + /// + /// Raised when DPAPI returns an error. + /// EOBDDpapiError = class(Exception); /// @@ -41,23 +43,37 @@ TOBDSecureSettings = class constructor Create(const AFilePath: string); destructor Destroy; override; - /// Read an encrypted value. Returns Default if missing or undecryptable. + /// + /// Read an encrypted value. Returns Default if missing or undecryptable. + /// function ReadString(const Section, Key, Default: string): string; - /// Write a value, DPAPI-encrypting first. + /// + /// Write a value, DPAPI-encrypting first. + /// procedure WriteString(const Section, Key, Value: string); - /// Remove a key (no-op if absent). + /// + /// Remove a key (no-op if absent). + /// procedure DeleteKey(const Section, Key: string); - /// Persist to disk. + /// + /// Persist to disk. + /// procedure Save; - /// List of section names (in the clear). + /// + /// List of section names (in the clear). + /// procedure ReadSections(Out: TStrings); property FilePath: string read FFilePath; end; -/// Encrypt arbitrary bytes with DPAPI (current user scope). +/// +/// Encrypt arbitrary bytes with DPAPI (current user scope). +/// function DPAPIEncrypt(const Plain: TBytes): TBytes; -/// Decrypt previously-encrypted bytes. +/// +/// Decrypt previously-encrypted bytes. +/// function DPAPIDecrypt(const Cipher: TBytes): TBytes; implementation @@ -75,12 +91,18 @@ DATA_BLOB = record end; PDATA_BLOB = ^DATA_BLOB; +//------------------------------------------------------------------------------ +// CRYPT PROTECT DATA +//------------------------------------------------------------------------------ function CryptProtectData(pDataIn: PDATA_BLOB; szDataDescr: PWideChar; pOptionalEntropy: PDATA_BLOB; pvReserved: Pointer; pPromptStruct: Pointer; dwFlags: DWORD; pDataOut: PDATA_BLOB): BOOL; stdcall; external 'crypt32.dll' name 'CryptProtectData'; +//------------------------------------------------------------------------------ +// CRYPT UNPROTECT DATA +//------------------------------------------------------------------------------ function CryptUnprotectData(pDataIn: PDATA_BLOB; ppszDataDescr: PPWideChar; pOptionalEntropy: PDATA_BLOB; pvReserved: Pointer; @@ -88,6 +110,9 @@ function CryptUnprotectData(pDataIn: PDATA_BLOB; pDataOut: PDATA_BLOB): BOOL; stdcall; external 'crypt32.dll' name 'CryptUnprotectData'; +//------------------------------------------------------------------------------ +// DPAPIENCRYPT +//------------------------------------------------------------------------------ function DPAPIEncrypt(const Plain: TBytes): TBytes; var In_, Out_: DATA_BLOB; @@ -109,6 +134,9 @@ function DPAPIEncrypt(const Plain: TBytes): TBytes; end; end; +//------------------------------------------------------------------------------ +// DPAPIDECRYPT +//------------------------------------------------------------------------------ function DPAPIDecrypt(const Cipher: TBytes): TBytes; var In_, Out_: DATA_BLOB; @@ -133,6 +161,10 @@ function DPAPIDecrypt(const Cipher: TBytes): TBytes; //============================================================================== // TOBDSecureSettings //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDSecureSettings.Create(const AFilePath: string); begin inherited Create; @@ -141,12 +173,18 @@ constructor TOBDSecureSettings.Create(const AFilePath: string); FIni := TMemIniFile.Create(FFilePath); end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDSecureSettings.Destroy; begin FIni.Free; inherited; end; +//------------------------------------------------------------------------------ +// PROTECT STRING +//------------------------------------------------------------------------------ function TOBDSecureSettings.ProtectString(const Plain: string): string; var Bytes, Encrypted: TBytes; @@ -156,6 +194,9 @@ function TOBDSecureSettings.ProtectString(const Plain: string): string; Result := TNetEncoding.Base64.EncodeBytesToString(Encrypted); end; +//------------------------------------------------------------------------------ +// UNPROTECT STRING +//------------------------------------------------------------------------------ function TOBDSecureSettings.UnprotectString(const Cipher: string): string; var Encrypted, Decrypted: TBytes; @@ -166,6 +207,9 @@ function TOBDSecureSettings.UnprotectString(const Cipher: string): string; Result := TEncoding.UTF8.GetString(Decrypted); end; +//------------------------------------------------------------------------------ +// READ STRING +//------------------------------------------------------------------------------ function TOBDSecureSettings.ReadString(const Section, Key, Default: string): string; var Cipher: string; @@ -182,21 +226,33 @@ function TOBDSecureSettings.ReadString(const Section, Key, Default: string): str end; end; +//------------------------------------------------------------------------------ +// WRITE STRING +//------------------------------------------------------------------------------ procedure TOBDSecureSettings.WriteString(const Section, Key, Value: string); begin FIni.WriteString(Section, Key, ProtectString(Value)); end; +//------------------------------------------------------------------------------ +// DELETE KEY +//------------------------------------------------------------------------------ procedure TOBDSecureSettings.DeleteKey(const Section, Key: string); begin FIni.DeleteKey(Section, Key); end; +//------------------------------------------------------------------------------ +// SAVE +//------------------------------------------------------------------------------ procedure TOBDSecureSettings.Save; begin FIni.UpdateFile; end; +//------------------------------------------------------------------------------ +// READ SECTIONS +//------------------------------------------------------------------------------ procedure TOBDSecureSettings.ReadSections(Out: TStrings); begin FIni.ReadSections(Out); diff --git a/src/Utilities/OBD.Security.AttemptCounter.pas b/src/Utilities/OBD.Security.AttemptCounter.pas index a3fee055..8f00bbf5 100644 --- a/src/Utilities/OBD.Security.AttemptCounter.pas +++ b/src/Utilities/OBD.Security.AttemptCounter.pas @@ -45,26 +45,41 @@ TOBDAttemptCounter = class /// function IsAllowed(const Identity: string; out WaitSeconds: Integer): Boolean; - /// Record a failed attempt — applies exponential back-off. + /// + /// Record a failed attempt — applies exponential back-off. + /// procedure RegisterFailure(const Identity: string); - /// Record a success — resets the counter. + /// + /// Record a success — resets the counter. + /// procedure RegisterSuccess(const Identity: string); - /// Reset the counter without recording a success. + /// + /// Reset the counter without recording a success. + /// procedure Reset(const Identity: string); function State(const Identity: string): TOBDAttemptState; published - /// Number of failed attempts allowed before the back-off kicks in. + /// + /// Number of failed attempts allowed before the back-off kicks in. + /// property FreeAttempts: Integer read FFreeAttempts write FFreeAttempts; - /// Initial lockout in seconds (doubles per subsequent failure). + /// + /// Initial lockout in seconds (doubles per subsequent failure). + /// property BaseLockoutSeconds: Integer read FBaseLockoutSeconds write FBaseLockoutSeconds; - /// Cap on the lockout (default 1 day). + /// + /// Cap on the lockout (default 1 day). + /// property MaxLockoutSeconds: Integer read FMaxLockoutSeconds write FMaxLockoutSeconds; end; implementation +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDAttemptCounter.Create; begin inherited Create; @@ -75,6 +90,9 @@ constructor TOBDAttemptCounter.Create; FMaxLockoutSeconds := 86400; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDAttemptCounter.Destroy; begin FStates.Free; @@ -82,6 +100,9 @@ destructor TOBDAttemptCounter.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// COMPUTE LOCKOUT +//------------------------------------------------------------------------------ function TOBDAttemptCounter.ComputeLockout(Failures: Integer): Integer; var Steps, Lockout: Int64; @@ -96,6 +117,9 @@ function TOBDAttemptCounter.ComputeLockout(Failures: Integer): Integer; Result := Lockout; end; +//------------------------------------------------------------------------------ +// IS ALLOWED +//------------------------------------------------------------------------------ function TOBDAttemptCounter.IsAllowed(const Identity: string; out WaitSeconds: Integer): Boolean; var @@ -115,6 +139,9 @@ function TOBDAttemptCounter.IsAllowed(const Identity: string; end; end; +//------------------------------------------------------------------------------ +// REGISTER FAILURE +//------------------------------------------------------------------------------ procedure TOBDAttemptCounter.RegisterFailure(const Identity: string); var S: TOBDAttemptState; @@ -139,17 +166,26 @@ procedure TOBDAttemptCounter.RegisterFailure(const Identity: string); end; end; +//------------------------------------------------------------------------------ +// REGISTER SUCCESS +//------------------------------------------------------------------------------ procedure TOBDAttemptCounter.RegisterSuccess(const Identity: string); begin Reset(Identity); end; +//------------------------------------------------------------------------------ +// RESET +//------------------------------------------------------------------------------ procedure TOBDAttemptCounter.Reset(const Identity: string); begin FLock.Enter; try FStates.Remove(Identity); finally FLock.Leave; end; end; +//------------------------------------------------------------------------------ +// STATE +//------------------------------------------------------------------------------ function TOBDAttemptCounter.State(const Identity: string): TOBDAttemptState; begin FLock.Enter; diff --git a/src/Utilities/OBD.Security.Nonce.pas b/src/Utilities/OBD.Security.Nonce.pas index e00655f8..6538c149 100644 --- a/src/Utilities/OBD.Security.Nonce.pas +++ b/src/Utilities/OBD.Security.Nonce.pas @@ -64,17 +64,25 @@ TOBDNonceVault = class /// procedure Redeem(const Nonce: string); - /// True if Nonce is currently valid + unredeemed. + /// + /// True if Nonce is currently valid + unredeemed. + /// function IsValid(const Nonce: string): Boolean; - /// Drop every issued and used nonce (e.g. on session reset). + /// + /// Drop every issued and used nonce (e.g. on session reset). + /// procedure Reset; - /// Number of currently-valid nonces. + /// + /// Number of currently-valid nonces. + /// function PendingCount: Integer; property TtlSeconds: Integer read FTtlSeconds write FTtlSeconds; - /// Nonce byte length before hex encoding (default 16 → 32 hex chars). + /// + /// Nonce byte length before hex encoding (default 16 → 32 hex chars). + /// property NonceLength: Integer read FNonceLength write FNonceLength; end; @@ -84,9 +92,16 @@ implementation WinApi.Windows; // Crypto-quality random bytes via Windows RtlGenRandom (advapi32!SystemFunction036). + +//------------------------------------------------------------------------------ +// SYSTEM FUNCTION036 +//------------------------------------------------------------------------------ function SystemFunction036(RandomBuffer: Pointer; RandomBufferLength: ULONG): BOOL; stdcall; external 'advapi32.dll' name 'SystemFunction036'; +//------------------------------------------------------------------------------ +// FILL SECURE RANDOM +//------------------------------------------------------------------------------ procedure FillSecureRandom(var Buffer: TBytes); begin if Length(Buffer) = 0 then Exit; @@ -96,6 +111,9 @@ procedure FillSecureRandom(var Buffer: TBytes); [GetLastError]); end; +//------------------------------------------------------------------------------ +// BYTES TO HEX +//------------------------------------------------------------------------------ function BytesToHex(const Bytes: TBytes): string; const HexDigits: array[0..15] of Char = @@ -120,6 +138,10 @@ function BytesToHex(const Bytes: TBytes): string; //============================================================================== // TOBDNonceVault //============================================================================== + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TOBDNonceVault.Create(ATtlSeconds, ANonceLength: Integer); begin inherited Create; @@ -134,6 +156,9 @@ constructor TOBDNonceVault.Create(ATtlSeconds, ANonceLength: Integer); FNonceLength := ANonceLength; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TOBDNonceVault.Destroy; begin FActive.Free; @@ -142,6 +167,9 @@ destructor TOBDNonceVault.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// GENERATE NONCE HEX +//------------------------------------------------------------------------------ function TOBDNonceVault.GenerateNonceHex: string; var Buffer: TBytes; @@ -151,6 +179,9 @@ function TOBDNonceVault.GenerateNonceHex: string; Result := BytesToHex(Buffer); end; +//------------------------------------------------------------------------------ +// SWEEP EXPIRED +//------------------------------------------------------------------------------ procedure TOBDNonceVault.SweepExpired; var Now_: TDateTime; @@ -179,6 +210,9 @@ procedure TOBDNonceVault.SweepExpired; for Key in ToDrop do FUsed.Remove(Key); end; +//------------------------------------------------------------------------------ +// ISSUE +//------------------------------------------------------------------------------ function TOBDNonceVault.Issue: string; begin FLock.Enter; @@ -191,6 +225,9 @@ function TOBDNonceVault.Issue: string; end; end; +//------------------------------------------------------------------------------ +// REDEEM +//------------------------------------------------------------------------------ procedure TOBDNonceVault.Redeem(const Nonce: string); var Issued: TDateTime; @@ -214,6 +251,9 @@ procedure TOBDNonceVault.Redeem(const Nonce: string); end; end; +//------------------------------------------------------------------------------ +// IS VALID +//------------------------------------------------------------------------------ function TOBDNonceVault.IsValid(const Nonce: string): Boolean; var Issued: TDateTime; @@ -228,6 +268,9 @@ function TOBDNonceVault.IsValid(const Nonce: string): Boolean; end; end; +//------------------------------------------------------------------------------ +// RESET +//------------------------------------------------------------------------------ procedure TOBDNonceVault.Reset; begin FLock.Enter; @@ -239,6 +282,9 @@ procedure TOBDNonceVault.Reset; end; end; +//------------------------------------------------------------------------------ +// PENDING COUNT +//------------------------------------------------------------------------------ function TOBDNonceVault.PendingCount: Integer; begin FLock.Enter; diff --git a/src/VIN/OBD.VIN.Constants.pas b/src/VIN/OBD.VIN.Constants.pas index 01bf50c9..6c42ce6a 100644 --- a/src/VIN/OBD.VIN.Constants.pas +++ b/src/VIN/OBD.VIN.Constants.pas @@ -1,12 +1,13 @@ -//------------------------------------------------------------------------------ +//------------------------------------------------------------------------------ // UNIT : OBD.VIN.Constants.pas -// CONTENTS : OBD VIN Constants +// CONTENTS : OBD VIN Constants (regions, countries, manufacturers, plants) // VERSION : 1.0 // TARGET : Embarcadero Delphi 11 or higher // AUTHOR : Ernst Reidinga (ERDesigns) // STATUS : Open source under Apache 2.0 library -// COMPATIBILITY : Windows 7, 8/8.1, 10, 11 +// COMPATIBILITY : Windows / macOS / Linux / iOS / Android // RELEASE DATE : 08/04/2024 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) //------------------------------------------------------------------------------ unit OBD.VIN.Constants; @@ -18,734 +19,21 @@ interface OBD.VIN.Types; //------------------------------------------------------------------------------ -// VIN REGIONS -//------------------------------------------------------------------------------ -const - VINRegions: array[0..5] of TVINRegion = ( - (RangeStart: 'A'; RangeEnd: 'C'; Name: 'Africa'), - (RangeStart: 'J'; RangeEnd: 'R'; Name: 'Asia'), - (RangeStart: 'S'; RangeEnd: 'Z'; Name: 'Europe'), - (RangeStart: '1'; RangeEnd: '5'; Name: 'North America'), - (RangeStart: '6'; RangeEnd: '7'; Name: 'Oceania'), - (RangeStart: '8'; RangeEnd: '9'; Name: 'South America') - ); - -//------------------------------------------------------------------------------ -// VIN COUNTRIES -//------------------------------------------------------------------------------ -const - VINCountries: array[0..133] of TVINCountry = ( - (RangeStart: 'AA'; RangeEnd: 'AH'; Name: 'South Africa'; Code: 'ZA'), - (RangeStart: 'AJ'; RangeEnd: 'AK'; Name: 'Ivory Coast'; Code: 'CI'), - (RangeStart: 'AL'; RangeEnd: 'AM'; Name: 'Lesotho'; Code: 'LS'), - (RangeStart: 'AN'; RangeEnd: 'AP'; Name: 'Botswana'; Code: 'BW'), - (RangeStart: 'AR'; RangeEnd: 'AS'; Name: 'Namibia'; Code: 'NA'), - (RangeStart: 'AT'; RangeEnd: 'AU'; Name: 'Madagascar'; Code: 'MG'), - (RangeStart: 'AV'; RangeEnd: 'AW'; Name: 'Mauritius'; Code: 'MU'), - (RangeStart: 'AX'; RangeEnd: 'AY'; Name: 'Tunisia'; Code: 'TN'), - (RangeStart: 'AZ'; RangeEnd: 'A1'; Name: 'Cyprus'; Code: 'CY'), - (RangeStart: 'A2'; RangeEnd: 'A3'; Name: 'Zimbabwe'; Code: 'ZW'), - (RangeStart: 'A4'; RangeEnd: 'A5'; Name: 'Mozambique'; Code: 'MZ'), - (RangeStart: 'BA'; RangeEnd: 'BB'; Name: 'Angola'; Code: 'AO'), - (RangeStart: 'BC'; RangeEnd: 'BC'; Name: 'Ethiopia'; Code: 'ET'), - (RangeStart: 'BF'; RangeEnd: 'BG'; Name: 'Kenya'; Code: 'KE'), - (RangeStart: 'BH'; RangeEnd: 'BH'; Name: 'Rwanda'; Code: 'RW'), - (RangeStart: 'BL'; RangeEnd: 'BL'; Name: 'Nigeria'; Code: 'NG'), - (RangeStart: 'BR'; RangeEnd: 'BR'; Name: 'Algeria'; Code: 'DZ'), - (RangeStart: 'BT'; RangeEnd: 'BT'; Name: 'Swaziland'; Code: 'SZ'), - (RangeStart: 'BU'; RangeEnd: 'BU'; Name: 'Uganda'; Code: 'UG'), - (RangeStart: 'B3'; RangeEnd: 'B4'; Name: 'Libya'; Code: 'LY'), - (RangeStart: 'CA'; RangeEnd: 'CB'; Name: 'Egypt'; Code: 'EG'), - (RangeStart: 'CF'; RangeEnd: 'CG'; Name: 'Morocco'; Code: 'MA'), - (RangeStart: 'CL'; RangeEnd: 'CM'; Name: 'Zambia'; Code: 'ZM'), - (RangeStart: 'EA'; RangeEnd: 'E0'; Name: 'Russia'; Code: 'RU'), - (RangeStart: 'HA'; RangeEnd: 'H0'; Name: 'China'; Code: 'CN'), - (RangeStart: 'JA'; RangeEnd: 'J0'; Name: 'Japan'; Code: 'JP'), - (RangeStart: 'KF'; RangeEnd: 'KH'; Name: 'Israel'; Code: 'IL'), - (RangeStart: 'KL'; RangeEnd: 'KR'; Name: 'South Korea'; Code: 'KR'), - (RangeStart: 'KS'; RangeEnd: 'KT'; Name: 'Jordan'; Code: 'JO'), - (RangeStart: 'K1'; RangeEnd: 'K3'; Name: 'South Korea'; Code: 'KR'), - (RangeStart: 'K5'; RangeEnd: 'K5'; Name: 'Kyrgyzstan'; Code: 'KG'), - (RangeStart: 'LA'; RangeEnd: 'L0'; Name: 'China'; Code: 'CN'), - (RangeStart: 'MA'; RangeEnd: 'ME'; Name: 'India'; Code: 'IN'), - (RangeStart: 'MF'; RangeEnd: 'MK'; Name: 'Indonesia'; Code: 'ID'), - (RangeStart: 'ML'; RangeEnd: 'MR'; Name: 'Thailand'; Code: 'TH'), - (RangeStart: 'MS'; RangeEnd: 'MS'; Name: 'Myanmar'; Code: 'MM'), - (RangeStart: 'MU'; RangeEnd: 'MU'; Name: 'Mongolia'; Code: 'MN'), - (RangeStart: 'MX'; RangeEnd: 'MX'; Name: 'Kazakhstan'; Code: 'KZ'), - (RangeStart: 'M1'; RangeEnd: 'M0'; Name: 'India'; Code: 'IN'), - (RangeStart: 'NA'; RangeEnd: 'NE'; Name: 'Iran'; Code: 'IR'), - (RangeStart: 'NF'; RangeEnd: 'NG'; Name: 'Pakistan'; Code: 'PK'), - (RangeStart: 'NJ'; RangeEnd: 'NJ'; Name: 'Iraq'; Code: 'IQ'), - (RangeStart: 'NL'; RangeEnd: 'NR'; Name: 'Turkey'; Code: 'TR'), - (RangeStart: 'NS'; RangeEnd: 'NT'; Name: 'Uzbekistan'; Code: 'UZ'), - (RangeStart: 'NV'; RangeEnd: 'NV'; Name: 'Azerbaijan'; Code: 'AZ'), - (RangeStart: 'NX'; RangeEnd: 'NX'; Name: 'Tajikistan'; Code: 'TJ'), - (RangeStart: 'NY'; RangeEnd: 'NY'; Name: 'Armenia'; Code: 'AM'), - (RangeStart: 'N1'; RangeEnd: 'N5'; Name: 'Iran'; Code: 'IR'), - (RangeStart: 'N7'; RangeEnd: 'N8'; Name: 'Turkey'; Code: 'TR'), - (RangeStart: 'PA'; RangeEnd: 'PC'; Name: 'Philippines'; Code: 'PH'), - (RangeStart: 'PF'; RangeEnd: 'PG'; Name: 'Singapore'; Code: 'SG'), - (RangeStart: 'PL'; RangeEnd: 'PR'; Name: 'Malaysia'; Code: 'MY'), - (RangeStart: 'PS'; RangeEnd: 'PT'; Name: 'Bangladesh'; Code: 'BD'), - (RangeStart: 'P5'; RangeEnd: 'P0'; Name: 'India'; Code: 'IN'), - (RangeStart: 'RA'; RangeEnd: 'RB'; Name: 'United Arab Emirates'; Code: 'AE'), - (RangeStart: 'RF'; RangeEnd: 'RK'; Name: 'Taiwan'; Code: 'TW'), - (RangeStart: 'RL'; RangeEnd: 'RM'; Name: 'Vietnam'; Code: 'VN'), - (RangeStart: 'RP'; RangeEnd: 'RP'; Name: 'Laos'; Code: 'LA'), - (RangeStart: 'RS'; RangeEnd: 'RT'; Name: 'Saudi Arabia'; Code: 'SA'), - (RangeStart: 'RU'; RangeEnd: 'RW'; Name: 'Russia'; Code: 'RU'), - (RangeStart: 'R1'; RangeEnd: 'R7'; Name: 'Hong Kong'; Code: 'HK'), - (RangeStart: 'SA'; RangeEnd: 'SM'; Name: 'United Kingdom'; Code: 'GB'), - (RangeStart: 'SN'; RangeEnd: 'ST'; Name: 'Germany'; Code: 'DE'), - (RangeStart: 'SU'; RangeEnd: 'SZ'; Name: 'Poland'; Code: 'PL'), - (RangeStart: 'S1'; RangeEnd: 'S2'; Name: 'Latvia'; Code: 'LV'), - (RangeStart: 'S3'; RangeEnd: 'S3'; Name: 'Georgia'; Code: 'GE'), - (RangeStart: 'S4'; RangeEnd: 'S4'; Name: 'Iceland'; Code: 'IS'), - (RangeStart: 'TA'; RangeEnd: 'TH'; Name: 'Switzerland'; Code: 'CH'), - (RangeStart: 'TJ'; RangeEnd: 'TP'; Name: 'Czech Republic'; Code: 'CZ'), - (RangeStart: 'TR'; RangeEnd: 'TV'; Name: 'Hungary'; Code: 'HU'), - (RangeStart: 'TW'; RangeEnd: 'T1'; Name: 'Portugal'; Code: 'PT'), - (RangeStart: 'T3'; RangeEnd: 'T5'; Name: 'Republic of Serbia'; Code: 'RS'), - (RangeStart: 'T6'; RangeEnd: 'T6'; Name: 'Andorra'; Code: 'AD'), - (RangeStart: 'T7'; RangeEnd: 'T8'; Name: 'Netherlands'; Code: 'NL'), - (RangeStart: 'UA'; RangeEnd: 'UC'; Name: 'Spain'; Code: 'ES'), - (RangeStart: 'UH'; RangeEnd: 'UM'; Name: 'Denmark'; Code: 'DK'), - (RangeStart: 'UN'; RangeEnd: 'UR'; Name: 'Ireland'; Code: 'IE'), - (RangeStart: 'UU'; RangeEnd: 'UX'; Name: 'Romania'; Code: 'RO'), - (RangeStart: 'U1'; RangeEnd: 'U2'; Name: 'Macedonia'; Code: 'MK'), - (RangeStart: 'U5'; RangeEnd: 'U7'; Name: 'Slovakia'; Code: 'SK'), - (RangeStart: 'U8'; RangeEnd: 'U0'; Name: 'Bosnia & Herzegovina'; Code: 'BA'), - (RangeStart: 'VA'; RangeEnd: 'VE'; Name: 'Austria'; Code: 'AT'), - (RangeStart: 'VF'; RangeEnd: 'VR'; Name: 'France'; Code: 'FR'), - (RangeStart: 'VS'; RangeEnd: 'VW'; Name: 'Spain'; Code: 'ES'), - (RangeStart: 'VX'; RangeEnd: 'V2'; Name: 'France'; Code: 'FR'), - (RangeStart: 'V3'; RangeEnd: 'V5'; Name: 'Croatia'; Code: 'HR'), - (RangeStart: 'V6'; RangeEnd: 'V8'; Name: 'Estonia'; Code: 'EE'), - (RangeStart: 'WA'; RangeEnd: 'W0'; Name: 'Germany'; Code: 'DE'), - (RangeStart: 'XA'; RangeEnd: 'XC'; Name: 'Bulgaria'; Code: 'BG'), - (RangeStart: 'XD'; RangeEnd: 'XE'; Name: 'Russia'; Code: 'RU'), - (RangeStart: 'XF'; RangeEnd: 'XH'; Name: 'Greece'; Code: 'GR'), - (RangeStart: 'XJ'; RangeEnd: 'XK'; Name: 'Russia'; Code: 'RU'), - (RangeStart: 'XL'; RangeEnd: 'XR'; Name: 'Netherlands'; Code: 'NL'), - (RangeStart: 'XS'; RangeEnd: 'XW'; Name: 'Russia'; Code: 'RU'), - (RangeStart: 'XX'; RangeEnd: 'XY'; Name: 'Luxembourg'; Code: 'LU'), - (RangeStart: 'XZ'; RangeEnd: 'X0'; Name: 'Russia'; Code: 'RU'), - (RangeStart: 'YA'; RangeEnd: 'YE'; Name: 'Belgium'; Code: 'BE'), - (RangeStart: 'YF'; RangeEnd: 'YK'; Name: 'Finland'; Code: 'FI'), - (RangeStart: 'YN'; RangeEnd: 'YN'; Name: 'Malta'; Code: 'MT'), - (RangeStart: 'YS'; RangeEnd: 'YW'; Name: 'Sweden'; Code: 'SE'), - (RangeStart: 'YX'; RangeEnd: 'Y2'; Name: 'Norway'; Code: 'NO'), - (RangeStart: 'Y3'; RangeEnd: 'Y5'; Name: 'Belarus'; Code: 'BY'), - (RangeStart: 'Y6'; RangeEnd: 'Y8'; Name: 'Ukraine'; Code: 'UA'), - (RangeStart: 'ZA'; RangeEnd: 'ZU'; Name: 'Italy'; Code: 'IT'), - (RangeStart: 'ZX'; RangeEnd: 'ZZ'; Name: 'Slovenia'; Code: 'SI'), - (RangeStart: 'Z1'; RangeEnd: 'Z1'; Name: 'San Marino'; Code: 'SM'), - (RangeStart: 'Z3'; RangeEnd: 'Z5'; Name: 'Lithuania'; Code: 'LT'), - (RangeStart: 'Z6'; RangeEnd: 'Z0'; Name: 'Russia'; Code: 'RU'), - (RangeStart: '1A'; RangeEnd: '10'; Name: 'United States'; Code: 'US'), - (RangeStart: '13'; RangeEnd: '13'; Name: 'United States'; Code: 'US'), - (RangeStart: '2A'; RangeEnd: '25'; Name: 'Canada'; Code: 'CA'), - (RangeStart: '3A'; RangeEnd: '3X'; Name: 'Mexico'; Code: 'MX'), - (RangeStart: '34'; RangeEnd: '34'; Name: 'Nicaragua'; Code: 'NI'), - (RangeStart: '35'; RangeEnd: '35'; Name: 'Dominican Republic'; Code: 'DO'), - (RangeStart: '36'; RangeEnd: '36'; Name: 'Honduras'; Code: 'HN'), - (RangeStart: '37'; RangeEnd: '37'; Name: 'Panama'; Code: 'PA'), - (RangeStart: '38'; RangeEnd: '39'; Name: 'Puerto Rico'; Code: 'PR'), - (RangeStart: '4A'; RangeEnd: '40'; Name: 'United States'; Code: 'US'), - (RangeStart: '5A'; RangeEnd: '50'; Name: 'United States'; Code: 'US'), - (RangeStart: '5G'; RangeEnd: '5G'; Name: 'United States'; Code: 'US'), - (RangeStart: '6A'; RangeEnd: '6X'; Name: 'Australia'; Code: 'AU'), - (RangeStart: '6Y'; RangeEnd: '61'; Name: 'New Zealand'; Code: 'NZ'), - (RangeStart: '7A'; RangeEnd: '70'; Name: 'United States'; Code: 'US'), - (RangeStart: '8A'; RangeEnd: '8E'; Name: 'Argentina'; Code: 'AR'), - (RangeStart: '8F'; RangeEnd: '8G'; Name: 'Chile'; Code: 'CL'), - (RangeStart: '8L'; RangeEnd: '8N'; Name: 'Ecuador'; Code: 'EC'), - (RangeStart: '8S'; RangeEnd: '8T'; Name: 'Peru'; Code: 'PE'), - (RangeStart: '8X'; RangeEnd: '8Z'; Name: 'Venezuela'; Code: 'VE'), - (RangeStart: '82'; RangeEnd: '82'; Name: 'Bolivia'; Code: 'BO'), - (RangeStart: '84'; RangeEnd: '84'; Name: 'Costa Rica'; Code: 'CR'), - (RangeStart: '9A'; RangeEnd: '9E'; Name: 'Brazil'; Code: 'BR'), - (RangeStart: '9F'; RangeEnd: '9G'; Name: 'Colombia'; Code: 'CO'), - (RangeStart: '9S'; RangeEnd: '9V'; Name: 'Uruguay'; Code: 'UY'), - (RangeStart: '91'; RangeEnd: '90'; Name: 'Brazil'; Code: 'BR') - ); - -//------------------------------------------------------------------------------ -// VIN MANUFACTURERS -//------------------------------------------------------------------------------ -const - VINManufacturers: array[0..553] of TVINManufacturer = ( - (Code: 'AAV'; Name: 'Volkswagen'), - (Code: 'AC5'; Name: 'Hyundai'), - (Code: 'ADD'; Name: 'Hyundai'), - (Code: 'AFA'; Name: 'Ford'), - (Code: 'AHT'; Name: 'Toyota'), - (Code: 'JA3'; Name: 'Mitsubishi'), - (Code: 'JA4'; Name: 'Mitsubishi'), - (Code: 'JA'; Name: 'Isuzu'), - (Code: 'JD'; Name: 'Daihatsu'), - (Code: 'JF'; Name: 'Subaru'), - (Code: 'JHA'; Name: 'Hino'), - (Code: 'JHB'; Name: 'Hino'), - (Code: 'JHC'; Name: 'Hino'), - (Code: 'JHD'; Name: 'Hino'), - (Code: 'JHE'; Name: 'Hino'), - (Code: 'JHF'; Name: 'Honda'), - (Code: 'JHG'; Name: 'Honda'), - (Code: 'JHL'; Name: 'Honda'), - (Code: 'JHM'; Name: 'Honda'), - (Code: 'JHN'; Name: 'Honda'), - (Code: 'JHZ'; Name: 'Honda'), - (Code: 'JH1'; Name: 'Honda'), - (Code: 'JH2'; Name: 'Honda'), - (Code: 'JH3'; Name: 'Honda'), - (Code: 'JH4'; Name: 'Honda'), - (Code: 'JH5'; Name: 'Honda'), - (Code: 'JK'; Name: 'Kawasaki'), - (Code: 'JL5'; Name: 'Mitsubishi'), - (Code: 'JM1'; Name: 'Mazda'), - (Code: 'JMB'; Name: 'Mitsubishi'), - (Code: 'JMY'; Name: 'Mitsubishi'), - (Code: 'JMZ'; Name: 'Mazda'), - (Code: 'JN'; Name: 'Infinity'), - (Code: 'JS'; Name: 'Suzuki'), - (Code: 'JT3'; Name: 'Toyota'), - (Code: 'JT'; Name: 'Lexus'), - (Code: 'JY'; Name: 'Yamaha'), - (Code: 'KL'; Name: 'Daewoo'), - (Code: 'KM'; Name: 'Hyundai'), - (Code: 'KMY'; Name: 'Daelim'), - (Code: 'KM1'; Name: 'Hyosung'), - (Code: 'KN'; Name: 'Kia'), - (Code: 'KNM'; Name: 'Renault'), - (Code: 'KPA'; Name: 'SsangYong'), - (Code: 'KPT'; Name: 'SsangYong'), - (Code: 'LAE'; Name: 'Jinan Qingqi'), - (Code: 'LAL'; Name: 'Honda'), - (Code: 'LAN'; Name: 'Changzhou Yamasaki'), - (Code: 'LBB'; Name: 'Keeway'), - (Code: 'LBE'; Name: 'Beijing Hyundai'), - (Code: 'LBM'; Name: 'Zongshen Piaggio'), - (Code: 'LBP'; Name: 'Yamaha'), - (Code: 'LB2'; Name: 'Geely'), - (Code: 'LCE'; Name: 'Hangzhou Chunfeng'), - (Code: 'LDC'; Name: 'Peugeot'), - (Code: 'LDD'; Name: 'Dandong'), - (Code: 'LDF'; Name: 'Dezhou Fulu'), - (Code: 'LDN'; Name: 'SouEast'), - (Code: 'LDY'; Name: 'Zhongtong Coach'), - (Code: 'LET'; Name: 'Jiangling-Isuzu'), - (Code: 'LE4'; Name: 'Beijing Benz'), - (Code: 'LFB'; Name: 'FAW'), - (Code: 'LFG'; Name: 'Taizhou Chuanl '), - (Code: 'LFP'; Name: 'FAW'), - (Code: 'LFT'; Name: 'FAW'), - (Code: 'LFV'; Name: 'FAW'), - (Code: 'LFW'; Name: 'FAW'), - (Code: 'LFY'; Name: 'Changshu'), - (Code: 'LGB'; Name: 'Dong Feng'), - (Code: 'LGH'; Name: 'Qoros'), - (Code: 'LGX'; Name: 'BYD'), - (Code: 'LHB'; Name: 'Beijing Automotive Industry Holding'), - (Code: 'LH1'; Name: 'FAW'), - (Code: 'LJC'; Name: 'JAC'), - (Code: 'LJ1'; Name: 'JAC'), - (Code: 'LKL'; Name: 'Suzhou King Long'), - (Code: 'LL6'; Name: 'Hunan Changfeng'), - (Code: 'LL8'; Name: 'Linhai'), - (Code: 'LMC'; Name: 'Suzuki'), - (Code: 'LPR'; Name: 'Yamaha'), - (Code: 'LPS'; Name: 'Polestar'), - (Code: 'LRW'; Name: 'Tesla'), - (Code: 'LSG'; Name: 'General Motors'), - (Code: 'LSJ'; Name: 'MG'), - (Code: 'LSV'; Name: 'Volkswagen'), - (Code: 'LSY'; Name: 'Brilliance Zhonghua'), - (Code: 'LTP'; Name: 'National Electric Vehicle Sweden AB'), - (Code: 'LTV'; Name: 'Toyota'), - (Code: 'LUC'; Name: 'Honda'), - (Code: 'LVS'; Name: 'Ford'), - (Code: 'LVV'; Name: 'Chery'), - (Code: 'LVZ'; Name: 'Dong Feng Sokon Motor Company'), - (Code: 'LV3'; Name: 'National Electric Vehicle Sweden AB'), - (Code: 'LZM'; Name: 'MAN'), - (Code: 'LZE'; Name: 'Isuzu'), - (Code: 'LZG'; Name: 'Shaanxi'), - (Code: 'LZP'; Name: 'Baotian'), - (Code: 'LZY'; Name: 'Yutong Zhengzhou,'), - (Code: 'LZZ'; Name: 'Chongqing Shuangzing Mech & Elec'), - (Code: 'L4B'; Name: 'Xingyue Group'), - (Code: 'L5C'; Name: 'KangDi)'), - (Code: 'L5K'; Name: 'Zhejiang Yongkang'), - (Code: 'L5N'; Name: 'Zhejiang Taotao'), - (Code: 'L5Y'; Name: 'Merato Motorcycle Taizhou Zhongneng'), - (Code: 'L85'; Name: 'Zhejiang Yongkang Huabao Electric Appliance'), - (Code: 'L8X'; Name: 'Zhejiang Summit Huawin Motorcycle'), - (Code: 'MAB'; Name: 'Mahindra & Mahindra'), - (Code: 'MAC'; Name: 'Mahindra & Mahindra'), - (Code: 'MAJ'; Name: 'Ford'), - (Code: 'MAK'; Name: 'Honda'), - (Code: 'MAL'; Name: 'Hyundai '), - (Code: 'MAT'; Name: 'Tata Motors'), - (Code: 'MA1'; Name: 'Mahindra & Mahindra'), - (Code: 'MA3'; Name: 'Suzuki'), - (Code: 'MA6'; Name: 'GM'), - (Code: 'MA7'; Name: 'Mitsubishi'), - (Code: 'MB8'; Name: 'Suzuki'), - (Code: 'MBH'; Name: 'Suzuki'), - (Code: 'MBJ'; Name: 'Toyota'), - (Code: 'MBR'; Name: 'Mercedes-Benz'), - (Code: 'MB1'; Name: 'Ashok Leyland'), - (Code: 'MCA'; Name: 'Fiat'), - (Code: 'MCB'; Name: 'GM'), - (Code: 'MC2'; Name: 'Volvo'), - (Code: 'MDH'; Name: 'Nissan'), - (Code: 'MD2'; Name: 'Bajaj'), - (Code: 'MD9'; Name: 'Shuttle Cars'), - (Code: 'MEC'; Name: 'Daimler'), - (Code: 'MEE'; Name: 'Renault'), - (Code: 'MEX'; Name: 'Volkswagen'), - (Code: 'MHF'; Name: 'Toyota'), - (Code: 'MHR'; Name: 'Honda'), - (Code: 'MLC'; Name: 'Suzuki'), - (Code: 'NAA'; Name: 'Peugeot'), - (Code: 'NAP'; Name: 'Pars Khodro'), - (Code: 'MLH'; Name: 'Honda'), - (Code: 'MMA'; Name: 'Mitsubishi'), - (Code: 'MMB'; Name: 'Mitsubishi'), - (Code: 'MMC'; Name: 'Mitsubishi'), - (Code: 'MMM'; Name: 'Chevrolet'), - (Code: 'MMS'; Name: 'Suzuki'), - (Code: 'MMT'; Name: 'Mitsubishi'), - (Code: 'MMU'; Name: 'Holden'), - (Code: 'MM8'; Name: 'Mazda'), - (Code: 'MNB'; Name: 'Ford'), - (Code: 'MNT'; Name: 'Nissan'), - (Code: 'MPA'; Name: 'Isuzu'), - (Code: 'MP1'; Name: 'Isuzu'), - (Code: 'MRH'; Name: 'Honda'), - (Code: 'MR0'; Name: 'Toyota'), - (Code: 'MS0'; Name: 'SSS MOTORS'), - (Code: 'MS3'; Name: 'Suzuki'), - (Code: 'NLA'; Name: 'Honda '), - (Code: 'NLE'; Name: 'Mercedes-Benz'), - (Code: 'NLH'; Name: 'Hyundai'), - (Code: 'NLN'; Name: 'Karsan'), - (Code: 'NLR'; Name: 'OTOKAR'), - (Code: 'NLT'; Name: 'TEMSA'), - (Code: 'NMB'; Name: 'Mercedes-Benz'), - (Code: 'NMC'; Name: 'BMC'), - (Code: 'NM0'; Name: 'Ford'), - (Code: 'NM4'; Name: 'Tofaş'), - (Code: 'NMT'; Name: 'Toyota'), - (Code: 'NNA'; Name: 'Isuzu'), - (Code: 'PE1'; Name: 'Ford'), - (Code: 'PE3'; Name: 'Mazda'), - (Code: 'PL1'; Name: 'Proton,'), - (Code: 'PNA'; Name: 'Peugeot'), - (Code: 'R2P'; Name: 'Evoke'), - (Code: 'RA1'; Name: 'Steyr'), - (Code: 'RFB'; Name: 'Kymco'), - (Code: 'RFG'; Name: 'Sanyang SYM'), - (Code: 'RFL'; Name: 'Adly'), - (Code: 'RFT'; Name: 'CPI'), - (Code: 'RF3'; Name: 'Aeon'), - (Code: 'SAB'; Name: 'Optare'), - (Code: 'SAD'; Name: 'Jaguar'), - (Code: 'SAL'; Name: 'Land Rover'), - (Code: 'SAJ'; Name: 'Jaguar'), - (Code: 'SAR'; Name: 'Rover'), - (Code: 'SAX'; Name: 'Austin-Rover'), - (Code: 'SA9'; Name: 'OX Global'), - (Code: 'SB1'; Name: 'Toyota'), - (Code: 'SBM'; Name: 'McLaren'), - (Code: 'SCA'; Name: 'Rolls Royce'), - (Code: 'SCB'; Name: 'Bentley'), - (Code: 'SCC'; Name: 'Lotus'), - (Code: 'SCE'; Name: 'DeLorean'), - (Code: 'SCF'; Name: 'Aston Martin'), - (Code: 'SCK'; Name: 'iFor Williams'), - (Code: 'SDB'; Name: 'Peugeot'), - (Code: 'SED'; Name: 'General Motors'), - (Code: 'SEY'; Name: 'LDV'), - (Code: 'SFA'; Name: 'Ford'), - (Code: 'SFD'; Name: 'Alexander Dennis'), - (Code: 'SHH'; Name: 'Honda'), - (Code: 'SHS'; Name: 'Honda'), - (Code: 'SJN'; Name: 'Nissan'), - (Code: 'SKF'; Name: 'Vauxhall'), - (Code: 'SLP'; Name: 'JCB'), - (Code: 'SMT'; Name: 'Triumph'), - (Code: 'SUF'; Name: 'Fiat'), - (Code: 'SUL'; Name: 'FSC'), - (Code: 'SUP'; Name: 'FSO-Daewoo'), - (Code: 'SU9'; Name: 'Solaris'), - (Code: 'SUU'; Name: 'Solaris'), - (Code: 'SWV'; Name: 'TA-NO '), - (Code: 'TCC'; Name: 'Smart'), - (Code: 'TDM'; Name: 'QUANTYA'), - (Code: 'TK9'; Name: 'SOR'), - (Code: 'TMA'; Name: 'Hyundai'), - (Code: 'TMB'; Name: 'Škoda)'), - (Code: 'TMK'; Name: 'Karosa'), - (Code: 'TMP'; Name: 'Škoda'), - (Code: 'TMT'; Name: 'Tatra'), - (Code: 'TM9'; Name: 'Škoda'), - (Code: 'TNE'; Name: 'TAZ'), - (Code: 'TN9'; Name: 'Karosa'), - (Code: 'TRA'; Name: 'Ikarus'), - (Code: 'TRU'; Name: 'Audi'), - (Code: 'TSB'; Name: 'Ikarus'), - (Code: 'TSE'; Name: 'Ikarus'), - (Code: 'TSM'; Name: 'Suzuki'), - (Code: 'TW1'; Name: 'Toyota '), - (Code: 'TYA'; Name: 'Mitsubishi'), - (Code: 'TYB'; Name: 'Mitsubishi'), - (Code: 'UU1'; Name: 'Dacia'), - (Code: 'UU2'; Name: 'Oltcit'), - (Code: 'UU3'; Name: 'ARO'), - (Code: 'UU4'; Name: 'Roman SA'), - (Code: 'UU5'; Name: 'Rocar'), - (Code: 'UU6'; Name: 'Daewoo'), - (Code: 'UU7'; Name: 'Euro Bus Diamond'), - (Code: 'UU9'; Name: 'Astra'), - (Code: 'UV9'; Name: 'ATP'), - (Code: 'UZT'; Name: 'UTB'), - (Code: 'U5Y'; Name: 'Kia'), - (Code: 'U6Y'; Name: 'Kia'), - (Code: 'VAG'; Name: 'Magna Steyr Puch'), - (Code: 'VAN'; Name: 'MAN'), - (Code: 'VBK'; Name: 'KTM'), - (Code: 'VF1'; Name: 'Renault'), - (Code: 'VF2'; Name: 'Renault'), - (Code: 'VF3'; Name: 'Peugeot'), - (Code: 'VF4'; Name: 'Talbot'), - (Code: 'VF6'; Name: 'Renault'), - (Code: 'VF7'; Name: 'Citroën'), - (Code: 'VF8'; Name: 'Matra'), - (Code: 'VF9'; Name: 'Bugatti'), - (Code: 'VG5'; Name: 'MBK'), - (Code: 'VLU'; Name: 'Scania'), - (Code: 'VN1'; Name: 'SOVAB'), - (Code: 'VNE'; Name: 'Irisbus'), - (Code: 'VNK'; Name: 'Toyota'), - (Code: 'VNV'; Name: 'Renault-Nissan'), - (Code: 'VSA'; Name: 'Mercedes-Benz'), - (Code: 'VSE'; Name: 'Suzuki'), - (Code: 'VSK'; Name: 'Nissan'), - (Code: 'VSS'; Name: 'SEAT'), - (Code: 'VSX'; Name: 'Opel'), - (Code: 'VS6'; Name: 'Ford'), - (Code: 'VS7'; Name: 'Citroën'), - (Code: 'VS9'; Name: 'Carrocerias Ayats'), - (Code: 'VTH'; Name: 'Derbi'), - (Code: 'VTL'; Name: 'Yamaha'), - (Code: 'VTT'; Name: 'Suzuki'), - (Code: 'VV9'; Name: 'TAURO'), - (Code: 'VWA'; Name: 'Nissan'), - (Code: 'VWV'; Name: 'Volkswagen'), - (Code: 'VX1'; Name: 'Zastava / Yugo Serbia'), - (Code: 'WAG'; Name: 'Neoplan'), - (Code: 'WAU'; Name: 'Audi'), - (Code: 'WA1'; Name: 'Audi'), - (Code: 'WBA'; Name: 'BMW'), - (Code: 'WBS'; Name: 'BMW'), - (Code: 'WBW'; Name: 'BMW'), - (Code: 'WBY'; Name: 'BMW'), - (Code: 'WB1'; Name: 'BMW'), - (Code: 'WDA'; Name: 'Daimler'), - (Code: 'WDB'; Name: 'Mercedes-Benz'), - (Code: 'WDC'; Name: 'DaimlerChrysler'), - (Code: 'WDD'; Name: 'Mercedes-Benz'), - (Code: 'WDF'; Name: 'Mercedes-Benz'), - (Code: 'WEB'; Name: 'Evobus'), - (Code: 'WJM'; Name: 'Iveco'), - (Code: 'WF0'; Name: 'Ford'), - (Code: 'WKE'; Name: 'Krone'), - (Code: 'WKK'; Name: 'Kässbohrer/Setra'), - (Code: 'WMA'; Name: 'MAN'), - (Code: 'WME'; Name: 'Smart'), - (Code: 'WMW'; Name: 'MINI'), - (Code: 'WMX'; Name: 'Mercedes-AMG'), - (Code: 'WMZ'; Name: 'MINI'), - (Code: 'WP0'; Name: 'Porsche'), - (Code: 'WP1'; Name: 'Porsche'), - (Code: 'WSM'; Name: 'Schmitz-Cargobull'), - (Code: 'W09'; Name: 'RUF'), - (Code: 'W0L'; Name: 'Opel'), - (Code: 'W0V'; Name: 'Opel'), - (Code: 'W1K'; Name: 'Mercedes'), - (Code: 'W1V'; name: 'Mercedes'), - (Code: 'W1N'; Name: 'Mercedes'), - (Code: 'WAP'; Name: 'BMW Alpine'), - (Code: 'WUA'; Name: 'Audi'), - (Code: 'WVG'; Name: 'Volkswagen'), - (Code: 'WVW'; Name: 'Volkswagen'), - (Code: 'WV1'; Name: 'Volkswagen'), - (Code: 'WV2'; Name: 'Volkswagen'), - (Code: 'WV3'; Name: 'Volkswagen '), - (Code: 'XLB'; Name: 'Volvo'), - (Code: 'XLE'; Name: 'Scania'), - (Code: 'XLR'; Name: 'DAF'), - (Code: 'XL4'; Name: 'Lightyear'), - (Code: 'XL9'; Name: 'Spyker'), - (Code: 'XMC'; Name: 'Mitsubishi'), - (Code: 'XMG'; Name: 'VDL'), - (Code: 'XTA'; Name: 'Lada/AvtoVAZ'), - (Code: 'XTC'; Name: 'KAMAZ'), - (Code: 'XTH'; Name: 'GAZ'), - (Code: 'XTT'; Name: 'UAZ/Sollers'), - (Code: 'XTU'; Name: 'Trolza'), - (Code: 'XTY'; Name: 'LiAZ'), - (Code: 'XUF'; Name: 'General Motors'), - (Code: 'XUU'; Name: 'General Motors'), - (Code: 'XW8'; Name: 'Volkswagen'), - (Code: 'XWB'; Name: 'Daewoo'), - (Code: 'XWE'; Name: 'Hyundai-Kia'), - (Code: 'X1M'; Name: 'PAZ'), - (Code: 'X4X'; Name: 'BMW'), - (Code: 'X7L'; Name: 'Renault'), - (Code: 'X7M'; Name: 'Hyundai'), - (Code: 'YAR'; Name: 'Toyota'), - (Code: 'YBW'; Name: 'Volkswagen'), - (Code: 'YB1'; Name: 'Volvo'), - (Code: 'YCM'; Name: 'Mazda'), - (Code: 'YE2'; Name: 'Van Hool'), - (Code: 'YH2'; Name: 'Lynx'), - (Code: 'YK1'; Name: 'Saab-Valmet'), - (Code: 'YSC'; Name: 'Cadillac'), - (Code: 'YS2'; Name: 'Scania'), - (Code: 'YS3'; Name: 'Saab'), - (Code: 'YS4'; Name: 'Scania'), - (Code: 'YTN'; Name: 'Saab'), - (Code: 'YT9'; Name: 'Koenigsegg'), - (Code: '007'; Name: 'Koenigsegg'), - (Code: 'YT9'; Name: 'Carvia'), - (Code: '034'; Name: 'Carvia'), - (Code: 'YU7'; Name: 'Husaberg'), - (Code: 'YVV'; Name: 'Polestar'), - (Code: 'YV1'; Name: 'Volvo'), - (Code: 'YV4'; Name: 'Volvo'), - (Code: 'YV2'; Name: 'Volvo'), - (Code: 'YV3'; Name: 'Volvo'), - (Code: 'Y3M'; Name: 'MAZ'), - (Code: 'Y6D'; Name: 'Zaporozhets'), - (Code: 'ZAA'; Name: 'Autobianchi'), - (Code: 'ZAM'; Name: 'Maserati'), - (Code: 'ZAP'; Name: 'Piaggio/Vespa/Gilera'), - (Code: 'ZAR'; Name: 'Alfa Romeo'), - (Code: 'ZA9'; Name: 'Lamborghini'), - (Code: 'ZBN'; Name: 'Benelli'), - (Code: 'ZCG'; Name: 'Cagiva SpA / MV Agusta'), - (Code: 'ZCF'; Name: 'Iveco'), - (Code: 'ZDC'; Name: 'Honda'), - (Code: 'ZDM'; Name: 'Ducati'), - (Code: 'ZDF'; Name: 'Ferrari'), - (Code: 'ZD0'; Name: 'Yamaha'), - (Code: 'ZD3'; Name: 'Beta Motor'), - (Code: 'ZD4'; Name: 'Aprilia'), - (Code: 'ZFA'; Name: 'Fiat'), - (Code: 'ZFC'; Name: 'Fiat'), - (Code: 'ZFF'; Name: 'Ferrari'), - (Code: 'ZGU'; Name: 'Moto Guzzi'), - (Code: 'ZHW'; Name: 'Lamborghini'), - (Code: 'ZJM'; Name: 'Malaguti'), - (Code: 'ZJN'; Name: 'Innocenti'), - (Code: 'ZKH'; Name: 'Husqvarna'), - (Code: 'ZLA'; Name: 'Lancia'), - (Code: 'Z8M'; Name: 'Marussia'), - (Code: '137'; Name: 'Hummer'), - (Code: '1B3'; Name: 'Dodge'), - (Code: '1C3'; Name: 'Chrysler'), - (Code: '1C4'; Name: 'Dodge'), - (Code: '1C6'; Name: 'Chrysler'), - (Code: '1D3'; Name: 'Dodge'), - (Code: '1FA'; Name: 'Ford'), - (Code: '1FB'; Name: 'Ford'), - (Code: '1FC'; Name: 'Ford'), - (Code: '1FD'; Name: 'Ford'), - (Code: '1FM'; Name: 'Ford'), - (Code: '1FT'; Name: 'Ford'), - (Code: '1FU'; Name: 'Freightliner'), - (Code: '1FV'; Name: 'Freightliner'), - (Code: '1F9'; Name: 'FWD.'), - (Code: '1G'; Name: 'General Motors'), - (Code: '1GC'; Name: 'Chevrolet'), - (Code: '1GT'; Name: 'GMC'), - (Code: '1G1'; Name: 'Chevrolet'), - (Code: '1G2'; Name: 'Pontiac'), - (Code: '1G3'; Name: 'Oldsmobile'), - (Code: '1G4'; Name: 'Buick'), - (Code: '1G6'; Name: 'Cadillac'), - (Code: '1G8'; Name: 'Saturn'), - (Code: '1GM'; Name: 'Pontiac'), - (Code: '1GN'; Name: 'Chevrolet'), - (Code: '1GY'; Name: 'Cadillac'), - (Code: '1H'; Name: 'Honda'), - (Code: '1HD'; Name: 'Harley-Davidson'), - (Code: '1HT'; Name: 'International Truck and Engine Corp'), - (Code: '1J4'; Name: 'Jeep'), - (Code: '1J8'; Name: 'Jeep'), - (Code: '1L'; Name: 'Lincoln'), - (Code: '1ME'; Name: 'Mercury'), - (Code: '1M1'; Name: 'Mack'), - (Code: '1M2'; Name: 'Mack'), - (Code: '1M3'; Name: 'Mack'), - (Code: '1M4'; Name: 'Mack'), - (Code: '1M9'; Name: 'Mynatt'), - (Code: '1N'; Name: 'Nissan'), - (Code: '1NX'; Name: 'NUMMI'), - (Code: '1P3'; Name: 'Plymouth'), - (Code: '1PY'; Name: 'John Deere'), - (Code: '1R9'; Name: 'Roadrunner'), - (Code: '1VW'; Name: 'Volkswagen'), - (Code: '1XK'; Name: 'Kenworth'), - (Code: '1XP'; Name: 'Peterbilt'), - (Code: '1YV'; Name: 'Mazda'), - (Code: '1ZV'; Name: 'Ford'), - (Code: '2A4'; Name: 'Chrysler'), - (Code: '2BP'; Name: 'Bombardier'), - (Code: '2B3'; Name: 'Dodge'), - (Code: '2B7'; Name: 'Dodge'), - (Code: '2C3'; Name: 'Dodge'), - (Code: '2CN'; Name: 'Chevrolet'), - (Code: '2D3'; Name: 'Dodge'), - (Code: '2FA'; Name: 'Ford'), - (Code: '2FB'; Name: 'Ford'), - (Code: '2FC'; Name: 'Ford'), - (Code: '2FM'; Name: 'Ford'), - (Code: '2FT'; Name: 'Ford'), - (Code: '2FU'; Name: 'Freightliner'), - (Code: '2FV'; Name: 'Freightliner'), - (Code: '2FZ'; Name: 'Sterling'), - (Code: '2Gx'; Name: 'General Motors'), - (Code: '2GC'; Name: 'Chevrolet'), - (Code: '2G1'; Name: 'Chevrolet'), - (Code: '2G2'; Name: 'Pontiac'), - (Code: '2G3'; Name: 'Oldsmobile'), - (Code: '2G4'; Name: 'Buick'), - (Code: '2HG'; Name: 'Honda'), - (Code: '2HK'; Name: 'Honda'), - (Code: '2HJ'; Name: 'Honda'), - (Code: '2HM'; Name: 'Hyundai'), - (Code: '2M'; Name: 'Mercury'), - (Code: '2NV'; Name: 'Nova'), - (Code: '2P3'; Name: 'Plymouth'), - (Code: '2T2'; Name: 'Lexus'), - (Code: '2T'; Name: 'Toyota'), - (Code: '2TP'; Name: 'Triple E'), - (Code: '2V4'; Name: 'Volkswagen'), - (Code: '2V8'; Name: 'Volkswagen'), - (Code: '2WK'; Name: 'Western Star'), - (Code: '2WL'; Name: 'Western Star'), - (Code: '2WM'; Name: 'Western Star'), - (Code: '363'; Name: 'Spyker'), - (Code: '3C4'; Name: 'Chrysler'), - (Code: '3C6'; Name: 'RAM'), - (Code: '3D3'; Name: 'Dodge'), - (Code: '3D4'; Name: 'Dodge'), - (Code: '3FA'; Name: 'Ford'), - (Code: '3FE'; Name: 'Ford'), - (Code: '3G'; Name: 'General Motors'), - (Code: '3H'; Name: 'Honda'), - (Code: '3JB'; Name: 'BRP'), - (Code: '3MD'; Name: 'Mazda'), - (Code: '3MZ'; Name: 'Mazda'), - (Code: '3N'; Name: 'Nissan'), - (Code: '3NS'; Name: 'Polaris'), - (Code: '3NE'; Name: 'Polaris'), - (Code: '3P3'; Name: 'Plymouth'), - (Code: '3VW'; Name: 'Volkswagen'), - (Code: '46J'; Name: 'Federal Motors Inc'), - (Code: '4EN'; Name: 'Emergency One'), - (Code: '4F'; Name: 'Mazda'), - (Code: '4JG'; Name: 'Mercedes-Benz'), - (Code: '4M'; Name: 'Mercury'), - (Code: '4P1'; Name: 'Pierce Manufacturing Inc'), - (Code: '4RK'; Name: 'Nova'), - (Code: '4S'; Name: 'Subaru-Isuzu'), - (Code: '4T'; Name: 'Toyota'), - (Code: '4T9'; Name: 'Lumen Motors'), - (Code: '4UF'; Name: 'Arctic Cat Inc.'), - (Code: '4US'; Name: 'BMW'), - (Code: '4UZ'; Name: 'Frt-Thomas'), - (Code: '4V1'; Name: 'Volvo'), - (Code: '4V2'; Name: 'Volvo'), - (Code: '4V3'; Name: 'Volvo'), - (Code: '4V4'; Name: 'Volvo'), - (Code: '4V5'; Name: 'Volvo'), - (Code: '4V6'; Name: 'Volvo'), - (Code: '4VL'; Name: 'Volvo'), - (Code: '4VM'; Name: 'Volvo'), - (Code: '4VZ'; Name: 'Volvo'), - (Code: '538'; Name: 'Zero'), - (Code: '5F'; Name: 'Honda'), - (Code: '5G'; Name: 'Hummer'), - (Code: '5J'; Name: 'Honda'), - (Code: '5L'; Name: 'Lincoln'), - (Code: '5N1'; Name: 'Infinity'), - (Code: '5NP'; Name: 'Hyundai'), - (Code: '5T'; Name: 'Toyota'), - (Code: '5YJ'; Name: 'Tesla'), - (Code: '5XY'; Name: 'Kia'), - (Code: '5UX'; Name: 'BMW'), - (Code: '56K'; Name: 'Indian'), - (Code: '6AB'; Name: 'MAN'), - (Code: '6F4'; Name: 'Nissan'), - (Code: '6F5'; Name: 'Kenworth'), - (Code: '6FP'; Name: 'Ford'), - (Code: '6G1'; Name: 'Holden'), - (Code: '6G2'; Name: 'Pontiac'), - (Code: '6H8'; Name: 'Holden'), - (Code: '6MM'; Name: 'Mitsubishi'), - (Code: '6T1'; Name: 'Toyota'), - (Code: '6U9'; Name: 'Privately Imported car in Australia'), - (Code: '795'; Name: 'Bugatti'), - (Code: '8AD'; Name: 'Peugeot'), - (Code: '8AF'; Name: 'Ford'), - (Code: '8AG'; Name: 'Chevrolet'), - (Code: '8AJ'; Name: 'Toyota'), - (Code: '8AK'; Name: 'Suzuki'), - (Code: '8AP'; Name: 'Fiat'), - (Code: '8AW'; Name: 'Volkswagen'), - (Code: '8A1'; Name: 'Renault'), - (Code: '8GD'; Name: 'Peugeot'), - (Code: '8GG'; Name: 'Chevrolet'), - (Code: '8LD'; Name: 'Chevrolet'), - (Code: '935'; Name: 'Citroën'), - (Code: '936'; Name: 'Peugeot'), - (Code: '93H'; Name: 'Honda'), - (Code: '93R'; Name: 'Toyota'), - (Code: '93U'; Name: 'Audi'), - (Code: '93V'; Name: 'Audi'), - (Code: '93X'; Name: 'Mitsubishi'), - (Code: '93Y'; Name: 'Renault'), - (Code: '94D'; Name: 'Nissan'), - (Code: '9BF'; Name: 'Ford'), - (Code: '9BG'; Name: 'Chevrolet'), - (Code: '9BM'; Name: 'Mercedes-Benz'), - (Code: '9BR'; Name: 'Toyota'), - (Code: '9BS'; Name: 'Scania'), - (Code: '9BW'; Name: 'Volkswagen'), - (Code: '9FB'; Name: 'Renault'), - (Code: '9GA'; Name: 'Chevrolet') - ); - -//------------------------------------------------------------------------------ -// VIN ALPHABET CHARACTERS (ONLY CHARACTERS USED IN VIN) +// CONSTANTS — VIN ALPHABETS (spec-defined, not data; never user-editable) //------------------------------------------------------------------------------ const + /// + /// VIN-permitted characters (excludes I, O, Q, plus '0' wraps). + /// ALPHABET_CHARS: array[0..32] of Char = ( 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0' ); -//------------------------------------------------------------------------------ -// VIN YEAR ALPHABET CHARACTERS (ONLY CHARACTERS USED FOR THE VIN YEARS) -//------------------------------------------------------------------------------ -const + /// + /// VIN year-character cycle (60-year window starting 1980). + /// YEAR_CHARS: array[0..59] of Char = ( 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'R', 'S', 'T', 'V', 'W', 'X', 'Y', '1', '2', '3', '4', '5', '6', '7', '8', '9', @@ -754,33 +42,233 @@ interface ); //------------------------------------------------------------------------------ -// VIN COUNTRY MAP +// LOOKUP TABLES — populated from JSON catalogs at unit init //------------------------------------------------------------------------------ var + /// + /// VIN region table. Loaded from catalogs/vin-regions.json. + /// + VINRegions: TArray; + /// + /// VIN country table. Loaded from catalogs/vin-countries.json. + /// + VINCountries: TArray; + /// + /// WMI-to-manufacturer table. Loaded from catalogs/vin-wmi-manufacturers.json. + /// + VINManufacturers: TArray; + + /// + /// WMI-prefix country lookup, computed from VINCountries + + /// ALPHABET_CHARS. + /// VINCountryMap: TDictionary; + /// + /// 3-char WMI manufacturer lookup, computed from VINManufacturers. + /// + VINManufacturerMap: TDictionary; + /// + /// Year-character to model-year map. + /// + VINYearMap: TArray; + /// + /// WMI+plant-char to plant location map. Loaded from + /// catalogs/vin-plants.json. + /// + VINPlantLocationMap: TDictionary; + +implementation + +uses + System.Classes, System.JSON, + OBD.Catalog.Path; //------------------------------------------------------------------------------ -// VIN MANUFACTURER MAP +// JSON LOADER HELPERS //------------------------------------------------------------------------------ +function LoadJsonObject(const FileName: string): TJSONObject; var - VINManufacturerMap: TDictionary; + Path, Raw: string; + Stream: TStringStream; + Doc: TJSONValue; +begin + // Initialize result + Result := nil; + Path := ResolveCatalogPath(FileName); + // Bail if catalog path is missing + if Path = '' then Exit; + // Create stream + Stream := TStringStream.Create('', TEncoding.UTF8); + try + // Load file into stream + Stream.LoadFromFile(Path); + Raw := Stream.DataString; + finally + // Free the stream + Stream.Free; + end; + // Parse JSON document + Doc := TJSONObject.ParseJSONValue(Raw); + if Doc is TJSONObject then + Result := Doc as TJSONObject + else + // Free the document + Doc.Free; +end; //------------------------------------------------------------------------------ -// VIN YEAR MAP +// LOAD REGIONS //------------------------------------------------------------------------------ +procedure LoadRegions; var - VINYearMap: TArray; + Doc: TJSONObject; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + R: TVINRegion; + S: string; +begin + Doc := LoadJsonObject('vin-regions.json'); + // Bail if document is nil + if Doc = nil then Exit; + try + // Pull the entries array + Arr := Doc.GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + S := Obj.GetValue('range_start', ''); + if S = '' then Continue; + R.RangeStart := S[1]; + S := Obj.GetValue('range_end', ''); + if S = '' then Continue; + R.RangeEnd := S[1]; + R.Name := Obj.GetValue('name', ''); + VINRegions := VINRegions + [R]; + end; + finally + // Free the document + Doc.Free; + end; +end; //------------------------------------------------------------------------------ -// VIN PLANT LOCATION MAP +// LOAD COUNTRIES //------------------------------------------------------------------------------ +procedure LoadCountries; var - VINPlantLocationMap: TDictionary; + Doc: TJSONObject; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + C: TVINCountry; +begin + Doc := LoadJsonObject('vin-countries.json'); + // Bail if document is nil + if Doc = nil then Exit; + try + // Pull the entries array + Arr := Doc.GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + C.RangeStart := Obj.GetValue('range_start', ''); + C.RangeEnd := Obj.GetValue('range_end', ''); + C.Name := Obj.GetValue('name', ''); + C.Code := Obj.GetValue('iso_code', ''); + if (C.RangeStart = '') or (C.RangeEnd = '') then Continue; + VINCountries := VINCountries + [C]; + end; + finally + // Free the document + Doc.Free; + end; +end; -implementation +//------------------------------------------------------------------------------ +// LOAD MANUFACTURERS +//------------------------------------------------------------------------------ +procedure LoadManufacturers; +var + Doc: TJSONObject; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + M: TVINManufacturer; +begin + Doc := LoadJsonObject('vin-wmi-manufacturers.json'); + // Bail if document is nil + if Doc = nil then Exit; + try + // Pull the entries array + Arr := Doc.GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + M.Code := Obj.GetValue('wmi', ''); + M.Name := Obj.GetValue('name', ''); + if M.Code = '' then Continue; + VINManufacturers := VINManufacturers + [M]; + end; + finally + // Free the document + Doc.Free; + end; +end; //------------------------------------------------------------------------------ -// INITIALIZE COUNTRY MAP +// LOAD PLANTS +//------------------------------------------------------------------------------ +procedure LoadPlants; +var + Doc: TJSONObject; + Arr: TJSONArray; + Item: TJSONValue; + Obj: TJSONObject; + P: TVINPlantLocation; + Key: string; +begin + Doc := LoadJsonObject('vin-plants.json'); + // Bail if document is nil + if Doc = nil then Exit; + try + // Pull the entries array + Arr := Doc.GetValue('entries'); + // Bail if array is missing + if Arr = nil then Exit; + // Loop over Arr + for Item in Arr do + begin + if not (Item is TJSONObject) then Continue; + Obj := Item as TJSONObject; + Key := Obj.GetValue('key', ''); + if Key = '' then Continue; + P.Code := Obj.GetValue('code', ''); + P.Name := Obj.GetValue('name', ''); + P.City := Obj.GetValue('city', ''); + P.Country := Obj.GetValue('country', ''); + VINPlantLocationMap.AddOrSetValue(Key, P); + end; + finally + // Free the document + Doc.Free; + end; +end; + +//------------------------------------------------------------------------------ +// DERIVED MAPS — built from the loaded arrays //------------------------------------------------------------------------------ procedure InitializeCountryMap; var @@ -788,45 +276,32 @@ procedure InitializeCountryMap; StartIndex, EndIndex, I, J, K: Integer; Key: string; begin - // Create dictionary for the country map + // Create VINCountryMap VINCountryMap := TDictionary.Create; - - // Loop over the countries + // Loop over VINCountries for Country in VINCountries do begin - // Initialize start index StartIndex := -1; - // Initialize end index - EndIndex := -1; - - // Find the start and end indexes in the ALPHABET_CHARS array + EndIndex := -1; + // Loop over ALPHABET_CHARS for I := Low(ALPHABET_CHARS) to High(ALPHABET_CHARS) do begin - // Find start index if ALPHABET_CHARS[I] = Country.RangeStart[1] then StartIndex := I; - // Find end index - if ALPHABET_CHARS[I] = Country.RangeEnd[1] then EndIndex := I; - // Exit if we found the start and end indexes + if ALPHABET_CHARS[I] = Country.RangeEnd[1] then EndIndex := I; if (StartIndex <> -1) and (EndIndex <> -1) then Break; end; - - // Skip if range is invalid if (StartIndex = -1) or (EndIndex = -1) then Continue; - - // Iterate through the range for I := StartIndex to EndIndex do + // Loop over ALPHABET_CHARS for J := Low(ALPHABET_CHARS) to High(ALPHABET_CHARS) do begin if ALPHABET_CHARS[J] = Country.RangeStart[2] then StartIndex := J; - if ALPHABET_CHARS[J] = Country.RangeEnd[2] then EndIndex := J; - + if ALPHABET_CHARS[J] = Country.RangeEnd[2] then EndIndex := J; for K := StartIndex to EndIndex do begin Key := Country.RangeStart[1] + ALPHABET_CHARS[K]; VINCountryMap.AddOrSetValue(Key, Country); end; - - // Exit after processing the first character's range Break; end; end; @@ -838,35 +313,22 @@ procedure InitializeCountryMap; procedure InitializeManufacturerMap; var Manufacturer: TVINManufacturer; - ManufacturerCode, Character: string; + ManufacturerCode: string; + Character: Char; begin - // Create dictionary for the manufacturer map + // Create VINManufacturerMap VINManufacturerMap := TDictionary.Create; - - // Loop over the manufacturers + // Loop over VINManufacturers for Manufacturer in VINManufacturers do begin - // Assign manufacturer code ManufacturerCode := Manufacturer.Code; - // If the manufacturer code is 3 characters long - // we can use it as-is. if Length(ManufacturerCode) = 3 then - begin - // Add the manufacturer code to the map - VINManufacturerMap.AddOrSetValue(ManufacturerCode, Manufacturer); - end else - - // If the manufacturer code is shorter than 3 characters, - // than expand the manufacturer codes from the (VIN) alphabet characters. - if Length(ManufacturerCode) < 3 then - begin - // Loop over the (VIN) alphabet characters, and add the code to the map + VINManufacturerMap.AddOrSetValue(ManufacturerCode, Manufacturer) + else if Length(ManufacturerCode) < 3 then + // Loop over ALPHABET_CHARS for Character in ALPHABET_CHARS do - begin if not VINManufacturerMap.ContainsKey(Manufacturer.Code + Character) then - VINManufacturerMap.Add(Manufacturer.Code + Character, Manufacturer); - end; - end + VINManufacturerMap.Add(Manufacturer.Code + Character, Manufacturer); end; end; @@ -879,9 +341,9 @@ procedure InitializeYearMap; var I: Integer; begin - // Set length of the map + // Allocate VINYearMap SetLength(VINYearMap, Length(YEAR_CHARS)); - // Loop over the year characters and fill the map + // Loop over YEAR_CHARS for I := Low(YEAR_CHARS) to High(YEAR_CHARS) do begin VINYearMap[I].Code := YEAR_CHARS[I]; @@ -889,111 +351,22 @@ procedure InitializeYearMap; end; end; -//------------------------------------------------------------------------------ -// INITIALIZE PLANT LOCATION MAP -//------------------------------------------------------------------------------ -procedure InitializePlantLocationMap; -var - Plant: TVINPlantLocation; -begin - // Create dictionary for the plant location map - VINPlantLocationMap := TDictionary.Create; - - // Sample plant locations for major manufacturers - // Format: WMI + PlantCode - - // Ford (1FA, 1FB, 1FC, 1FD, 1FM, 1FT, etc.) - Plant.Code := 'A'; Plant.Name := 'Atlanta Assembly'; Plant.City := 'Hapeville'; Plant.Country := 'USA'; - VINPlantLocationMap.Add('1FAA', Plant); - - Plant.Code := 'D'; Plant.Name := 'Dearborn Assembly'; Plant.City := 'Dearborn'; Plant.Country := 'USA'; - VINPlantLocationMap.Add('1FAD', Plant); - - Plant.Code := 'F'; Plant.Name := 'Flat Rock Assembly'; Plant.City := 'Flat Rock'; Plant.Country := 'USA'; - VINPlantLocationMap.Add('1FAF', Plant); - - Plant.Code := 'K'; Plant.Name := 'Kansas City Assembly'; Plant.City := 'Claycomo'; Plant.Country := 'USA'; - VINPlantLocationMap.Add('1FAK', Plant); - - Plant.Code := 'P'; Plant.Name := 'Twin Cities Assembly'; Plant.City := 'St. Paul'; Plant.Country := 'USA'; - VINPlantLocationMap.Add('1FAP', Plant); - - // GM (1G1, 1G2, 1GC, etc.) - Plant.Code := 'A'; Plant.Name := 'Lakewood Assembly'; Plant.City := 'Doraville'; Plant.Country := 'USA'; - VINPlantLocationMap.Add('1G1A', Plant); - - Plant.Code := 'D'; Plant.Name := 'Fairfax Assembly'; Plant.City := 'Kansas City'; Plant.Country := 'USA'; - VINPlantLocationMap.Add('1G1D', Plant); - - Plant.Code := 'F'; Plant.Name := 'Flint Assembly'; Plant.City := 'Flint'; Plant.Country := 'USA'; - VINPlantLocationMap.Add('1G1F', Plant); - - // Toyota (JT2, JT3, 4T1, 5T1, etc.) - Plant.Code := 'A'; Plant.Name := 'Takaoka Plant'; Plant.City := 'Toyota'; Plant.Country := 'Japan'; - VINPlantLocationMap.Add('JT2A', Plant); - - Plant.Code := 'B'; Plant.Name := 'Tsutsumi Plant'; Plant.City := 'Toyota'; Plant.Country := 'Japan'; - VINPlantLocationMap.Add('JT2B', Plant); - - Plant.Code := 'K'; Plant.Name := 'Georgetown Plant'; Plant.City := 'Georgetown'; Plant.Country := 'USA'; - VINPlantLocationMap.Add('4T1K', Plant); - - // Honda (1HG, JHM, etc.) - Plant.Code := 'C'; Plant.Name := 'Marysville Auto Plant'; Plant.City := 'Marysville'; Plant.Country := 'USA'; - VINPlantLocationMap.Add('1HGC', Plant); - - Plant.Code := 'E'; Plant.Name := 'East Liberty Auto Plant'; Plant.City := 'East Liberty'; Plant.Country := 'USA'; - VINPlantLocationMap.Add('1HGE', Plant); - - Plant.Code := 'A'; Plant.Name := 'Suzuka Plant'; Plant.City := 'Suzuka'; Plant.Country := 'Japan'; - VINPlantLocationMap.Add('JHMA', Plant); - - // Mercedes-Benz (WDB, WDD, etc.) - Plant.Code := 'F'; Plant.Name := 'Sindelfingen Plant'; Plant.City := 'Sindelfingen'; Plant.Country := 'Germany'; - VINPlantLocationMap.Add('WDBF', Plant); - - Plant.Code := 'J'; Plant.Name := 'Rastatt Plant'; Plant.City := 'Rastatt'; Plant.Country := 'Germany'; - VINPlantLocationMap.Add('WDBJ', Plant); - - // BMW (WBA, WBS, etc.) - Plant.Code := 'A'; Plant.Name := 'Munich Plant'; Plant.City := 'Munich'; Plant.Country := 'Germany'; - VINPlantLocationMap.Add('WBAA', Plant); - - Plant.Code := 'C'; Plant.Name := 'Regensburg Plant'; Plant.City := 'Regensburg'; Plant.Country := 'Germany'; - VINPlantLocationMap.Add('WBAC', Plant); - - // VW (WVW, 3VW, etc.) - Plant.Code := 'W'; Plant.Name := 'Wolfsburg Plant'; Plant.City := 'Wolfsburg'; Plant.Country := 'Germany'; - VINPlantLocationMap.Add('WVWW', Plant); - - Plant.Code := 'Z'; Plant.Name := 'Zwickau Plant'; Plant.City := 'Zwickau'; Plant.Country := 'Germany'; - VINPlantLocationMap.Add('WVWZ', Plant); - - // Add more plant locations as needed -end; - //------------------------------------------------------------------------------ // INITIALIZATION //------------------------------------------------------------------------------ initialization - // Initialize the country map + VINPlantLocationMap := TDictionary.Create; + LoadRegions; + LoadCountries; + LoadManufacturers; + LoadPlants; InitializeCountryMap; - // Initialize the manufacturer map InitializeManufacturerMap; - // Initialize the year map InitializeYearMap; - // Initialize the plant location map - InitializePlantLocationMap; -//------------------------------------------------------------------------------ -// FINALIZATION -//------------------------------------------------------------------------------ finalization - // Free the country map FreeAndNil(VINCountryMap); - // Free the manufacturer map FreeAndNil(VINManufacturerMap); - // Free the plant location map FreeAndNil(VINPlantLocationMap); end. diff --git a/tests/Tests.Adapter.Capabilities.pas b/tests/Tests.Adapter.Capabilities.pas new file mode 100644 index 00000000..50a89664 --- /dev/null +++ b/tests/Tests.Adapter.Capabilities.pas @@ -0,0 +1,165 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.Adapter.Capabilities.pas +// CONTENTS : Tests for OBD.Adapter.Capabilities +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.Adapter.Capabilities; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TAdapterCapabilitiesTests = class + public + /// + /// E l m327 does not claim c a n f d. + /// + [Test] procedure ELM327DoesNotClaimCANFD; + /// + /// O b d link e x claims c a n f d. + /// + [Test] procedure OBDLinkEXClaimsCANFD; + /// + /// Do i p gateway has no k line. + /// + [Test] procedure DoIPGatewayHasNoKLine; + /// + /// Unknown adapter returns false. + /// + [Test] procedure UnknownAdapterReturnsFalse; + /// + /// Resolve iso tp falls back to seven. + /// + [Test] procedure ResolveIsoTpFallsBackToSeven; + /// + /// Resolve iso tp returns sixty two for c a n f d adapter. + /// + [Test] procedure ResolveIsoTpReturnsSixtyTwoForCANFDAdapter; + /// + /// Register is case insensitive. + /// + [Test] procedure RegisterIsCaseInsensitive; + /// + /// Set to string contains c a n. + /// + [Test] procedure SetToStringContainsCAN; + /// + /// Register replaces existing. + /// + [Test] procedure RegisterReplacesExisting; + end; + +implementation + +uses + System.SysUtils, OBD.Adapter.Capabilities; + +//------------------------------------------------------------------------------ +// ELM327 DOES NOT CLAIM CANFD +//------------------------------------------------------------------------------ +procedure TAdapterCapabilitiesTests.ELM327DoesNotClaimCANFD; +begin + Assert.IsTrue(AdapterSupports('elm327', acCAN)); + Assert.IsFalse(AdapterSupports('elm327', acCANFD)); +end; + +//------------------------------------------------------------------------------ +// OBDLINK EXCLAIMS CANFD +//------------------------------------------------------------------------------ +procedure TAdapterCapabilitiesTests.OBDLinkEXClaimsCANFD; +begin + Assert.IsTrue(AdapterSupports('obdlink_ex', acCAN)); + Assert.IsTrue(AdapterSupports('obdlink_ex', acCANFD)); + Assert.IsTrue(AdapterSupports('obdlink_ex', acISOTPLargeFrame)); +end; + +//------------------------------------------------------------------------------ +// DO IPGATEWAY HAS NO KLINE +//------------------------------------------------------------------------------ +procedure TAdapterCapabilitiesTests.DoIPGatewayHasNoKLine; +begin + Assert.IsTrue(AdapterSupports('doip_gateway', acDoIP)); + Assert.IsFalse(AdapterSupports('doip_gateway', acKLine)); +end; + +//------------------------------------------------------------------------------ +// UNKNOWN ADAPTER RETURNS FALSE +//------------------------------------------------------------------------------ +procedure TAdapterCapabilitiesTests.UnknownAdapterReturnsFalse; +var + Caps: TOBDAdapterCapabilities; +begin + Assert.IsFalse(FindAdapterCapabilities('does-not-exist', Caps)); + Assert.IsFalse(AdapterSupports('does-not-exist', acCAN)); +end; + +//------------------------------------------------------------------------------ +// RESOLVE ISO TP FALLS BACK TO SEVEN +//------------------------------------------------------------------------------ +procedure TAdapterCapabilitiesTests.ResolveIsoTpFallsBackToSeven; +begin + Assert.AreEqual(7, ResolveIsoTpFrameBytes('elm327')); + Assert.AreEqual(7, ResolveIsoTpFrameBytes('does-not-exist')); +end; + +//------------------------------------------------------------------------------ +// RESOLVE ISO TP RETURNS SIXTY TWO FOR CANFDADAPTER +//------------------------------------------------------------------------------ +procedure TAdapterCapabilitiesTests.ResolveIsoTpReturnsSixtyTwoForCANFDAdapter; +begin + Assert.AreEqual(62, ResolveIsoTpFrameBytes('obdlink_ex')); +end; + +//------------------------------------------------------------------------------ +// REGISTER IS CASE INSENSITIVE +//------------------------------------------------------------------------------ +procedure TAdapterCapabilitiesTests.RegisterIsCaseInsensitive; +begin + Assert.IsTrue(AdapterSupports('ELM327', acCAN)); + Assert.IsTrue(AdapterSupports('Elm327', acCAN)); +end; + +//------------------------------------------------------------------------------ +// SET TO STRING CONTAINS CAN +//------------------------------------------------------------------------------ +procedure TAdapterCapabilitiesTests.SetToStringContainsCAN; +var + S: string; +begin + S := CapabilitySetToString([acCAN, acISOTP, acVoltageMonitor]); + Assert.IsTrue(S.Contains('CAN')); + Assert.IsTrue(S.Contains('ISO-TP')); +end; + +//------------------------------------------------------------------------------ +// REGISTER REPLACES EXISTING +//------------------------------------------------------------------------------ +procedure TAdapterCapabilitiesTests.RegisterReplacesExisting; +var + R: TOBDAdapterCapabilities; +begin + R.AdapterKey := 'test_replace'; + R.DisplayName := 'first'; + R.CapSet := [acCAN]; + R.MaxIsoTpFrameBytes := 7; + RegisterAdapterCapabilities(R); + R.DisplayName := 'second'; + R.CapSet := [acCAN, acCANFD]; + R.MaxIsoTpFrameBytes := 62; + RegisterAdapterCapabilities(R); + Assert.IsTrue(AdapterSupports('test_replace', acCANFD)); +end; + +initialization + TDUnitX.RegisterTestFixture(TAdapterCapabilitiesTests); + +end. diff --git a/tests/Tests.Adapter.ELM327.pas b/tests/Tests.Adapter.ELM327.pas index e24d81e9..7fda9341 100644 --- a/tests/Tests.Adapter.ELM327.pas +++ b/tests/Tests.Adapter.ELM327.pas @@ -70,6 +70,9 @@ implementation { TFormatATCommandTests } +//------------------------------------------------------------------------------ +// NO PARAM_COMMAND_FORMATS LITERALLY +//------------------------------------------------------------------------------ procedure TFormatATCommandTests.NoParam_Command_FormatsLiterally; begin Assert.AreEqual('E0', FormatATCommand(ECHO_OFF, [])); @@ -78,6 +81,9 @@ procedure TFormatATCommandTests.NoParam_Command_FormatsLiterally; Assert.AreEqual('Z', FormatATCommand(RESET_ALL, [])); end; +//------------------------------------------------------------------------------ +// SINGLE STRING PARAM_FORMATS EXPECTED +//------------------------------------------------------------------------------ procedure TFormatATCommandTests.SingleStringParam_FormatsExpected; var Cmd: string; @@ -87,6 +93,9 @@ procedure TFormatATCommandTests.SingleStringParam_FormatsExpected; Assert.AreEqual('@3 MYTAG', Cmd); end; +//------------------------------------------------------------------------------ +// SET HEADER_AT_SH_FORMATS HEX HEADER +//------------------------------------------------------------------------------ procedure TFormatATCommandTests.SetHeader_AT_SH_FormatsHexHeader; var Cmd: string; @@ -96,6 +105,9 @@ procedure TFormatATCommandTests.SetHeader_AT_SH_FormatsHexHeader; Assert.AreEqual('SH 7E0', Cmd); end; +//------------------------------------------------------------------------------ +// SET PROTOCOL_AT_SP_FORMATS PROTOCOL DIGIT +//------------------------------------------------------------------------------ procedure TFormatATCommandTests.SetProtocol_AT_SP_FormatsProtocolDigit; var Cmd: string; @@ -105,6 +117,9 @@ procedure TFormatATCommandTests.SetProtocol_AT_SP_FormatsProtocolDigit; Assert.AreEqual('SP 6', Cmd); end; +//------------------------------------------------------------------------------ +// PARAM COUNT MISMATCH_TOO FEW_RAISES EXCEPTION +//------------------------------------------------------------------------------ procedure TFormatATCommandTests.ParamCountMismatch_TooFew_RaisesException; begin Assert.WillRaise( @@ -115,6 +130,9 @@ procedure TFormatATCommandTests.ParamCountMismatch_TooFew_RaisesException; TATCommandException); end; +//------------------------------------------------------------------------------ +// PARAM COUNT MISMATCH_TOO MANY_RAISES EXCEPTION +//------------------------------------------------------------------------------ procedure TFormatATCommandTests.ParamCountMismatch_TooMany_RaisesException; begin Assert.WillRaise( @@ -127,6 +145,9 @@ procedure TFormatATCommandTests.ParamCountMismatch_TooMany_RaisesException; { TElm327ChipTypeTests } +//------------------------------------------------------------------------------ +// DESCRIPTION_CONTAINS EXPECTED SUBSTRING +//------------------------------------------------------------------------------ procedure TElm327ChipTypeTests.Description_ContainsExpectedSubstring( const ChipOrdinal: Integer; const ExpectedSubstring: string); var @@ -139,6 +160,9 @@ procedure TElm327ChipTypeTests.Description_ContainsExpectedSubstring( [Description, ExpectedSubstring])); end; +//------------------------------------------------------------------------------ +// DESCRIPTION_NEVER EMPTY_FOR KNOWN TYPES +//------------------------------------------------------------------------------ procedure TElm327ChipTypeTests.Description_NeverEmpty_ForKnownTypes; var ChipType: TELM327ChipType; diff --git a/tests/Tests.Adapter.PassThrough.J2534v2.pas b/tests/Tests.Adapter.PassThrough.J2534v2.pas new file mode 100644 index 00000000..8f94c629 --- /dev/null +++ b/tests/Tests.Adapter.PassThrough.J2534v2.pas @@ -0,0 +1,150 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.Adapter.PassThrough.J2534v2.pas +// CONTENTS : Tests for OBD.Adapter.PassThrough.J2534v2 +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.Adapter.PassThrough.J2534v2; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TJ2534v2Tests = class + public + /// + /// Empty list serialises to four zero bytes. + /// + [Test] procedure EmptyListSerialisesToFourZeroBytes; + /// + /// Single entry serialises little endian. + /// + [Test] procedure SingleEntrySerialisesLittleEndian; + /// + /// Multiple entries preserve order. + /// + [Test] procedure MultipleEntriesPreserveOrder; + /// + /// Count reports length. + /// + [Test] procedure CountReportsLength; + /// + /// C a n f d data rate constant is0x8011. + /// + [Test] procedure CANFDDataRateConstantIs0x8011; + /// + /// Mixed format constant is0x800 b. + /// + [Test] procedure MixedFormatConstantIs0x800B; + end; + +implementation + +uses + System.SysUtils, OBD.Adapter.PassThrough.J2534v2; + +//------------------------------------------------------------------------------ +// EMPTY LIST SERIALISES TO FOUR ZERO BYTES +//------------------------------------------------------------------------------ +procedure TJ2534v2Tests.EmptyListSerialisesToFourZeroBytes; +var + L: TJ2534ConfigList; + B: TBytes; +begin + L := TJ2534ConfigList.Create; + try + B := L.ToBytes; + Assert.AreEqual(4, Length(B)); + Assert.AreEqual(0, Integer(B[0])); + Assert.AreEqual(0, Integer(B[1])); + Assert.AreEqual(0, Integer(B[2])); + Assert.AreEqual(0, Integer(B[3])); + finally L.Free; end; +end; + +//------------------------------------------------------------------------------ +// SINGLE ENTRY SERIALISES LITTLE ENDIAN +//------------------------------------------------------------------------------ +procedure TJ2534v2Tests.SingleEntrySerialisesLittleEndian; +var + L: TJ2534ConfigList; + B: TBytes; +begin + L := TJ2534ConfigList.Create; + try + L.Add(CFG_DATA_RATE, 500000); + B := L.ToBytes; + Assert.AreEqual(12, Length(B)); + // count = 1 + Assert.AreEqual(1, Integer(B[0])); + // parameter = 0x00000001 + Assert.AreEqual($01, Integer(B[4])); + Assert.AreEqual($00, Integer(B[5])); + // value = 500000 = 0x0007A120 -> LE: 20 A1 07 00 + Assert.AreEqual($20, Integer(B[8])); + Assert.AreEqual($A1, Integer(B[9])); + Assert.AreEqual($07, Integer(B[10])); + Assert.AreEqual($00, Integer(B[11])); + finally L.Free; end; +end; + +//------------------------------------------------------------------------------ +// MULTIPLE ENTRIES PRESERVE ORDER +//------------------------------------------------------------------------------ +procedure TJ2534v2Tests.MultipleEntriesPreserveOrder; +var + L: TJ2534ConfigList; +begin + L := TJ2534ConfigList.Create; + try + L.Add(CFG_DATA_RATE, 500000); + L.Add(CFG_CAN_FD_DATA_RATE, 2000000); + L.Add(CFG_CAN_MIXED_FORMAT, 1); + Assert.AreEqual(3, L.Count); + Assert.AreEqual(4 + 3 * 8, Length(L.ToBytes)); + finally L.Free; end; +end; + +//------------------------------------------------------------------------------ +// COUNT REPORTS LENGTH +//------------------------------------------------------------------------------ +procedure TJ2534v2Tests.CountReportsLength; +var + L: TJ2534ConfigList; +begin + L := TJ2534ConfigList.Create; + try + Assert.AreEqual(0, L.Count); + L.Add(CFG_LOOPBACK, 0); + Assert.AreEqual(1, L.Count); + finally L.Free; end; +end; + +//------------------------------------------------------------------------------ +// CANFDDATA RATE CONSTANT IS0X8011 +//------------------------------------------------------------------------------ +procedure TJ2534v2Tests.CANFDDataRateConstantIs0x8011; +begin + Assert.AreEqual(Cardinal($8011), CFG_CAN_FD_DATA_RATE); +end; + +//------------------------------------------------------------------------------ +// MIXED FORMAT CONSTANT IS0X800 B +//------------------------------------------------------------------------------ +procedure TJ2534v2Tests.MixedFormatConstantIs0x800B; +begin + Assert.AreEqual(Cardinal($800B), CFG_CAN_MIXED_FORMAT); +end; + +initialization + TDUnitX.RegisterTestFixture(TJ2534v2Tests); + +end. diff --git a/tests/Tests.Async.pas b/tests/Tests.Async.pas index ac49a6d2..e23a3acd 100644 --- a/tests/Tests.Async.pas +++ b/tests/Tests.Async.pas @@ -35,15 +35,23 @@ implementation uses System.SysUtils, OBD.Async; +//------------------------------------------------------------------------------ +// CANCELLATION TOKEN_STARTS UNCANCELLED +//------------------------------------------------------------------------------ procedure TAsyncTests.CancellationToken_StartsUncancelled; -var T: IOBDCancellationToken; +var + T: IOBDCancellationToken; begin T := NewCancellationToken; Assert.IsFalse(T.IsCancelled); end; +//------------------------------------------------------------------------------ +// CANCELLATION TOKEN_CANCEL IS IDEMPOTENT +//------------------------------------------------------------------------------ procedure TAsyncTests.CancellationToken_CancelIsIdempotent; -var T: IOBDCancellationToken; +var + T: IOBDCancellationToken; begin T := NewCancellationToken; T.Cancel; @@ -51,16 +59,24 @@ procedure TAsyncTests.CancellationToken_CancelIsIdempotent; Assert.IsTrue(T.IsCancelled); end; +//------------------------------------------------------------------------------ +// PROMISE_STARTS PENDING +//------------------------------------------------------------------------------ procedure TAsyncTests.Promise_StartsPending; -var P: IOBDPromise; +var + P: IOBDPromise; begin P := NewPromise; Assert.AreEqual(Ord(fsPending), Ord(P.State)); Assert.IsFalse(P.IsCompleted); end; +//------------------------------------------------------------------------------ +// PROMISE_SET RESULT SETTLES AND AWAIT RETURNS +//------------------------------------------------------------------------------ procedure TAsyncTests.Promise_SetResultSettlesAndAwaitReturns; -var P: IOBDPromise; +var + P: IOBDPromise; begin P := NewPromise; P.SetResult(42); @@ -68,8 +84,12 @@ procedure TAsyncTests.Promise_SetResultSettlesAndAwaitReturns; Assert.AreEqual(42, P.Await(0)); end; +//------------------------------------------------------------------------------ +// PROMISE_SET ERROR RAISES ON AWAIT +//------------------------------------------------------------------------------ procedure TAsyncTests.Promise_SetErrorRaisesOnAwait; -var P: IOBDPromise; +var + P: IOBDPromise; begin P := NewPromise; P.SetError(Exception.Create('boom')); @@ -77,8 +97,12 @@ procedure TAsyncTests.Promise_SetErrorRaisesOnAwait; Assert.WillRaise(procedure begin P.Await(0); end, Exception); end; +//------------------------------------------------------------------------------ +// PROMISE_SIGNAL CANCELLED RAISES ON AWAIT +//------------------------------------------------------------------------------ procedure TAsyncTests.Promise_SignalCancelledRaisesOnAwait; -var P: IOBDPromise; +var + P: IOBDPromise; begin P := NewPromise; P.SignalCancelled; @@ -86,13 +110,20 @@ procedure TAsyncTests.Promise_SignalCancelledRaisesOnAwait; Assert.WillRaise(procedure begin P.Await(0); end, EOBDOperationCancelled); end; +//------------------------------------------------------------------------------ +// PROMISE_AWAIT TIMEOUT THROWS +//------------------------------------------------------------------------------ procedure TAsyncTests.Promise_AwaitTimeoutThrows; -var P: IOBDPromise; +var + P: IOBDPromise; begin P := NewPromise; Assert.WillRaise(procedure begin P.Await(50); end, EOBDFutureTimeout); end; +//------------------------------------------------------------------------------ +// PROMISE_ON COMPLETE FIRES ONCE WHEN ALREADY SETTLED +//------------------------------------------------------------------------------ procedure TAsyncTests.Promise_OnCompleteFiresOnceWhenAlreadySettled; var P: IOBDPromise; @@ -105,6 +136,9 @@ procedure TAsyncTests.Promise_OnCompleteFiresOnceWhenAlreadySettled; Assert.AreEqual(1, Fired); end; +//------------------------------------------------------------------------------ +// PROMISE_ON COMPLETE FIRES AFTER SETTLE +//------------------------------------------------------------------------------ procedure TAsyncTests.Promise_OnCompleteFiresAfterSettle; var P: IOBDPromise; @@ -117,6 +151,9 @@ procedure TAsyncTests.Promise_OnCompleteFiresAfterSettle; Assert.AreEqual(1, Fired); end; +//------------------------------------------------------------------------------ +// PROMISE_DOUBLE SETTLE IS IGNORED +//------------------------------------------------------------------------------ procedure TAsyncTests.Promise_DoubleSettleIsIgnored; var P: IOBDPromise; @@ -128,15 +165,23 @@ procedure TAsyncTests.Promise_DoubleSettleIsIgnored; Assert.AreEqual(1, P.Await(0)); end; +//------------------------------------------------------------------------------ +// FROM RESULT_AWAITS IMMEDIATELY +//------------------------------------------------------------------------------ procedure TAsyncTests.FromResult_AwaitsImmediately; -var F: IOBDFuture; +var + F: IOBDFuture; begin F := FromResult('hi'); Assert.AreEqual('hi', F.Await(0)); end; +//------------------------------------------------------------------------------ +// FROM ERROR_RAISES ON AWAIT +//------------------------------------------------------------------------------ procedure TAsyncTests.FromError_RaisesOnAwait; -var F: IOBDFuture; +var + F: IOBDFuture; begin F := FromError(Exception.Create('explode')); Assert.WillRaise(procedure begin F.Await(0); end, Exception); diff --git a/tests/Tests.Audit.pas b/tests/Tests.Audit.pas index f0b5429d..cb2653cb 100644 --- a/tests/Tests.Audit.pas +++ b/tests/Tests.Audit.pas @@ -39,43 +39,67 @@ TCapturingSink = class(TInterfacedObject, IOBDLogSink) function Count: Integer; end; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TCapturingSink.Create; begin inherited Create; FEvents := TList.Create; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TCapturingSink.Destroy; begin FEvents.Free; inherited; end; +//------------------------------------------------------------------------------ +// WRITE +//------------------------------------------------------------------------------ procedure TCapturingSink.Write(const Event: TOBDLogEvent); begin FEvents.Add(Event); end; +//------------------------------------------------------------------------------ +// FLUSH +//------------------------------------------------------------------------------ procedure TCapturingSink.Flush; begin end; +//------------------------------------------------------------------------------ +// AT +//------------------------------------------------------------------------------ function TCapturingSink.At(I: Integer): TOBDLogEvent; begin Result := FEvents[I]; end; +//------------------------------------------------------------------------------ +// COUNT +//------------------------------------------------------------------------------ function TCapturingSink.Count: Integer; begin Result := FEvents.Count; end; +//------------------------------------------------------------------------------ +// SCRATCH LOG PATH +//------------------------------------------------------------------------------ function ScratchLogPath: string; begin Result := TPath.Combine(TPath.GetTempPath, Format('obdaudit-%d-%d.log', [GetCurrentProcessId, GetTickCount])); end; +//------------------------------------------------------------------------------ +// SUCCESS ROUTES TO INFO LEVEL_AND JSON HAS OUTCOME +//------------------------------------------------------------------------------ procedure TAuditTests.SuccessRoutesToInfoLevel_AndJsonHasOutcome; var Path: string; @@ -118,6 +142,9 @@ procedure TAuditTests.SuccessRoutesToInfoLevel_AndJsonHasOutcome; end; end; +//------------------------------------------------------------------------------ +// FAILURE ROUTES TO ERROR LEVEL +//------------------------------------------------------------------------------ procedure TAuditTests.FailureRoutesToErrorLevel; var Path: string; @@ -140,6 +167,9 @@ procedure TAuditTests.FailureRoutesToErrorLevel; end; end; +//------------------------------------------------------------------------------ +// DENIED ROUTES TO WARNING LEVEL +//------------------------------------------------------------------------------ procedure TAuditTests.DeniedRoutesToWarningLevel; var Path: string; @@ -160,6 +190,9 @@ procedure TAuditTests.DeniedRoutesToWarningLevel; end; end; +//------------------------------------------------------------------------------ +// SOURCE TAG SET TO AUDIT +//------------------------------------------------------------------------------ procedure TAuditTests.SourceTagSetToAudit; var Path: string; @@ -180,6 +213,9 @@ procedure TAuditTests.SourceTagSetToAudit; end; end; +//------------------------------------------------------------------------------ +// SOURCE TAG RESTORED AFTER RECORD +//------------------------------------------------------------------------------ procedure TAuditTests.SourceTagRestoredAfterRecord; var Path: string; diff --git a/tests/Tests.Components.Smoke.pas b/tests/Tests.Components.Smoke.pas index 0b028db4..f8e19f1b 100644 --- a/tests/Tests.Components.Smoke.pas +++ b/tests/Tests.Components.Smoke.pas @@ -72,6 +72,9 @@ implementation { TComponentSmokeTests } +//------------------------------------------------------------------------------ +// CIRCULAR GAUGE_CONSTRUCTS AND ACCEPTS MIN MAX VALUE +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.CircularGauge_ConstructsAndAcceptsMinMaxValue; var G: TOBDCircularGauge; @@ -89,6 +92,9 @@ procedure TComponentSmokeTests.CircularGauge_ConstructsAndAcceptsMinMaxValue; end; end; +//------------------------------------------------------------------------------ +// CIRCULAR GAUGE_VALUE IS CLAMPED INTO RANGE +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.CircularGauge_ValueIsClampedIntoRange; var G: TOBDCircularGauge; @@ -111,6 +117,9 @@ procedure TComponentSmokeTests.CircularGauge_ValueIsClampedIntoRange; end; end; +//------------------------------------------------------------------------------ +// LINEAR GAUGE_CONSTRUCTS AND ACCEPTS MIN MAX VALUE +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.LinearGauge_ConstructsAndAcceptsMinMaxValue; var G: TOBDLinearGauge; @@ -128,6 +137,9 @@ procedure TComponentSmokeTests.LinearGauge_ConstructsAndAcceptsMinMaxValue; end; end; +//------------------------------------------------------------------------------ +// LINEAR GAUGE_VALUE IS CLAMPED INTO RANGE +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.LinearGauge_ValueIsClampedIntoRange; var G: TOBDLinearGauge; @@ -145,6 +157,9 @@ procedure TComponentSmokeTests.LinearGauge_ValueIsClampedIntoRange; end; end; +//------------------------------------------------------------------------------ +// LINEAR GAUGE_ORIENTATION AND DIRECTION TOGGLE +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.LinearGauge_OrientationAndDirectionToggle; var G: TOBDLinearGauge; @@ -160,6 +175,9 @@ procedure TComponentSmokeTests.LinearGauge_OrientationAndDirectionToggle; end; end; +//------------------------------------------------------------------------------ +// TACHOMETER_CONSTRUCTS WITH RPM DEFAULTS +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.Tachometer_ConstructsWithRpmDefaults; var T: TOBDTachometer; @@ -177,6 +195,9 @@ procedure TComponentSmokeTests.Tachometer_ConstructsWithRpmDefaults; end; end; +//------------------------------------------------------------------------------ +// TACHOMETER_SHIFT LIGHT ACTIVE ABOVE SHIFT POINT +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.Tachometer_ShiftLightActiveAboveShiftPoint; var T: TOBDTachometer; @@ -197,6 +218,9 @@ procedure TComponentSmokeTests.Tachometer_ShiftLightActiveAboveShiftPoint; end; end; +//------------------------------------------------------------------------------ +// TREND GRAPH_ADD SERIES_AND PUSH VALUES +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.TrendGraph_AddSeries_AndPushValues; var G: TOBDTrendGraph; @@ -219,6 +243,9 @@ procedure TComponentSmokeTests.TrendGraph_AddSeries_AndPushValues; end; end; +//------------------------------------------------------------------------------ +// TREND GRAPH_RING BUFFER OVERWRITES OLDEST +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.TrendGraph_RingBufferOverwritesOldest; var G: TOBDTrendGraph; @@ -239,6 +266,9 @@ procedure TComponentSmokeTests.TrendGraph_RingBufferOverwritesOldest; end; end; +//------------------------------------------------------------------------------ +// TREND GRAPH_RESIZE MAX SAMPLES PRESERVES RECENT +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.TrendGraph_ResizeMaxSamplesPreservesRecent; var G: TOBDTrendGraph; @@ -259,6 +289,9 @@ procedure TComponentSmokeTests.TrendGraph_ResizeMaxSamplesPreservesRecent; end; end; +//------------------------------------------------------------------------------ +// DTC LIST_ADD REMOVE CLEAR +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.DtcList_AddRemoveClear; var L: TOBDDtcList; @@ -282,6 +315,9 @@ procedure TComponentSmokeTests.DtcList_AddRemoveClear; end; end; +//------------------------------------------------------------------------------ +// DTC LIST_SELECTED INDEX CLAMPS ON REMOVE +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.DtcList_SelectedIndexClampsOnRemove; var L: TOBDDtcList; @@ -300,6 +336,9 @@ procedure TComponentSmokeTests.DtcList_SelectedIndexClampsOnRemove; end; end; +//------------------------------------------------------------------------------ +// TERMINAL_LOG METHODS APPEND IN ORDER +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.Terminal_LogMethodsAppendInOrder; var T: TOBDTerminal; @@ -321,6 +360,9 @@ procedure TComponentSmokeTests.Terminal_LogMethodsAppendInOrder; end; end; +//------------------------------------------------------------------------------ +// TERMINAL_MAX LINES EVICTS OLDEST +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.Terminal_MaxLinesEvictsOldest; var T: TOBDTerminal; @@ -347,12 +389,18 @@ TKnobChangeRecorder = class procedure HandleChange(Sender: TObject; const Value: Single); end; +//------------------------------------------------------------------------------ +// HANDLE CHANGE +//------------------------------------------------------------------------------ procedure TKnobChangeRecorder.HandleChange(Sender: TObject; const Value: Single); begin Fired := True; LastValue := Value; end; +//------------------------------------------------------------------------------ +// KNOB_VALUE CLAMPS AND SNAPS +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.Knob_ValueClampsAndSnaps; var K: TOBDKnob; @@ -373,6 +421,9 @@ procedure TComponentSmokeTests.Knob_ValueClampsAndSnaps; end; end; +//------------------------------------------------------------------------------ +// KNOB_ON CHANGE FIRES +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.Knob_OnChangeFires; var K: TOBDKnob; @@ -392,6 +443,9 @@ procedure TComponentSmokeTests.Knob_OnChangeFires; end; end; +//------------------------------------------------------------------------------ +// SEGMENTED SWITCH_ADD SEGMENTS AND SELECT +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.SegmentedSwitch_AddSegmentsAndSelect; var S: TOBDSegmentedSwitch; @@ -409,6 +463,9 @@ procedure TComponentSmokeTests.SegmentedSwitch_AddSegmentsAndSelect; end; end; +//------------------------------------------------------------------------------ +// SEGMENTED SWITCH_SELECTED INDEX CLAMPS +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.SegmentedSwitch_SelectedIndexClamps; var S: TOBDSegmentedSwitch; @@ -426,6 +483,9 @@ procedure TComponentSmokeTests.SegmentedSwitch_SelectedIndexClamps; end; end; +//------------------------------------------------------------------------------ +// THEME_DARK APPLIES TO TREND GRAPH +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.Theme_DarkAppliesToTrendGraph; var Theme: TOBDTheme; @@ -446,6 +506,9 @@ procedure TComponentSmokeTests.Theme_DarkAppliesToTrendGraph; end; end; +//------------------------------------------------------------------------------ +// THEME_LIGHT APPLIES TO TACHOMETER +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.Theme_LightAppliesToTachometer; var Theme: TOBDTheme; @@ -464,6 +527,9 @@ procedure TComponentSmokeTests.Theme_LightAppliesToTachometer; end; end; +//------------------------------------------------------------------------------ +// LED_CONSTRUCTS AND ACCEPTS STATE +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.Led_ConstructsAndAcceptsState; var L: TOBDLed; @@ -481,6 +547,9 @@ procedure TComponentSmokeTests.Led_ConstructsAndAcceptsState; end; end; +//------------------------------------------------------------------------------ +// MATRIX DISPLAY_CONSTRUCTS +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.MatrixDisplay_Constructs; var M: TOBDMatrixDisplay; @@ -493,6 +562,9 @@ procedure TComponentSmokeTests.MatrixDisplay_Constructs; end; end; +//------------------------------------------------------------------------------ +// TOUCH HEADER_CONSTRUCTS +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.TouchHeader_Constructs; var H: TOBDTouchHeader; @@ -505,6 +577,9 @@ procedure TComponentSmokeTests.TouchHeader_Constructs; end; end; +//------------------------------------------------------------------------------ +// TOUCH STATUSBAR_CONSTRUCTS +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.TouchStatusbar_Constructs; var S: TOBDTouchStatusbar; @@ -517,6 +592,9 @@ procedure TComponentSmokeTests.TouchStatusbar_Constructs; end; end; +//------------------------------------------------------------------------------ +// TOUCH SUBHEADER_CONSTRUCTS +//------------------------------------------------------------------------------ procedure TComponentSmokeTests.TouchSubheader_Constructs; var S: TOBDTouchSubheader; diff --git a/tests/Tests.DriveCycle.Advisor.pas b/tests/Tests.DriveCycle.Advisor.pas new file mode 100644 index 00000000..125a4b06 --- /dev/null +++ b/tests/Tests.DriveCycle.Advisor.pas @@ -0,0 +1,193 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.DriveCycle.Advisor.pas +// CONTENTS : Tests for OBD.DriveCycle.Advisor +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.DriveCycle.Advisor; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TDriveCycleAdvisorTests = class + public + /// + /// Empty readiness produces no steps. + /// + [Test] procedure EmptyReadinessProducesNoSteps; + /// + /// Complete readiness produces no steps. + /// + [Test] procedure CompleteReadinessProducesNoSteps; + /// + /// Pending catalyst produces generic step. + /// + [Test] procedure PendingCatalystProducesGenericStep; + /// + /// Generic step has non empty description. + /// + [Test] procedure GenericStepHasNonEmptyDescription; + /// + /// Custom resolver overrides generic. + /// + [Test] procedure CustomResolverOverridesGeneric; + /// + /// Custom resolver empty description falls back to generic. + /// + [Test] procedure CustomResolverEmptyDescriptionFallsBackToGeneric; + /// + /// Diesel monitors produce diesel steps. + /// + [Test] procedure DieselMonitorsProduceDieselSteps; + end; + +implementation + +uses + System.SysUtils, + OBD.Protocol.WWHOBD.Readiness, + OBD.DriveCycle.Advisor; + +//------------------------------------------------------------------------------ +// EMPTY READINESS PRODUCES NO STEPS +//------------------------------------------------------------------------------ +procedure TDriveCycleAdvisorTests.EmptyReadinessProducesNoSteps; +var + R: TWWHOBDReadinessSet; + Steps: TArray; +begin + R := Default(TWWHOBDReadinessSet); + Steps := BuildDriveCycle(R); + Assert.AreEqual(0, Length(Steps)); +end; + +//------------------------------------------------------------------------------ +// COMPLETE READINESS PRODUCES NO STEPS +//------------------------------------------------------------------------------ +procedure TDriveCycleAdvisorTests.CompleteReadinessProducesNoSteps; +var + R: TWWHOBDReadinessSet; + Steps: TArray; +begin + R := Default(TWWHOBDReadinessSet); + R.Catalyst.Supported := True; R.Catalyst.Complete := True; + R.OxygenSensor.Supported := True; R.OxygenSensor.Complete := True; + Steps := BuildDriveCycle(R); + Assert.AreEqual(0, Length(Steps)); +end; + +//------------------------------------------------------------------------------ +// PENDING CATALYST PRODUCES GENERIC STEP +//------------------------------------------------------------------------------ +procedure TDriveCycleAdvisorTests.PendingCatalystProducesGenericStep; +var + R: TWWHOBDReadinessSet; + Steps: TArray; +begin + R := Default(TWWHOBDReadinessSet); + R.Catalyst.Supported := True; + R.Catalyst.Complete := False; + Steps := BuildDriveCycle(R); + Assert.AreEqual(1, Length(Steps)); + Assert.AreEqual('Catalyst', Steps[0].Monitor); + Assert.IsNotEmpty(Steps[0].Description); +end; + +//------------------------------------------------------------------------------ +// GENERIC STEP HAS NON EMPTY DESCRIPTION +//------------------------------------------------------------------------------ +procedure TDriveCycleAdvisorTests.GenericStepHasNonEmptyDescription; +var + Step: TDriveCycleStep; +begin + Step := GenericStepFor('OxygenSensor'); + Assert.IsNotEmpty(Step.Description); + Assert.IsTrue(Step.DurationSeconds > 0); +end; + +//------------------------------------------------------------------------------ +// CUSTOM RESOLVER OVERRIDES GENERIC +//------------------------------------------------------------------------------ +procedure TDriveCycleAdvisorTests.CustomResolverOverridesGeneric; +var + R: TWWHOBDReadinessSet; + Steps: TArray; +begin + R := Default(TWWHOBDReadinessSet); + R.Catalyst.Supported := True; + R.Catalyst.Complete := False; + RegisterDriveCycleResolver('test_oem', + function(const Mon, OEM: string): TDriveCycleStep + begin + Result.Monitor := Mon; + Result.Description := 'Custom OEM steps for ' + Mon; + Result.DurationSeconds := 42; + end); + Steps := BuildDriveCycle(R, 'test_oem'); + Assert.AreEqual(1, Length(Steps)); + Assert.IsTrue(Steps[0].Description.Contains('Custom OEM steps')); + Assert.AreEqual(42, Steps[0].DurationSeconds); +end; + +//------------------------------------------------------------------------------ +// CUSTOM RESOLVER EMPTY DESCRIPTION FALLS BACK TO GENERIC +//------------------------------------------------------------------------------ +procedure TDriveCycleAdvisorTests.CustomResolverEmptyDescriptionFallsBackToGeneric; +var + R: TWWHOBDReadinessSet; + Steps: TArray; +begin + R := Default(TWWHOBDReadinessSet); + R.Misfire.Supported := True; + R.Misfire.Complete := False; + RegisterDriveCycleResolver('test_fallback', + function(const Mon, OEM: string): TDriveCycleStep + begin + Result.Monitor := Mon; + Result.Description := ''; // signals "use generic" + end); + Steps := BuildDriveCycle(R, 'test_fallback'); + Assert.AreEqual(1, Length(Steps)); + Assert.IsNotEmpty(Steps[0].Description); + Assert.IsTrue(Steps[0].Description.Contains('Cold start'), + 'Should have fallen back to the generic Misfire description'); +end; + +//------------------------------------------------------------------------------ +// DIESEL MONITORS PRODUCE DIESEL STEPS +//------------------------------------------------------------------------------ +procedure TDriveCycleAdvisorTests.DieselMonitorsProduceDieselSteps; +var + R: TWWHOBDReadinessSet; + Steps: TArray; + S: TDriveCycleStep; + HasPM: Boolean; +begin + R := Default(TWWHOBDReadinessSet); + R.PMFilter.Supported := True; R.PMFilter.Complete := False; + R.NOxAftertreatment.Supported := True; R.NOxAftertreatment.Complete := False; + Steps := BuildDriveCycle(R); + Assert.AreEqual(2, Length(Steps)); + HasPM := False; + for S in Steps do + if S.Monitor = 'PMFilter' then + begin + HasPM := True; + Assert.IsTrue(S.Description.Contains('regen')); + end; + Assert.IsTrue(HasPM); +end; + +initialization + TDUnitX.RegisterTestFixture(TDriveCycleAdvisorTests); + +end. diff --git a/tests/Tests.DriveCycle.Resolvers.pas b/tests/Tests.DriveCycle.Resolvers.pas new file mode 100644 index 00000000..b90c3cae --- /dev/null +++ b/tests/Tests.DriveCycle.Resolvers.pas @@ -0,0 +1,238 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.DriveCycle.Resolvers.pas +// CONTENTS : Tests for OBD.DriveCycle.Resolvers +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.DriveCycle.Resolvers; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TDriveCycleResolversTests = class + public + /// + /// V w catalyst uses s s p388. + /// + [Test] procedure VWCatalystUsesSSP388; + /// + /// B m w catalyst uses t i s. + /// + [Test] procedure BMWCatalystUsesTIS; + /// + /// Mercedes catalyst uses w i s. + /// + [Test] procedure MercedesCatalystUsesWIS; + /// + /// Ford catalyst uses t s b. + /// + [Test] procedure FordCatalystUsesTSB; + /// + /// Toyota catalyst uses repair manual. + /// + [Test] procedure ToyotaCatalystUsesRepairManual; + /// + /// Unknown monitor falls through to generic. + /// + [Test] procedure UnknownMonitorFallsThroughToGeneric; + /// + /// Unregistered o e m uses generic. + /// + [Test] procedure UnregisteredOEMUsesGeneric; + /// + /// V w e v a p has fuel level guidance. + /// + [Test] procedure VWEVAPHasFuelLevelGuidance; + /// + /// Ford e v a p requires cold start. + /// + [Test] procedure FordEVAPRequiresColdStart; + /// + /// Toyota e v a p requires eight hour soak. + /// + [Test] procedure ToyotaEVAPRequiresEightHourSoak; + end; + +implementation + +uses + System.SysUtils, + OBD.Protocol.WWHOBD.Readiness, + OBD.DriveCycle.Advisor, + OBD.DriveCycle.Resolvers; + +//------------------------------------------------------------------------------ +// PENDING MONITOR +//------------------------------------------------------------------------------ +function PendingMonitor(const Name: string): TWWHOBDReadinessSet; +begin + Result := Default(TWWHOBDReadinessSet); + if Name = 'Catalyst' then + begin + Result.Catalyst.Supported := True; + Result.Catalyst.Complete := False; + end + else if Name = 'EvaporativeSystem' then + begin + Result.EvaporativeSystem.Supported := True; + Result.EvaporativeSystem.Complete := False; + end + else if Name = 'OxygenSensor' then + begin + Result.OxygenSensor.Supported := True; + Result.OxygenSensor.Complete := False; + end + else if Name = 'Misfire' then + begin + Result.Misfire.Supported := True; + Result.Misfire.Complete := False; + end; +end; + +//------------------------------------------------------------------------------ +// FIRST STEP +//------------------------------------------------------------------------------ +function FirstStep(const Steps: TArray): TDriveCycleStep; +begin + if Length(Steps) = 0 then + raise Exception.Create('No steps returned'); + Result := Steps[0]; +end; + +//------------------------------------------------------------------------------ +// VWCATALYST USES SSP388 +//------------------------------------------------------------------------------ +procedure TDriveCycleResolversTests.VWCatalystUsesSSP388; +var + Step: TDriveCycleStep; +begin + Step := FirstStep(BuildDriveCycle(PendingMonitor('Catalyst'), 'vw')); + Assert.IsTrue(Step.Description.Contains('SSP')); + Assert.IsTrue(Step.Description.Contains('VW')); + Assert.IsTrue(Step.DurationSeconds > 0); +end; + +//------------------------------------------------------------------------------ +// BMWCATALYST USES TIS +//------------------------------------------------------------------------------ +procedure TDriveCycleResolversTests.BMWCatalystUsesTIS; +var + Step: TDriveCycleStep; +begin + Step := FirstStep(BuildDriveCycle(PendingMonitor('Catalyst'), 'bmw')); + Assert.IsTrue(Step.Description.Contains('TIS')); + Assert.IsTrue(Step.Description.Contains('BMW')); +end; + +//------------------------------------------------------------------------------ +// MERCEDES CATALYST USES WIS +//------------------------------------------------------------------------------ +procedure TDriveCycleResolversTests.MercedesCatalystUsesWIS; +var + Step: TDriveCycleStep; +begin + Step := FirstStep(BuildDriveCycle(PendingMonitor('Catalyst'), 'mercedes')); + Assert.IsTrue(Step.Description.Contains('WIS')); +end; + +//------------------------------------------------------------------------------ +// FORD CATALYST USES TSB +//------------------------------------------------------------------------------ +procedure TDriveCycleResolversTests.FordCatalystUsesTSB; +var + Step: TDriveCycleStep; +begin + Step := FirstStep(BuildDriveCycle(PendingMonitor('Catalyst'), 'ford')); + Assert.IsTrue(Step.Description.Contains('TSB')); + Assert.IsTrue(Step.Description.Contains('OD')); // overdrive guidance +end; + +//------------------------------------------------------------------------------ +// TOYOTA CATALYST USES REPAIR MANUAL +//------------------------------------------------------------------------------ +procedure TDriveCycleResolversTests.ToyotaCatalystUsesRepairManual; +var + Step: TDriveCycleStep; +begin + Step := FirstStep(BuildDriveCycle(PendingMonitor('Catalyst'), 'toyota')); + Assert.IsTrue(Step.Description.Contains('RM')); +end; + +//------------------------------------------------------------------------------ +// UNKNOWN MONITOR FALLS THROUGH TO GENERIC +//------------------------------------------------------------------------------ +procedure TDriveCycleResolversTests.UnknownMonitorFallsThroughToGeneric; +var + Generic, VWStep: TDriveCycleStep; +begin + // Misfire isn't OEM-overridden by the VW resolver (it returns a default + // record); the advisor must fall back to the generic step. + Generic := GenericStepFor('Misfire'); + VWStep := FirstStep(BuildDriveCycle(PendingMonitor('Misfire'), 'vw')); + Assert.AreEqual(Generic.Description, VWStep.Description); +end; + +//------------------------------------------------------------------------------ +// UNREGISTERED OEMUSES GENERIC +//------------------------------------------------------------------------------ +procedure TDriveCycleResolversTests.UnregisteredOEMUsesGeneric; +var + Generic, OEMStep: TDriveCycleStep; +begin + Generic := GenericStepFor('Catalyst'); + OEMStep := FirstStep(BuildDriveCycle(PendingMonitor('Catalyst'), + 'no-such-oem')); + // BuildDriveCycle for an unregistered OEM falls all the way to the + // generic table (advisor's else branch). + Assert.AreEqual(Generic.Description, OEMStep.Description); +end; + +//------------------------------------------------------------------------------ +// VWEVAPHAS FUEL LEVEL GUIDANCE +//------------------------------------------------------------------------------ +procedure TDriveCycleResolversTests.VWEVAPHasFuelLevelGuidance; +var + Step: TDriveCycleStep; +begin + Step := FirstStep(BuildDriveCycle(PendingMonitor('EvaporativeSystem'), 'vw')); + Assert.IsTrue(Step.Description.Contains('fuel level')); + Assert.IsTrue(Step.Description.Contains('25')); // 25–75% range cited +end; + +//------------------------------------------------------------------------------ +// FORD EVAPREQUIRES COLD START +//------------------------------------------------------------------------------ +procedure TDriveCycleResolversTests.FordEVAPRequiresColdStart; +var + Step: TDriveCycleStep; +begin + Step := FirstStep(BuildDriveCycle(PendingMonitor('EvaporativeSystem'), 'ford')); + Assert.IsTrue(Step.Description.Contains('cold start') or + Step.Description.Contains('Cold start')); +end; + +//------------------------------------------------------------------------------ +// TOYOTA EVAPREQUIRES EIGHT HOUR SOAK +//------------------------------------------------------------------------------ +procedure TDriveCycleResolversTests.ToyotaEVAPRequiresEightHourSoak; +var + Step: TDriveCycleStep; +begin + Step := FirstStep(BuildDriveCycle(PendingMonitor('EvaporativeSystem'), + 'toyota')); + Assert.IsTrue(Step.Description.Contains('8 hours')); +end; + +initialization + TDUnitX.RegisterTestFixture(TDriveCycleResolversTests); + +end. diff --git a/tests/Tests.ECU.Flashing.Checkpoint.pas b/tests/Tests.ECU.Flashing.Checkpoint.pas new file mode 100644 index 00000000..aa5458d9 --- /dev/null +++ b/tests/Tests.ECU.Flashing.Checkpoint.pas @@ -0,0 +1,216 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.ECU.Flashing.Checkpoint.pas +// CONTENTS : Tests for OBD.ECU.Flashing.Checkpoint +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.ECU.Flashing.Checkpoint; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TFlashCheckpointTests = class + strict private + FFwPath, FSidecar, FSnap: string; + procedure WriteFile(const Path, Body: string); + public + [Setup] procedure Setup; + [TearDown] procedure TearDown; + + /// + /// Initialise persists and is resumable. + /// + [Test] procedure InitialisePersistsAndIsResumable; + /// + /// Progress is recorded across blocks. + /// + [Test] procedure ProgressIsRecordedAcrossBlocks; + /// + /// Firmware mismatch prevents resume. + /// + [Test] procedure FirmwareMismatchPreventsResume; + /// + /// Completed flash is not resumable. + /// + [Test] procedure CompletedFlashIsNotResumable; + /// + /// Clear deletes sidecar. + /// + [Test] procedure ClearDeletesSidecar; + /// + /// Out of range block index raises. + /// + [Test] procedure OutOfRangeBlockIndexRaises; + end; + +implementation + +uses + System.SysUtils, System.IOUtils, + OBD.ECU.Flashing.Checkpoint; + +//------------------------------------------------------------------------------ +// WRITE FILE +//------------------------------------------------------------------------------ +procedure TFlashCheckpointTests.WriteFile(const Path, Body: string); +begin + TFile.WriteAllText(Path, Body, TEncoding.UTF8); +end; + +//------------------------------------------------------------------------------ +// SETUP +//------------------------------------------------------------------------------ +procedure TFlashCheckpointTests.Setup; +var + Stem: string; +begin + Stem := TGUID.NewGuid.ToString; + FFwPath := TPath.Combine(TPath.GetTempPath, 'obd-fw-' + Stem + '.bin'); + FSidecar := TPath.Combine(TPath.GetTempPath, 'obd-cp-' + Stem + '.json'); + FSnap := TPath.Combine(TPath.GetTempPath, 'obd-snap-' + Stem + '.bin'); + WriteFile(FFwPath, 'firmware-payload-v1'); +end; + +//------------------------------------------------------------------------------ +// TEAR DOWN +//------------------------------------------------------------------------------ +procedure TFlashCheckpointTests.TearDown; +begin + if TFile.Exists(FFwPath) then TFile.Delete(FFwPath); + if TFile.Exists(FSidecar) then TFile.Delete(FSidecar); + if TFile.Exists(FSnap) then TFile.Delete(FSnap); +end; + +//------------------------------------------------------------------------------ +// INITIALISE PERSISTS AND IS RESUMABLE +//------------------------------------------------------------------------------ +procedure TFlashCheckpointTests.InitialisePersistsAndIsResumable; +var + CP: TOBDFlashCheckpoint; + R: TOBDFlashCheckpointVerifyResult; +begin + CP := TOBDFlashCheckpoint.Initialise(FSidecar, FFwPath, 256, 10, FSnap); + try + Assert.IsTrue(TFile.Exists(FSidecar)); + Assert.AreEqual(-1, CP.State.LastCompletedBlock); + finally + CP.Free; + end; + R := TOBDFlashCheckpoint.LoadAndVerify(FSidecar, FFwPath); + Assert.IsTrue(R.Resumable, R.Reason); + Assert.AreEqual(0, R.NextBlock); +end; + +//------------------------------------------------------------------------------ +// PROGRESS IS RECORDED ACROSS BLOCKS +//------------------------------------------------------------------------------ +procedure TFlashCheckpointTests.ProgressIsRecordedAcrossBlocks; +var + CP: TOBDFlashCheckpoint; + R: TOBDFlashCheckpointVerifyResult; +begin + CP := TOBDFlashCheckpoint.Initialise(FSidecar, FFwPath, 256, 10, FSnap); + try + CP.MarkBlockComplete(0); + CP.MarkBlockComplete(1); + CP.MarkBlockComplete(2); + // Idempotent re-mark stays at 2. + CP.MarkBlockComplete(1); + Assert.AreEqual(2, CP.State.LastCompletedBlock); + finally + CP.Free; + end; + R := TOBDFlashCheckpoint.LoadAndVerify(FSidecar, FFwPath); + Assert.IsTrue(R.Resumable); + Assert.AreEqual(3, R.NextBlock); +end; + +//------------------------------------------------------------------------------ +// FIRMWARE MISMATCH PREVENTS RESUME +//------------------------------------------------------------------------------ +procedure TFlashCheckpointTests.FirmwareMismatchPreventsResume; +var + CP: TOBDFlashCheckpoint; + R: TOBDFlashCheckpointVerifyResult; +begin + CP := TOBDFlashCheckpoint.Initialise(FSidecar, FFwPath, 256, 10, FSnap); + try + CP.MarkBlockComplete(0); + finally + CP.Free; + end; + // Replace firmware with different bytes; SHA changes. + WriteFile(FFwPath, 'firmware-payload-v2'); + R := TOBDFlashCheckpoint.LoadAndVerify(FSidecar, FFwPath); + Assert.IsFalse(R.Resumable); + Assert.IsTrue(R.Reason.Contains('SHA-256 mismatch'), + 'Reason should call out the SHA mismatch: ' + R.Reason); +end; + +//------------------------------------------------------------------------------ +// COMPLETED FLASH IS NOT RESUMABLE +//------------------------------------------------------------------------------ +procedure TFlashCheckpointTests.CompletedFlashIsNotResumable; +var + CP: TOBDFlashCheckpoint; + R: TOBDFlashCheckpointVerifyResult; + I: Integer; +begin + CP := TOBDFlashCheckpoint.Initialise(FSidecar, FFwPath, 256, 3, FSnap); + try + for I := 0 to 2 do CP.MarkBlockComplete(I); + finally + CP.Free; + end; + R := TOBDFlashCheckpoint.LoadAndVerify(FSidecar, FFwPath); + Assert.IsFalse(R.Resumable); + Assert.IsTrue(R.Reason.Contains('all blocks already completed')); +end; + +//------------------------------------------------------------------------------ +// CLEAR DELETES SIDECAR +//------------------------------------------------------------------------------ +procedure TFlashCheckpointTests.ClearDeletesSidecar; +var + CP: TOBDFlashCheckpoint; +begin + CP := TOBDFlashCheckpoint.Initialise(FSidecar, FFwPath, 256, 5, FSnap); + try + Assert.IsTrue(TFile.Exists(FSidecar)); + CP.Clear; + Assert.IsFalse(TFile.Exists(FSidecar)); + finally + CP.Free; + end; +end; + +//------------------------------------------------------------------------------ +// OUT OF RANGE BLOCK INDEX RAISES +//------------------------------------------------------------------------------ +procedure TFlashCheckpointTests.OutOfRangeBlockIndexRaises; +var + CP: TOBDFlashCheckpoint; +begin + CP := TOBDFlashCheckpoint.Initialise(FSidecar, FFwPath, 256, 3, FSnap); + try + Assert.WillRaise( + procedure begin CP.MarkBlockComplete(99); end, + EOBDFlashCheckpoint); + finally + CP.Free; + end; +end; + +initialization + TDUnitX.RegisterTestFixture(TFlashCheckpointTests); + +end. diff --git a/tests/Tests.ECU.Flashing.VoltageGate.pas b/tests/Tests.ECU.Flashing.VoltageGate.pas new file mode 100644 index 00000000..da00246b --- /dev/null +++ b/tests/Tests.ECU.Flashing.VoltageGate.pas @@ -0,0 +1,264 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.ECU.Flashing.VoltageGate.pas +// CONTENTS : Tests for OBD.ECU.Flashing.VoltageGate +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.ECU.Flashing.VoltageGate; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TVoltageGateTests = class + public + /// + /// Default threshold is125 v. + /// + [Test] procedure DefaultThresholdIs125V; + /// + /// Reading above threshold passes. + /// + [Test] procedure ReadingAboveThresholdPasses; + /// + /// Reading below threshold fails. + /// + [Test] procedure ReadingBelowThresholdFails; + /// + /// Per o e m override takes effect. + /// + [Test] procedure PerOEMOverrideTakesEffect; + /// + /// Per o e m lookup is case insensitive. + /// + [Test] procedure PerOEMLookupIsCaseInsensitive; + /// + /// Nil reader produces graceful failure. + /// + [Test] procedure NilReaderProducesGracefulFailure; + /// + /// Reader that raises is caught. + /// + [Test] procedure ReaderThatRaisesIsCaught; + /// + /// Require pass raises on low voltage. + /// + [Test] procedure RequirePassRaisesOnLowVoltage; + /// + /// Require pass raises on reader unavailable. + /// + [Test] procedure RequirePassRaisesOnReaderUnavailable; + /// + /// Non positive voltage rejected. + /// + [Test] procedure NonPositiveVoltageRejected; + end; + +implementation + +uses + System.SysUtils, OBD.ECU.Flashing.VoltageGate; + +//------------------------------------------------------------------------------ +// DEFAULT THRESHOLD IS125 V +//------------------------------------------------------------------------------ +procedure TVoltageGateTests.DefaultThresholdIs125V; +var + G: TOBDProgrammingVoltageGate; + R: TOBDVoltageGateResult; +begin + G := TOBDProgrammingVoltageGate.Create; + try + R := G.Check(function: Single begin Result := 12.6; end); + Assert.IsTrue(R.Passed); + Assert.AreEqual(Single(12.5), R.RequiredVolts, 0.001); + finally + G.Free; + end; +end; + +//------------------------------------------------------------------------------ +// READING ABOVE THRESHOLD PASSES +//------------------------------------------------------------------------------ +procedure TVoltageGateTests.ReadingAboveThresholdPasses; +var + G: TOBDProgrammingVoltageGate; + R: TOBDVoltageGateResult; +begin + G := TOBDProgrammingVoltageGate.Create; + try + R := G.Check(function: Single begin Result := 13.0; end); + Assert.IsTrue(R.Passed, R.Reason); + finally + G.Free; + end; +end; + +//------------------------------------------------------------------------------ +// READING BELOW THRESHOLD FAILS +//------------------------------------------------------------------------------ +procedure TVoltageGateTests.ReadingBelowThresholdFails; +var + G: TOBDProgrammingVoltageGate; + R: TOBDVoltageGateResult; +begin + G := TOBDProgrammingVoltageGate.Create; + try + R := G.Check(function: Single begin Result := 11.5; end); + Assert.IsFalse(R.Passed); + Assert.IsTrue(R.Reason.Contains('11.50'), + 'Reason should embed the measured voltage: ' + R.Reason); + finally + G.Free; + end; +end; + +//------------------------------------------------------------------------------ +// PER OEMOVERRIDE TAKES EFFECT +//------------------------------------------------------------------------------ +procedure TVoltageGateTests.PerOEMOverrideTakesEffect; +var + G: TOBDProgrammingVoltageGate; + R: TOBDVoltageGateResult; +begin + G := TOBDProgrammingVoltageGate.Create; + try + G.SetOEMThreshold('tesla', 13.0); + R := G.Check(function: Single begin Result := 12.6; end, 'tesla'); + Assert.IsFalse(R.Passed); + Assert.AreEqual(Single(13.0), R.RequiredVolts, 0.001); + Assert.AreEqual('tesla', R.OEMUsed); + finally + G.Free; + end; +end; + +//------------------------------------------------------------------------------ +// PER OEMLOOKUP IS CASE INSENSITIVE +//------------------------------------------------------------------------------ +procedure TVoltageGateTests.PerOEMLookupIsCaseInsensitive; +var + G: TOBDProgrammingVoltageGate; + R: TOBDVoltageGateResult; +begin + G := TOBDProgrammingVoltageGate.Create; + try + G.SetOEMThreshold('Tesla', 13.0); + R := G.Check(function: Single begin Result := 13.5; end, 'TESLA'); + Assert.IsTrue(R.Passed); + Assert.AreEqual(Single(13.0), R.RequiredVolts, 0.001); + finally + G.Free; + end; +end; + +//------------------------------------------------------------------------------ +// NIL READER PRODUCES GRACEFUL FAILURE +//------------------------------------------------------------------------------ +procedure TVoltageGateTests.NilReaderProducesGracefulFailure; +var + G: TOBDProgrammingVoltageGate; + R: TOBDVoltageGateResult; +begin + G := TOBDProgrammingVoltageGate.Create; + try + R := G.Check(nil); + Assert.IsFalse(R.Passed); + Assert.IsNotEmpty(R.Reason); + finally + G.Free; + end; +end; + +//------------------------------------------------------------------------------ +// READER THAT RAISES IS CAUGHT +//------------------------------------------------------------------------------ +procedure TVoltageGateTests.ReaderThatRaisesIsCaught; +var + G: TOBDProgrammingVoltageGate; + R: TOBDVoltageGateResult; +begin + G := TOBDProgrammingVoltageGate.Create; + try + R := G.Check(function: Single + begin + raise Exception.Create('adapter offline'); + end); + Assert.IsFalse(R.Passed); + Assert.IsTrue(R.Reason.Contains('adapter offline')); + finally + G.Free; + end; +end; + +//------------------------------------------------------------------------------ +// REQUIRE PASS RAISES ON LOW VOLTAGE +//------------------------------------------------------------------------------ +procedure TVoltageGateTests.RequirePassRaisesOnLowVoltage; +var + G: TOBDProgrammingVoltageGate; +begin + G := TOBDProgrammingVoltageGate.Create; + try + Assert.WillRaise( + procedure + begin + G.RequirePass(function: Single begin Result := 10.0; end); + end, + EOBDProgrammingVoltageTooLow); + finally + G.Free; + end; +end; + +//------------------------------------------------------------------------------ +// REQUIRE PASS RAISES ON READER UNAVAILABLE +//------------------------------------------------------------------------------ +procedure TVoltageGateTests.RequirePassRaisesOnReaderUnavailable; +var + G: TOBDProgrammingVoltageGate; +begin + G := TOBDProgrammingVoltageGate.Create; + try + Assert.WillRaise( + procedure + begin + G.RequirePass(function: Single + begin raise Exception.Create('USB unplugged') end); + end, + EOBDProgrammingVoltageUnavailable); + finally + G.Free; + end; +end; + +//------------------------------------------------------------------------------ +// NON POSITIVE VOLTAGE REJECTED +//------------------------------------------------------------------------------ +procedure TVoltageGateTests.NonPositiveVoltageRejected; +var + G: TOBDProgrammingVoltageGate; + R: TOBDVoltageGateResult; +begin + G := TOBDProgrammingVoltageGate.Create; + try + R := G.Check(function: Single begin Result := 0; end); + Assert.IsFalse(R.Passed); + Assert.IsTrue(R.Reason.Contains('non-positive')); + finally + G.Free; + end; +end; + +initialization + TDUnitX.RegisterTestFixture(TVoltageGateTests); + +end. diff --git a/tests/Tests.ECU.Flashing.pas b/tests/Tests.ECU.Flashing.pas index 0dd93e81..b1fb263b 100644 --- a/tests/Tests.ECU.Flashing.pas +++ b/tests/Tests.ECU.Flashing.pas @@ -13,15 +13,45 @@ interface [TestFixture] TFlashingTests = class public + /// + /// Happy path transitions through every stage and completes. + /// [Test] procedure HappyPath_TransitionsThroughEveryStageAndCompletes; + /// + /// Health check fail stops at pre check. + /// [Test] procedure HealthCheckFail_StopsAtPreCheck; + /// + /// Signature fail stops before writing. + /// [Test] procedure SignatureFail_StopsBeforeWriting; + /// + /// Snapshot fail stops before writing. + /// [Test] procedure SnapshotFail_StopsBeforeWriting; + /// + /// Write fail triggers rollback to snapshot bytes. + /// [Test] procedure WriteFail_TriggersRollback_ToSnapshotBytes; + /// + /// Finalise fail triggers rollback. + /// [Test] procedure FinaliseFail_TriggersRollback; + /// + /// Verify fail triggers rollback. + /// [Test] procedure VerifyFail_TriggersRollback; + /// + /// Progress events fire through write phase. + /// [Test] procedure ProgressEventsFireThroughWritePhase; + /// + /// Cancel during write aborts before all chunks. + /// [Test] procedure CancelDuringWrite_AbortsBeforeAllChunks; + /// + /// Block size splits firmware correctly. + /// [Test] procedure BlockSize_SplitsFirmwareCorrectly; end; @@ -31,19 +61,29 @@ implementation System.SysUtils, System.Classes, System.Generics.Collections, OBD.ECU.Flashing, OBD.ECU.Signature; +//------------------------------------------------------------------------------ +// MAKE FIRMWARE +//------------------------------------------------------------------------------ function MakeFirmware(const Bytes: array of Byte): TBytes; -var I: Integer; +var + I: Integer; begin SetLength(Result, Length(Bytes)); for I := 0 to High(Bytes) do Result[I] := Bytes[I]; end; +//------------------------------------------------------------------------------ +// FIRMWARE MATCHING SNAPSHOT +//------------------------------------------------------------------------------ function FirmwareMatchingSnapshot(const Snapshot: TBytes; out Sig: TBytes): TBytes; begin Result := Copy(Snapshot, 0, Length(Snapshot)); Sig := ComputeSha256(Result); end; +//------------------------------------------------------------------------------ +// HAPPY PATH_TRANSITIONS THROUGH EVERY STAGE AND COMPLETES +//------------------------------------------------------------------------------ procedure TFlashingTests.HappyPath_TransitionsThroughEveryStageAndCompletes; var Flasher: TOBDECUFlashing; @@ -62,19 +102,38 @@ procedure TFlashingTests.HappyPath_TransitionsThroughEveryStageAndCompletes; Flasher.BlockSize := 4; Flasher.OnHealthCheck := function(out Reason: string): Boolean - begin HealthCalled := True; Reason := ''; Result := True; end; + begin + HealthCalled := True; + Reason := ''; + Result := True; + end; Flasher.OnSnapshot := function(const Progress: TProc): TBytes - begin Progress(50); Progress(100); Result := Snapshot; end; + begin + Progress(50); + Progress(100); + Result := Snapshot; + end; Flasher.OnWriteChunk := function(BlockIndex: Integer; const Data: TBytes): Boolean - begin WrittenBlocks.Add(Data); Result := True; end; + begin + WrittenBlocks.Add(Data); + Result := True; + end; Flasher.OnFinalise := function(out Reason: string): Boolean - begin FinaliseCalled := True; Reason := ''; Result := True; end; + begin + FinaliseCalled := True; + Reason := ''; + Result := True; + end; Flasher.OnVerifyEcu := function(out Reason: string): Boolean - begin VerifyCalled := True; Reason := ''; Result := True; end; + begin + VerifyCalled := True; + Reason := ''; + Result := True; + end; Assert.IsTrue(Flasher.StartFlash(Firmware, Signature)); Assert.AreEqual(Ord(fsCompleted), Ord(Flasher.Stage)); @@ -89,6 +148,9 @@ procedure TFlashingTests.HappyPath_TransitionsThroughEveryStageAndCompletes; end; end; +//------------------------------------------------------------------------------ +// HEALTH CHECK FAIL_STOPS AT PRE CHECK +//------------------------------------------------------------------------------ procedure TFlashingTests.HealthCheckFail_StopsAtPreCheck; var Flasher: TOBDECUFlashing; @@ -105,12 +167,20 @@ procedure TFlashingTests.HealthCheckFail_StopsAtPreCheck; try Flasher.OnHealthCheck := function(out Reason: string): Boolean - begin Reason := 'battery low'; Result := False; end; + begin + Reason := 'battery low'; + Result := False; + end; Flasher.OnWriteChunk := function(BlockIndex: Integer; const Data: TBytes): Boolean - begin WriteCalled := True; Result := True; end; + begin + WriteCalled := True; + Result := True; + end; Flasher.OnFailed := procedure(Sender: TObject; Stage: TOBDFlashStage) - begin FailedAt := Stage; end; + begin + FailedAt := Stage; + end; Assert.IsFalse(Flasher.StartFlash(Firmware, Sig)); Assert.AreEqual(Ord(fsFailed), Ord(Flasher.Stage)); @@ -121,6 +191,9 @@ procedure TFlashingTests.HealthCheckFail_StopsAtPreCheck; end; end; +//------------------------------------------------------------------------------ +// SIGNATURE FAIL_STOPS BEFORE WRITING +//------------------------------------------------------------------------------ procedure TFlashingTests.SignatureFail_StopsBeforeWriting; var Flasher: TOBDECUFlashing; @@ -137,9 +210,14 @@ procedure TFlashingTests.SignatureFail_StopsBeforeWriting; try Flasher.OnWriteChunk := function(BlockIndex: Integer; const Data: TBytes): Boolean - begin WriteCalled := True; Result := True; end; + begin + WriteCalled := True; + Result := True; + end; Flasher.OnFailed := procedure(Sender: TObject; Stage: TOBDFlashStage) - begin FailedAt := Stage; end; + begin + FailedAt := Stage; + end; Assert.IsFalse(Flasher.StartFlash(Firmware, BadSig)); Assert.AreEqual(Ord(fsVerifySignature), Ord(FailedAt)); @@ -149,6 +227,9 @@ procedure TFlashingTests.SignatureFail_StopsBeforeWriting; end; end; +//------------------------------------------------------------------------------ +// SNAPSHOT FAIL_STOPS BEFORE WRITING +//------------------------------------------------------------------------------ procedure TFlashingTests.SnapshotFail_StopsBeforeWriting; var Flasher: TOBDECUFlashing; @@ -168,9 +249,14 @@ procedure TFlashingTests.SnapshotFail_StopsBeforeWriting; begin Result := nil; end; // empty buffer ⇒ fail Flasher.OnWriteChunk := function(BlockIndex: Integer; const Data: TBytes): Boolean - begin WriteCalled := True; Result := True; end; + begin + WriteCalled := True; + Result := True; + end; Flasher.OnFailed := procedure(Sender: TObject; Stage: TOBDFlashStage) - begin FailedAt := Stage; end; + begin + FailedAt := Stage; + end; Assert.IsFalse(Flasher.StartFlash(Firmware, Sig)); Assert.AreEqual(Ord(fsSnapshot), Ord(FailedAt)); @@ -180,6 +266,9 @@ procedure TFlashingTests.SnapshotFail_StopsBeforeWriting; end; end; +//------------------------------------------------------------------------------ +// WRITE FAIL_TRIGGERS ROLLBACK_TO SNAPSHOT BYTES +//------------------------------------------------------------------------------ procedure TFlashingTests.WriteFail_TriggersRollback_ToSnapshotBytes; var Flasher: TOBDECUFlashing; @@ -200,7 +289,9 @@ procedure TFlashingTests.WriteFail_TriggersRollback_ToSnapshotBytes; Flasher.BlockSize := 2; Flasher.OnSnapshot := function(const Progress: TProc): TBytes - begin Result := Snapshot; end; + begin + Result := Snapshot; + end; Flasher.OnWriteChunk := function(BlockIndex: Integer; const Data: TBytes): Boolean begin @@ -240,6 +331,9 @@ procedure TFlashingTests.WriteFail_TriggersRollback_ToSnapshotBytes; end; end; +//------------------------------------------------------------------------------ +// FINALISE FAIL_TRIGGERS ROLLBACK +//------------------------------------------------------------------------------ procedure TFlashingTests.FinaliseFail_TriggersRollback; var Flasher: TOBDECUFlashing; @@ -256,13 +350,21 @@ procedure TFlashingTests.FinaliseFail_TriggersRollback; Flasher.BlockSize := 4; Flasher.OnSnapshot := function(const Progress: TProc): TBytes - begin Result := Snapshot; end; + begin + Result := Snapshot; + end; Flasher.OnWriteChunk := function(BlockIndex: Integer; const Data: TBytes): Boolean - begin Inc(WriteCount); Result := True; end; + begin + Inc(WriteCount); + Result := True; + end; Flasher.OnFinalise := function(out Reason: string): Boolean - begin Reason := 'checksum mismatch'; Result := False; end; + begin + Reason := 'checksum mismatch'; + Result := False; + end; Assert.IsFalse(Flasher.StartFlash(Firmware, Sig)); Assert.AreEqual(Ord(fsFailed), Ord(Flasher.Stage)); @@ -273,6 +375,9 @@ procedure TFlashingTests.FinaliseFail_TriggersRollback; end; end; +//------------------------------------------------------------------------------ +// VERIFY FAIL_TRIGGERS ROLLBACK +//------------------------------------------------------------------------------ procedure TFlashingTests.VerifyFail_TriggersRollback; var Flasher: TOBDECUFlashing; @@ -289,13 +394,21 @@ procedure TFlashingTests.VerifyFail_TriggersRollback; Flasher.BlockSize := 4; Flasher.OnSnapshot := function(const Progress: TProc): TBytes - begin Result := Snapshot; end; + begin + Result := Snapshot; + end; Flasher.OnWriteChunk := function(BlockIndex: Integer; const Data: TBytes): Boolean - begin Inc(WriteCount); Result := True; end; + begin + Inc(WriteCount); + Result := True; + end; Flasher.OnVerifyEcu := function(out Reason: string): Boolean - begin Reason := 'verify failed'; Result := False; end; + begin + Reason := 'verify failed'; + Result := False; + end; Assert.IsFalse(Flasher.StartFlash(Firmware, Sig)); Assert.AreEqual(Ord(fsFailed), Ord(Flasher.Stage)); @@ -305,6 +418,9 @@ procedure TFlashingTests.VerifyFail_TriggersRollback; end; end; +//------------------------------------------------------------------------------ +// PROGRESS EVENTS FIRE THROUGH WRITE PHASE +//------------------------------------------------------------------------------ procedure TFlashingTests.ProgressEventsFireThroughWritePhase; var Flasher: TOBDECUFlashing; @@ -320,7 +436,9 @@ procedure TFlashingTests.ProgressEventsFireThroughWritePhase; Flasher.BlockSize := 2; Flasher.OnWriteChunk := function(BlockIndex: Integer; const Data: TBytes): Boolean - begin Result := True; end; + begin + Result := True; + end; Flasher.OnProgress := procedure(Sender: TObject; Stage: TOBDFlashStage; PercentComplete: Single; const StageMessage: string) begin @@ -339,6 +457,9 @@ procedure TFlashingTests.ProgressEventsFireThroughWritePhase; end; end; +//------------------------------------------------------------------------------ +// CANCEL DURING WRITE_ABORTS BEFORE ALL CHUNKS +//------------------------------------------------------------------------------ procedure TFlashingTests.CancelDuringWrite_AbortsBeforeAllChunks; var Flasher: TOBDECUFlashing; @@ -371,6 +492,9 @@ procedure TFlashingTests.CancelDuringWrite_AbortsBeforeAllChunks; end; end; +//------------------------------------------------------------------------------ +// BLOCK SIZE_SPLITS FIRMWARE CORRECTLY +//------------------------------------------------------------------------------ procedure TFlashingTests.BlockSize_SplitsFirmwareCorrectly; var Flasher: TOBDECUFlashing; @@ -386,7 +510,10 @@ procedure TFlashingTests.BlockSize_SplitsFirmwareCorrectly; Flasher.BlockSize := 3; Flasher.OnWriteChunk := function(BlockIndex: Integer; const Data: TBytes): Boolean - begin Sizes.Add(Length(Data)); Result := True; end; + begin + Sizes.Add(Length(Data)); + Result := True; + end; Assert.IsTrue(Flasher.StartFlash(Firmware, Sig)); // 7 bytes / 3 → 3, 3, 1 diff --git a/tests/Tests.ECU.Signature.BCrypt.pas b/tests/Tests.ECU.Signature.BCrypt.pas index 24a71392..3cba8754 100644 --- a/tests/Tests.ECU.Signature.BCrypt.pas +++ b/tests/Tests.ECU.Signature.BCrypt.pas @@ -21,18 +21,51 @@ interface [TestFixture] TBCryptVerifierTests = class public + /// + /// R s a construct recognises algorithm. + /// [Test] procedure RSA_Construct_RecognisesAlgorithm; + /// + /// R s a verify accepts known good signature. + /// [Test] procedure RSA_Verify_AcceptsKnownGoodSignature; + /// + /// R s a verify rejects tampered firmware. + /// [Test] procedure RSA_Verify_RejectsTamperedFirmware; + /// + /// R s a verify rejects tampered signature. + /// [Test] procedure RSA_Verify_RejectsTamperedSignature; + /// + /// R s a verify rejects empty firmware. + /// [Test] procedure RSA_Verify_RejectsEmptyFirmware; + /// + /// R s a verify rejects empty signature. + /// [Test] procedure RSA_Verify_RejectsEmptySignature; + /// + /// E c d s a construct recognises algorithm. + /// [Test] procedure ECDSA_Construct_RecognisesAlgorithm; + /// + /// E c d s a verify accepts known good signature. + /// [Test] procedure ECDSA_Verify_AcceptsKnownGoodSignature; + /// + /// E c d s a verify rejects tampered firmware. + /// [Test] procedure ECDSA_Verify_RejectsTamperedFirmware; + /// + /// Construct rejects empty der. + /// [Test] procedure Construct_RejectsEmptyDer; + /// + /// Construct rejects garbage der. + /// [Test] procedure Construct_RejectsGarbageDer; end; @@ -44,37 +77,56 @@ implementation const {$I 'fixtures\test-fixtures.inc'} +//------------------------------------------------------------------------------ +// PUB KEY RSA +//------------------------------------------------------------------------------ function PubKeyRSA: TBytes; begin SetLength(Result, TEST_RSA_PUB_DER_LEN); Move(TEST_RSA_PUB_DER[0], Result[0], TEST_RSA_PUB_DER_LEN); end; +//------------------------------------------------------------------------------ +// PUB KEY EC +//------------------------------------------------------------------------------ function PubKeyEC: TBytes; begin SetLength(Result, TEST_EC_PUB_DER_LEN); Move(TEST_EC_PUB_DER[0], Result[0], TEST_EC_PUB_DER_LEN); end; +//------------------------------------------------------------------------------ +// SIG RSA +//------------------------------------------------------------------------------ function SigRSA: TBytes; begin SetLength(Result, TEST_RSA_SIG_PKCS1_LEN); Move(TEST_RSA_SIG_PKCS1[0], Result[0], TEST_RSA_SIG_PKCS1_LEN); end; +//------------------------------------------------------------------------------ +// SIG EC +//------------------------------------------------------------------------------ function SigEC: TBytes; begin SetLength(Result, TEST_EC_SIG_LEN); Move(TEST_EC_SIG[0], Result[0], TEST_EC_SIG_LEN); end; +//------------------------------------------------------------------------------ +// MESSAGE BYTES +//------------------------------------------------------------------------------ function MessageBytes: TBytes; begin Result := TEncoding.ASCII.GetBytes(TEST_MESSAGE_TEXT); end; +//------------------------------------------------------------------------------ +// RSA_CONSTRUCT_RECOGNISES ALGORITHM +//------------------------------------------------------------------------------ procedure TBCryptVerifierTests.RSA_Construct_RecognisesAlgorithm; -var V: TOBDBCryptVerifier; +var + V: TOBDBCryptVerifier; begin V := TOBDBCryptVerifier.Create(PubKeyRSA); try @@ -85,8 +137,12 @@ procedure TBCryptVerifierTests.RSA_Construct_RecognisesAlgorithm; end; end; +//------------------------------------------------------------------------------ +// RSA_VERIFY_ACCEPTS KNOWN GOOD SIGNATURE +//------------------------------------------------------------------------------ procedure TBCryptVerifierTests.RSA_Verify_AcceptsKnownGoodSignature; -var V: TOBDBCryptVerifier; +var + V: TOBDBCryptVerifier; begin V := TOBDBCryptVerifier.Create(PubKeyRSA); try @@ -97,6 +153,9 @@ procedure TBCryptVerifierTests.RSA_Verify_AcceptsKnownGoodSignature; end; end; +//------------------------------------------------------------------------------ +// RSA_VERIFY_REJECTS TAMPERED FIRMWARE +//------------------------------------------------------------------------------ procedure TBCryptVerifierTests.RSA_Verify_RejectsTamperedFirmware; var V: TOBDBCryptVerifier; @@ -112,6 +171,9 @@ procedure TBCryptVerifierTests.RSA_Verify_RejectsTamperedFirmware; end; end; +//------------------------------------------------------------------------------ +// RSA_VERIFY_REJECTS TAMPERED SIGNATURE +//------------------------------------------------------------------------------ procedure TBCryptVerifierTests.RSA_Verify_RejectsTamperedSignature; var V: TOBDBCryptVerifier; @@ -128,6 +190,9 @@ procedure TBCryptVerifierTests.RSA_Verify_RejectsTamperedSignature; end; end; +//------------------------------------------------------------------------------ +// RSA_VERIFY_REJECTS EMPTY FIRMWARE +//------------------------------------------------------------------------------ procedure TBCryptVerifierTests.RSA_Verify_RejectsEmptyFirmware; var V: TOBDBCryptVerifier; Empty: TBytes; begin @@ -140,6 +205,9 @@ procedure TBCryptVerifierTests.RSA_Verify_RejectsEmptyFirmware; end; end; +//------------------------------------------------------------------------------ +// RSA_VERIFY_REJECTS EMPTY SIGNATURE +//------------------------------------------------------------------------------ procedure TBCryptVerifierTests.RSA_Verify_RejectsEmptySignature; var V: TOBDBCryptVerifier; Empty: TBytes; begin @@ -152,8 +220,12 @@ procedure TBCryptVerifierTests.RSA_Verify_RejectsEmptySignature; end; end; +//------------------------------------------------------------------------------ +// ECDSA_CONSTRUCT_RECOGNISES ALGORITHM +//------------------------------------------------------------------------------ procedure TBCryptVerifierTests.ECDSA_Construct_RecognisesAlgorithm; -var V: TOBDBCryptVerifier; +var + V: TOBDBCryptVerifier; begin V := TOBDBCryptVerifier.Create(PubKeyEC); try @@ -164,8 +236,12 @@ procedure TBCryptVerifierTests.ECDSA_Construct_RecognisesAlgorithm; end; end; +//------------------------------------------------------------------------------ +// ECDSA_VERIFY_ACCEPTS KNOWN GOOD SIGNATURE +//------------------------------------------------------------------------------ procedure TBCryptVerifierTests.ECDSA_Verify_AcceptsKnownGoodSignature; -var V: TOBDBCryptVerifier; +var + V: TOBDBCryptVerifier; begin V := TOBDBCryptVerifier.Create(PubKeyEC); try @@ -176,6 +252,9 @@ procedure TBCryptVerifierTests.ECDSA_Verify_AcceptsKnownGoodSignature; end; end; +//------------------------------------------------------------------------------ +// ECDSA_VERIFY_REJECTS TAMPERED FIRMWARE +//------------------------------------------------------------------------------ procedure TBCryptVerifierTests.ECDSA_Verify_RejectsTamperedFirmware; var V: TOBDBCryptVerifier; @@ -190,8 +269,12 @@ procedure TBCryptVerifierTests.ECDSA_Verify_RejectsTamperedFirmware; end; end; +//------------------------------------------------------------------------------ +// CONSTRUCT_REJECTS EMPTY DER +//------------------------------------------------------------------------------ procedure TBCryptVerifierTests.Construct_RejectsEmptyDer; -var Empty: TBytes; +var + Empty: TBytes; begin SetLength(Empty, 0); Assert.WillRaise( @@ -199,8 +282,12 @@ procedure TBCryptVerifierTests.Construct_RejectsEmptyDer; EOBDBCryptError); end; +//------------------------------------------------------------------------------ +// CONSTRUCT_REJECTS GARBAGE DER +//------------------------------------------------------------------------------ procedure TBCryptVerifierTests.Construct_RejectsGarbageDer; -var Garbage: TBytes; +var + Garbage: TBytes; begin Garbage := TBytes.Create($AA, $BB, $CC, $DD, $EE, $FF); Assert.WillRaise( diff --git a/tests/Tests.ECU.Signature.OpenSSL.pas b/tests/Tests.ECU.Signature.OpenSSL.pas index 962d07fe..35ae7a08 100644 --- a/tests/Tests.ECU.Signature.OpenSSL.pas +++ b/tests/Tests.ECU.Signature.OpenSSL.pas @@ -19,11 +19,29 @@ interface [TestFixture] TOpenSSLVerifierTests = class public + /// + /// Not available construct raises when libcrypto missing. + /// [Test] procedure NotAvailable_ConstructRaises_When_LibcryptoMissing; + /// + /// R s a verifies known good signature. + /// [Test] procedure RSA_VerifiesKnownGoodSignature; + /// + /// R s a rejects tampered firmware. + /// [Test] procedure RSA_RejectsTamperedFirmware; + /// + /// E c d s a verifies known good signature. + /// [Test] procedure ECDSA_VerifiesKnownGoodSignature; + /// + /// E c d s a rejects tampered firmware. + /// [Test] procedure ECDSA_RejectsTamperedFirmware; + /// + /// Construct rejects garbage der. + /// [Test] procedure Construct_RejectsGarbageDer; end; @@ -35,35 +53,53 @@ implementation const {$I 'fixtures\test-fixtures.inc'} +//------------------------------------------------------------------------------ +// PUB KEY RSA +//------------------------------------------------------------------------------ function PubKeyRSA: TBytes; begin SetLength(Result, TEST_RSA_PUB_DER_LEN); Move(TEST_RSA_PUB_DER[0], Result[0], TEST_RSA_PUB_DER_LEN); end; +//------------------------------------------------------------------------------ +// PUB KEY EC +//------------------------------------------------------------------------------ function PubKeyEC: TBytes; begin SetLength(Result, TEST_EC_PUB_DER_LEN); Move(TEST_EC_PUB_DER[0], Result[0], TEST_EC_PUB_DER_LEN); end; +//------------------------------------------------------------------------------ +// SIG RSA +//------------------------------------------------------------------------------ function SigRSA: TBytes; begin SetLength(Result, TEST_RSA_SIG_PKCS1_LEN); Move(TEST_RSA_SIG_PKCS1[0], Result[0], TEST_RSA_SIG_PKCS1_LEN); end; +//------------------------------------------------------------------------------ +// SIG EC +//------------------------------------------------------------------------------ function SigEC: TBytes; begin SetLength(Result, TEST_EC_SIG_LEN); Move(TEST_EC_SIG[0], Result[0], TEST_EC_SIG_LEN); end; +//------------------------------------------------------------------------------ +// MESSAGE BYTES +//------------------------------------------------------------------------------ function MessageBytes: TBytes; begin Result := TEncoding.ASCII.GetBytes(TEST_MESSAGE_TEXT); end; +//------------------------------------------------------------------------------ +// NOT AVAILABLE_CONSTRUCT RAISES_WHEN_LIBCRYPTO MISSING +//------------------------------------------------------------------------------ procedure TOpenSSLVerifierTests.NotAvailable_ConstructRaises_When_LibcryptoMissing; begin // The negative branch only fires on a runner without OpenSSL on the @@ -80,11 +116,18 @@ procedure TOpenSSLVerifierTests.NotAvailable_ConstructRaises_When_LibcryptoMissi Assert.Pass('libcrypto is on PATH — negative branch skipped'); end; +//------------------------------------------------------------------------------ +// RSA_VERIFIES KNOWN GOOD SIGNATURE +//------------------------------------------------------------------------------ procedure TOpenSSLVerifierTests.RSA_VerifiesKnownGoodSignature; -var V: TOBDOpenSSLVerifier; +var + V: TOBDOpenSSLVerifier; begin if not OpenSSLAvailable then - begin Assert.Pass('libcrypto not on PATH'); Exit; end; + begin + Assert.Pass('libcrypto not on PATH'); + Exit; + end; V := TOBDOpenSSLVerifier.Create(PubKeyRSA); try Assert.IsTrue(V.AlgorithmName.Contains('RSA')); @@ -94,13 +137,19 @@ procedure TOpenSSLVerifierTests.RSA_VerifiesKnownGoodSignature; end; end; +//------------------------------------------------------------------------------ +// RSA_REJECTS TAMPERED FIRMWARE +//------------------------------------------------------------------------------ procedure TOpenSSLVerifierTests.RSA_RejectsTamperedFirmware; var V: TOBDOpenSSLVerifier; Tampered: TBytes; begin if not OpenSSLAvailable then - begin Assert.Pass('libcrypto not on PATH'); Exit; end; + begin + Assert.Pass('libcrypto not on PATH'); + Exit; + end; V := TOBDOpenSSLVerifier.Create(PubKeyRSA); try Tampered := TEncoding.ASCII.GetBytes('hello WORLD'); @@ -110,11 +159,18 @@ procedure TOpenSSLVerifierTests.RSA_RejectsTamperedFirmware; end; end; +//------------------------------------------------------------------------------ +// ECDSA_VERIFIES KNOWN GOOD SIGNATURE +//------------------------------------------------------------------------------ procedure TOpenSSLVerifierTests.ECDSA_VerifiesKnownGoodSignature; -var V: TOBDOpenSSLVerifier; +var + V: TOBDOpenSSLVerifier; begin if not OpenSSLAvailable then - begin Assert.Pass('libcrypto not on PATH'); Exit; end; + begin + Assert.Pass('libcrypto not on PATH'); + Exit; + end; V := TOBDOpenSSLVerifier.Create(PubKeyEC); try Assert.IsTrue(V.AlgorithmName.Contains('ECDSA')); @@ -124,13 +180,19 @@ procedure TOpenSSLVerifierTests.ECDSA_VerifiesKnownGoodSignature; end; end; +//------------------------------------------------------------------------------ +// ECDSA_REJECTS TAMPERED FIRMWARE +//------------------------------------------------------------------------------ procedure TOpenSSLVerifierTests.ECDSA_RejectsTamperedFirmware; var V: TOBDOpenSSLVerifier; Tampered: TBytes; begin if not OpenSSLAvailable then - begin Assert.Pass('libcrypto not on PATH'); Exit; end; + begin + Assert.Pass('libcrypto not on PATH'); + Exit; + end; V := TOBDOpenSSLVerifier.Create(PubKeyEC); try Tampered := TEncoding.ASCII.GetBytes('hello WORLD'); @@ -140,11 +202,18 @@ procedure TOpenSSLVerifierTests.ECDSA_RejectsTamperedFirmware; end; end; +//------------------------------------------------------------------------------ +// CONSTRUCT_REJECTS GARBAGE DER +//------------------------------------------------------------------------------ procedure TOpenSSLVerifierTests.Construct_RejectsGarbageDer; -var Garbage: TBytes; +var + Garbage: TBytes; begin if not OpenSSLAvailable then - begin Assert.Pass('libcrypto not on PATH'); Exit; end; + begin + Assert.Pass('libcrypto not on PATH'); + Exit; + end; Garbage := TBytes.Create($AA, $BB, $CC, $DD, $EE); Assert.WillRaise( procedure begin TOBDOpenSSLVerifier.Create(Garbage).Free; end, diff --git a/tests/Tests.ECU.Signature.PQC.pas b/tests/Tests.ECU.Signature.PQC.pas new file mode 100644 index 00000000..d086e4d7 --- /dev/null +++ b/tests/Tests.ECU.Signature.PQC.pas @@ -0,0 +1,226 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.ECU.Signature.PQC.pas +// CONTENTS : Tests for OBD.ECU.Signature.PQC +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.ECU.Signature.PQC; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TPQCSignatureTests = class + public + /// + /// Envelope round trips. + /// + [Test] procedure EnvelopeRoundTrips; + /// + /// Envelope with empty key id round trips. + /// + [Test] procedure EnvelopeWithEmptyKeyIdRoundTrips; + /// + /// Envelope truncated at sig len raises. + /// + [Test] procedure EnvelopeTruncatedAtSigLenRaises; + /// + /// Envelope truncated at signature raises. + /// + [Test] procedure EnvelopeTruncatedAtSignatureRaises; + /// + /// Envelope too short raises. + /// + [Test] procedure EnvelopeTooShortRaises; + /// + /// Verify algorithm mismatch raises. + /// + [Test] procedure VerifyAlgorithmMismatchRaises; + /// + /// Verify raises not available until binding ships. + /// + [Test] procedure VerifyRaisesNotAvailableUntilBindingShips; + /// + /// Constructor rejects unknown algorithm. + /// + [Test] procedure ConstructorRejectsUnknownAlgorithm; + /// + /// Constructor rejects empty public key. + /// + [Test] procedure ConstructorRejectsEmptyPublicKey; + /// + /// Algorithm name matches enum. + /// + [Test] procedure AlgorithmNameMatchesEnum; + end; + +implementation + +uses + System.SysUtils, OBD.ECU.Signature, OBD.ECU.Signature.PQC; + +//------------------------------------------------------------------------------ +// ENVELOPE ROUND TRIPS +//------------------------------------------------------------------------------ +procedure TPQCSignatureTests.EnvelopeRoundTrips; +var + Env, Out_: TOBDPQCEnvelope; + Bytes: TBytes; +begin + Env := Default(TOBDPQCEnvelope); + Env.Algorithm := pqcMlDsa65; + Env.KeyId := TBytes.Create($AA, $BB, $CC, $DD); + Env.Signature := TBytes.Create($01, $02, $03, $04, $05); + Bytes := EncodePQCEnvelope(Env); + Out_ := DecodePQCEnvelope(Bytes); + Assert.AreEqual(Ord(pqcMlDsa65), Ord(Out_.Algorithm)); + Assert.AreEqual(4, Length(Out_.KeyId)); + Assert.AreEqual(5, Length(Out_.Signature)); + Assert.AreEqual($AA, Integer(Out_.KeyId[0])); + Assert.AreEqual($05, Integer(Out_.Signature[4])); +end; + +//------------------------------------------------------------------------------ +// ENVELOPE WITH EMPTY KEY ID ROUND TRIPS +//------------------------------------------------------------------------------ +procedure TPQCSignatureTests.EnvelopeWithEmptyKeyIdRoundTrips; +var + Env, Out_: TOBDPQCEnvelope; + Bytes: TBytes; +begin + Env := Default(TOBDPQCEnvelope); + Env.Algorithm := pqcSlhDsaSha2128s; + Env.Signature := TBytes.Create($FF); + Bytes := EncodePQCEnvelope(Env); + Out_ := DecodePQCEnvelope(Bytes); + Assert.AreEqual(0, Length(Out_.KeyId)); + Assert.AreEqual(1, Length(Out_.Signature)); +end; + +//------------------------------------------------------------------------------ +// ENVELOPE TRUNCATED AT SIG LEN RAISES +//------------------------------------------------------------------------------ +procedure TPQCSignatureTests.EnvelopeTruncatedAtSigLenRaises; +var + Bytes: TBytes; +begin + Bytes := TBytes.Create($02, $00, $00, $00, $00); // missing one sig-len byte + Assert.WillRaise( + procedure begin DecodePQCEnvelope(Bytes); end, + EOBDPQCSignature); +end; + +//------------------------------------------------------------------------------ +// ENVELOPE TRUNCATED AT SIGNATURE RAISES +//------------------------------------------------------------------------------ +procedure TPQCSignatureTests.EnvelopeTruncatedAtSignatureRaises; +var + Bytes: TBytes; +begin + // alg=2, keylen=0, siglen=4, but only 2 sig bytes follow + Bytes := TBytes.Create($02, $00, $00, $00, $00, $04, $AA, $BB); + Assert.WillRaise( + procedure begin DecodePQCEnvelope(Bytes); end, + EOBDPQCSignature); +end; + +//------------------------------------------------------------------------------ +// ENVELOPE TOO SHORT RAISES +//------------------------------------------------------------------------------ +procedure TPQCSignatureTests.EnvelopeTooShortRaises; +begin + Assert.WillRaise( + procedure begin DecodePQCEnvelope(TBytes.Create($00, $00, $00)); end, + EOBDPQCSignature); +end; + +//------------------------------------------------------------------------------ +// VERIFY ALGORITHM MISMATCH RAISES +//------------------------------------------------------------------------------ +procedure TPQCSignatureTests.VerifyAlgorithmMismatchRaises; +var + V: IFirmwareSignatureVerifier; + Env: TOBDPQCEnvelope; + EnvBytes: TBytes; +begin + V := TOBDPQCSignatureVerifier.Create(pqcMlDsa65, TBytes.Create($01, $02)); + Env := Default(TOBDPQCEnvelope); + Env.Algorithm := pqcSlhDsaSha2128s; + Env.Signature := TBytes.Create($AA); + EnvBytes := EncodePQCEnvelope(Env); + Assert.WillRaise( + procedure begin V.Verify(TBytes.Create($00), EnvBytes); end, + EOBDPQCSignature); +end; + +//------------------------------------------------------------------------------ +// VERIFY RAISES NOT AVAILABLE UNTIL BINDING SHIPS +//------------------------------------------------------------------------------ +procedure TPQCSignatureTests.VerifyRaisesNotAvailableUntilBindingShips; +var + V: IFirmwareSignatureVerifier; + Env: TOBDPQCEnvelope; + EnvBytes: TBytes; +begin + V := TOBDPQCSignatureVerifier.Create(pqcMlDsa65, TBytes.Create($01, $02)); + Env := Default(TOBDPQCEnvelope); + Env.Algorithm := pqcMlDsa65; + Env.Signature := TBytes.Create($AA, $BB); + EnvBytes := EncodePQCEnvelope(Env); + Assert.WillRaise( + procedure begin V.Verify(TBytes.Create($00), EnvBytes); end, + EOBDPQCNotAvailable); +end; + +//------------------------------------------------------------------------------ +// CONSTRUCTOR REJECTS UNKNOWN ALGORITHM +//------------------------------------------------------------------------------ +procedure TPQCSignatureTests.ConstructorRejectsUnknownAlgorithm; +begin + Assert.WillRaise( + procedure + var + V: IFirmwareSignatureVerifier; + begin + V := TOBDPQCSignatureVerifier.Create(pqcUnknown, TBytes.Create($01)); + end, + EOBDPQCSignature); +end; + +//------------------------------------------------------------------------------ +// CONSTRUCTOR REJECTS EMPTY PUBLIC KEY +//------------------------------------------------------------------------------ +procedure TPQCSignatureTests.ConstructorRejectsEmptyPublicKey; +begin + Assert.WillRaise( + procedure + var + V: IFirmwareSignatureVerifier; + begin + V := TOBDPQCSignatureVerifier.Create(pqcMlDsa65, nil); + end, + EOBDPQCSignature); +end; + +//------------------------------------------------------------------------------ +// ALGORITHM NAME MATCHES ENUM +//------------------------------------------------------------------------------ +procedure TPQCSignatureTests.AlgorithmNameMatchesEnum; +begin + Assert.AreEqual('ML-DSA-65', PQCAlgorithmName(pqcMlDsa65)); + Assert.AreEqual('SLH-DSA-SHA2-128s', PQCAlgorithmName(pqcSlhDsaSha2128s)); + Assert.AreEqual('PQC-UNKNOWN', PQCAlgorithmName(pqcUnknown)); +end; + +initialization + TDUnitX.RegisterTestFixture(TPQCSignatureTests); + +end. diff --git a/tests/Tests.ECU.Signature.pas b/tests/Tests.ECU.Signature.pas index c3820f80..2f09b8dc 100644 --- a/tests/Tests.ECU.Signature.pas +++ b/tests/Tests.ECU.Signature.pas @@ -13,10 +13,25 @@ interface [TestFixture] TSignatureTests = class public + /// + /// Sha256 accepts known gold hash. + /// [Test] procedure Sha256_AcceptsKnownGoldHash; + /// + /// Sha256 rejects tampered firmware. + /// [Test] procedure Sha256_RejectsTamperedFirmware; + /// + /// Sha256 length mismatch rejected. + /// [Test] procedure Sha256_LengthMismatchRejected; + /// + /// Permissive accepts anything. + /// [Test] procedure Permissive_AcceptsAnything; + /// + /// Compute sha256 empty has known value. + /// [Test] procedure ComputeSha256_EmptyHasKnownValue; end; @@ -25,14 +40,21 @@ implementation uses System.SysUtils, OBD.ECU.Signature; +//------------------------------------------------------------------------------ +// HEX TO BYTES +//------------------------------------------------------------------------------ function HexToBytes(const Hex: string): TBytes; -var I: Integer; +var + I: Integer; begin SetLength(Result, Length(Hex) div 2); for I := 0 to High(Result) do Result[I] := StrToInt('$' + Copy(Hex, I * 2 + 1, 2)); end; +//------------------------------------------------------------------------------ +// COMPUTE SHA256_EMPTY HAS KNOWN VALUE +//------------------------------------------------------------------------------ procedure TSignatureTests.ComputeSha256_EmptyHasKnownValue; var Empty: TBytes; @@ -52,6 +74,9 @@ procedure TSignatureTests.ComputeSha256_EmptyHasKnownValue; Hex); end; +//------------------------------------------------------------------------------ +// SHA256_ACCEPTS KNOWN GOLD HASH +//------------------------------------------------------------------------------ procedure TSignatureTests.Sha256_AcceptsKnownGoldHash; var Verifier: IFirmwareSignatureVerifier; @@ -66,6 +91,9 @@ procedure TSignatureTests.Sha256_AcceptsKnownGoldHash; Assert.IsTrue(Verifier.Verify(Firmware, Signature)); end; +//------------------------------------------------------------------------------ +// SHA256_REJECTS TAMPERED FIRMWARE +//------------------------------------------------------------------------------ procedure TSignatureTests.Sha256_RejectsTamperedFirmware; var Verifier: IFirmwareSignatureVerifier; @@ -78,6 +106,9 @@ procedure TSignatureTests.Sha256_RejectsTamperedFirmware; Assert.IsFalse(Verifier.Verify(Firmware, Signature)); end; +//------------------------------------------------------------------------------ +// SHA256_LENGTH MISMATCH REJECTED +//------------------------------------------------------------------------------ procedure TSignatureTests.Sha256_LengthMismatchRejected; var Verifier: IFirmwareSignatureVerifier; @@ -89,6 +120,9 @@ procedure TSignatureTests.Sha256_LengthMismatchRejected; Assert.IsFalse(Verifier.Verify(Firmware, Short)); end; +//------------------------------------------------------------------------------ +// PERMISSIVE_ACCEPTS ANYTHING +//------------------------------------------------------------------------------ procedure TSignatureTests.Permissive_AcceptsAnything; var Verifier: IFirmwareSignatureVerifier; diff --git a/tests/Tests.EV.BatteryHealth.pas b/tests/Tests.EV.BatteryHealth.pas new file mode 100644 index 00000000..88741262 --- /dev/null +++ b/tests/Tests.EV.BatteryHealth.pas @@ -0,0 +1,231 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.EV.BatteryHealth.pas +// CONTENTS : Tests for OBD.EV.BatteryHealth +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.EV.BatteryHealth; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TBatteryHealthTests = class + public + /// + /// Imbalance flat pack has zero spread. + /// + [Test] procedure ImbalanceFlatPackHasZeroSpread; + /// + /// Imbalance spread and std dev. + /// + [Test] procedure ImbalanceSpreadAndStdDev; + /// + /// Imbalance outlier beyond three sigma. + /// + [Test] procedure ImbalanceOutlierBeyondThreeSigma; + /// + /// Imbalance empty array raises. + /// + [Test] procedure ImbalanceEmptyArrayRaises; + /// + /// So h at rated capacity is one. + /// + [Test] procedure SoHAtRatedCapacityIsOne; + /// + /// So h at half capacity is half. + /// + [Test] procedure SoHAtHalfCapacityIsHalf; + /// + /// So h rated zero raises. + /// + [Test] procedure SoHRatedZeroRaises; + /// + /// So h temperature derating composite. + /// + [Test] procedure SoHTemperatureDeratingComposite; + /// + /// Charging session round trips. + /// + [Test] procedure ChargingSessionRoundTrips; + /// + /// Charging session end before start raises. + /// + [Test] procedure ChargingSessionEndBeforeStartRaises; + /// + /// Charging session out of range so c raises. + /// + [Test] procedure ChargingSessionOutOfRangeSoCRaises; + end; + +implementation + +uses + System.SysUtils, System.Math, OBD.EV.BatteryHealth; + +//------------------------------------------------------------------------------ +// IMBALANCE FLAT PACK HAS ZERO SPREAD +//------------------------------------------------------------------------------ +procedure TBatteryHealthTests.ImbalanceFlatPackHasZeroSpread; +var + R: TOBDCellImbalance; +begin + R := ComputeCellImbalance([3.7, 3.7, 3.7, 3.7]); + Assert.AreEqual(Single(0.0), R.SpreadVolts, 0.0001); + Assert.AreEqual(Single(0.0), R.StdDev, 0.0001); + Assert.AreEqual(-1, R.OutlierIndex); +end; + +//------------------------------------------------------------------------------ +// IMBALANCE SPREAD AND STD DEV +//------------------------------------------------------------------------------ +procedure TBatteryHealthTests.ImbalanceSpreadAndStdDev; +var + R: TOBDCellImbalance; +begin + R := ComputeCellImbalance([3.6, 3.7, 3.8, 3.7]); + Assert.AreEqual(Single(3.6), R.MinVoltage, 0.0001); + Assert.AreEqual(Single(3.8), R.MaxVoltage, 0.0001); + Assert.AreEqual(Single(0.2), R.SpreadVolts, 0.0001); + Assert.AreEqual(Single(3.7), R.MeanVoltage, 0.0001); + Assert.IsTrue(R.StdDev > 0); +end; + +//------------------------------------------------------------------------------ +// IMBALANCE OUTLIER BEYOND THREE SIGMA +//------------------------------------------------------------------------------ +procedure TBatteryHealthTests.ImbalanceOutlierBeyondThreeSigma; +var + V: array of Single; + R: TOBDCellImbalance; + I: Integer; +begin + // 99 cells around 3.700 + one cell at 4.50 -> definitely > 3 sigma. + SetLength(V, 100); + for I := 0 to 98 do V[I] := 3.700 + (Random - 0.5) * 0.001; + V[42] := 4.500; + R := ComputeCellImbalance(V); + Assert.AreEqual(42, R.OutlierIndex); + Assert.IsTrue(R.OutlierDeltaSigma > 3.0); +end; + +//------------------------------------------------------------------------------ +// IMBALANCE EMPTY ARRAY RAISES +//------------------------------------------------------------------------------ +procedure TBatteryHealthTests.ImbalanceEmptyArrayRaises; +var + V: array of Single; +begin + SetLength(V, 0); + Assert.WillRaise( + procedure begin ComputeCellImbalance(V); end, + EOBDBatteryHealth); +end; + +//------------------------------------------------------------------------------ +// SO HAT RATED CAPACITY IS ONE +//------------------------------------------------------------------------------ +procedure TBatteryHealthTests.SoHAtRatedCapacityIsOne; +var + R: TOBDBatterySoH; +begin + R := ComputeBatterySoH(77.0, 77.0); + Assert.AreEqual(Single(1.0), R.SoHFromCapacity, 0.0001); + Assert.AreEqual(Single(1.0), R.CompositeSoH, 0.0001); +end; + +//------------------------------------------------------------------------------ +// SO HAT HALF CAPACITY IS HALF +//------------------------------------------------------------------------------ +procedure TBatteryHealthTests.SoHAtHalfCapacityIsHalf; +var + R: TOBDBatterySoH; +begin + R := ComputeBatterySoH(100.0, 50.0); + Assert.AreEqual(Single(0.5), R.SoHFromCapacity, 0.0001); +end; + +//------------------------------------------------------------------------------ +// SO HRATED ZERO RAISES +//------------------------------------------------------------------------------ +procedure TBatteryHealthTests.SoHRatedZeroRaises; +begin + Assert.WillRaise( + procedure begin ComputeBatterySoH(0, 50); end, + EOBDBatteryHealth); +end; + +//------------------------------------------------------------------------------ +// SO HTEMPERATURE DERATING COMPOSITE +//------------------------------------------------------------------------------ +procedure TBatteryHealthTests.SoHTemperatureDeratingComposite; +var + R: TOBDBatterySoH; +begin + R := ComputeBatterySoH(100, 80, 250, 0.9); + Assert.AreEqual(Single(0.8), R.SoHFromCapacity, 0.0001); + Assert.AreEqual(Single(0.72), R.CompositeSoH, 0.0001); + Assert.AreEqual(250, R.EquivalentFullCycles); +end; + +//------------------------------------------------------------------------------ +// CHARGING SESSION ROUND TRIPS +//------------------------------------------------------------------------------ +procedure TBatteryHealthTests.ChargingSessionRoundTrips; +var + Raw, Out_: TOBDChargingSession; +begin + Raw := Default(TOBDChargingSession); + Raw.StartSoCPercent := 20; + Raw.EndSoCPercent := 80; + Raw.EnergyDeliveredKwh := 45; + Raw.PeakPowerKw := 150; + Raw.AverageBatteryTempC := 28; + Raw.DurationSeconds := 1800; + Raw.SessionType := 'DC'; + Out_ := NormaliseChargingSession(Raw); + Assert.AreEqual(Single(45.0), Out_.EnergyDeliveredKwh, 0.0001); + Assert.AreEqual('DC', Out_.SessionType); +end; + +//------------------------------------------------------------------------------ +// CHARGING SESSION END BEFORE START RAISES +//------------------------------------------------------------------------------ +procedure TBatteryHealthTests.ChargingSessionEndBeforeStartRaises; +var + Raw: TOBDChargingSession; +begin + Raw := Default(TOBDChargingSession); + Raw.StartSoCPercent := 80; + Raw.EndSoCPercent := 60; + Assert.WillRaise( + procedure begin NormaliseChargingSession(Raw); end, + EOBDBatteryHealth); +end; + +//------------------------------------------------------------------------------ +// CHARGING SESSION OUT OF RANGE SO CRAISES +//------------------------------------------------------------------------------ +procedure TBatteryHealthTests.ChargingSessionOutOfRangeSoCRaises; +var + Raw: TOBDChargingSession; +begin + Raw := Default(TOBDChargingSession); + Raw.StartSoCPercent := -1; + Assert.WillRaise( + procedure begin NormaliseChargingSession(Raw); end, + EOBDBatteryHealth); +end; + +initialization + TDUnitX.RegisterTestFixture(TBatteryHealthTests); + +end. diff --git a/tests/Tests.J1939.PGNs.pas b/tests/Tests.J1939.PGNs.pas new file mode 100644 index 00000000..ae7a748b --- /dev/null +++ b/tests/Tests.J1939.PGNs.pas @@ -0,0 +1,226 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.J1939.PGNs.pas +// CONTENTS : Tests for OBD.J1939.PGNs +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.J1939.PGNs; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TJ1939PGNsTests = class + public + /// + /// Seed has at least forty entries. + /// + [Test] procedure SeedHasAtLeastFortyEntries; + /// + /// No duplicate p g n ids. + /// + [Test] procedure NoDuplicatePGNIds; + /// + /// Every entry has mnemonic and name. + /// + [Test] procedure EveryEntryHasMnemonicAndName; + /// + /// Every entry has spec citation. + /// + [Test] procedure EveryEntryHasSpecCitation; + /// + /// Find d m1 returns correct mnemonic. + /// + [Test] procedure FindDM1ReturnsCorrectMnemonic; + /// + /// Find e e c1 has priority three. + /// + [Test] procedure FindEEC1HasPriorityThree; + /// + /// Find unknown p g n returns zero record. + /// + [Test] procedure FindUnknownPGNReturnsZeroRecord; + /// + /// Register replaces existing. + /// + [Test] procedure RegisterReplacesExisting; + /// + /// Register adds new entry. + /// + [Test] procedure RegisterAddsNewEntry; + /// + /// All returns sorted ascending. + /// + [Test] procedure AllReturnsSortedAscending; + /// + /// Address claim and transport protocol distinct. + /// + [Test] procedure AddressClaimAndTransportProtocolDistinct; + end; + +implementation + +uses + System.SysUtils, OBD.J1939.PGNs; + +//------------------------------------------------------------------------------ +// SEED HAS AT LEAST FORTY ENTRIES +//------------------------------------------------------------------------------ +procedure TJ1939PGNsTests.SeedHasAtLeastFortyEntries; +begin + Assert.IsTrue(J1939PGNCount >= 40, + 'Expected >= 40 PGN entries, got ' + IntToStr(J1939PGNCount)); +end; + +//------------------------------------------------------------------------------ +// NO DUPLICATE PGNIDS +//------------------------------------------------------------------------------ +procedure TJ1939PGNsTests.NoDuplicatePGNIds; +var + All: TArray; + I: Integer; +begin + All := J1939PGNAll; + for I := 1 to High(All) do + Assert.AreNotEqual(All[I - 1].PGN, All[I].PGN, + Format('Duplicate PGN 0x%.4X between %s and %s', + [All[I].PGN, All[I - 1].Mnemonic, All[I].Mnemonic])); +end; + +//------------------------------------------------------------------------------ +// EVERY ENTRY HAS MNEMONIC AND NAME +//------------------------------------------------------------------------------ +procedure TJ1939PGNsTests.EveryEntryHasMnemonicAndName; +var + D: TJ1939PGNDescriptor; +begin + for D in J1939PGNAll do + begin + Assert.IsNotEmpty(D.Mnemonic, Format('PGN 0x%.4X has empty mnemonic', [D.PGN])); + Assert.IsNotEmpty(D.Name, Format('PGN 0x%.4X has empty name', [D.PGN])); + end; +end; + +//------------------------------------------------------------------------------ +// EVERY ENTRY HAS SPEC CITATION +//------------------------------------------------------------------------------ +procedure TJ1939PGNsTests.EveryEntryHasSpecCitation; +var + D: TJ1939PGNDescriptor; +begin + for D in J1939PGNAll do + Assert.IsNotEmpty(D.SpecSection, + Format('PGN 0x%.4X (%s) missing spec section', [D.PGN, D.Mnemonic])); +end; + +//------------------------------------------------------------------------------ +// FIND DM1 RETURNS CORRECT MNEMONIC +//------------------------------------------------------------------------------ +procedure TJ1939PGNsTests.FindDM1ReturnsCorrectMnemonic; +var + D: TJ1939PGNDescriptor; +begin + D := FindPGN($FECA); + Assert.AreEqual('DM1', D.Mnemonic); + Assert.IsTrue(D.Name.Contains('Active')); +end; + +//------------------------------------------------------------------------------ +// FIND EEC1 HAS PRIORITY THREE +//------------------------------------------------------------------------------ +procedure TJ1939PGNsTests.FindEEC1HasPriorityThree; +var + D: TJ1939PGNDescriptor; +begin + D := FindPGN($F004); + Assert.AreEqual('EEC1', D.Mnemonic); + Assert.AreEqual(3, Integer(D.DefaultPriority)); +end; + +//------------------------------------------------------------------------------ +// FIND UNKNOWN PGNRETURNS ZERO RECORD +//------------------------------------------------------------------------------ +procedure TJ1939PGNsTests.FindUnknownPGNReturnsZeroRecord; +var + D: TJ1939PGNDescriptor; +begin + D := FindPGN($1234); + Assert.AreEqual(UInt32(0), D.PGN); +end; + +//------------------------------------------------------------------------------ +// REGISTER REPLACES EXISTING +//------------------------------------------------------------------------------ +procedure TJ1939PGNsTests.RegisterReplacesExisting; +var + Custom, Round: TJ1939PGNDescriptor; +begin + Custom := FindPGN($FECA); // DM1 + Custom.Mnemonic := 'CUSTOM-DM1'; + RegisterJ1939PGN(Custom); + Round := FindPGN($FECA); + Assert.AreEqual('CUSTOM-DM1', Round.Mnemonic); + // Restore so other tests don't see the mutation: + Custom.Mnemonic := 'DM1'; + RegisterJ1939PGN(Custom); +end; + +//------------------------------------------------------------------------------ +// REGISTER ADDS NEW ENTRY +//------------------------------------------------------------------------------ +procedure TJ1939PGNsTests.RegisterAddsNewEntry; +var + Before, After: Integer; + D: TJ1939PGNDescriptor; +begin + Before := J1939PGNCount; + D.PGN := $9999; + D.Mnemonic := 'TEST'; + D.Name := 'Test PGN for unit test'; + D.LengthBytes := 8; + D.DefaultPriority := 6; + D.TxRateMs := 1000; + D.SpecSection := 'test only'; + RegisterJ1939PGN(D); + After := J1939PGNCount; + Assert.AreEqual(Before + 1, After); + Assert.AreEqual('TEST', FindPGN($9999).Mnemonic); +end; + +//------------------------------------------------------------------------------ +// ALL RETURNS SORTED ASCENDING +//------------------------------------------------------------------------------ +procedure TJ1939PGNsTests.AllReturnsSortedAscending; +var + All: TArray; + I: Integer; +begin + All := J1939PGNAll; + for I := 1 to High(All) do + Assert.IsTrue(All[I - 1].PGN < All[I].PGN, + 'PGN list must be ascending'); +end; + +//------------------------------------------------------------------------------ +// ADDRESS CLAIM AND TRANSPORT PROTOCOL DISTINCT +//------------------------------------------------------------------------------ +procedure TJ1939PGNsTests.AddressClaimAndTransportProtocolDistinct; +begin + Assert.AreEqual(UInt32($EE00), FindPGN($EE00).PGN); + Assert.AreEqual('AC', FindPGN($EE00).Mnemonic); + Assert.AreEqual('TP.CM', FindPGN($EC00).Mnemonic); + Assert.AreEqual('TP.DT', FindPGN($EB00).Mnemonic); +end; + +initialization + TDUnitX.RegisterTestFixture(TJ1939PGNsTests); + +end. diff --git a/tests/Tests.Logger.Sinks.pas b/tests/Tests.Logger.Sinks.pas index d92b6dfb..a33a1153 100644 --- a/tests/Tests.Logger.Sinks.pas +++ b/tests/Tests.Logger.Sinks.pas @@ -28,6 +28,9 @@ implementation System.DateUtils, OBD.Logger.Sinks; +//------------------------------------------------------------------------------ +// MAKE EVENT +//------------------------------------------------------------------------------ function MakeEvent(L: TOBDLogLevel; const Msg: string; const Source: string = ''): TOBDLogEvent; begin @@ -37,6 +40,9 @@ function MakeEvent(L: TOBDLogLevel; const Msg: string; Result.Message := Msg; end; +//------------------------------------------------------------------------------ +// SCRATCH PATH +//------------------------------------------------------------------------------ function ScratchPath(const Name: string): string; begin Result := TPath.Combine(TPath.GetTempPath, @@ -45,6 +51,9 @@ function ScratchPath(const Name: string): string; { TLoggerSinksTests } +//------------------------------------------------------------------------------ +// IN MEMORY SINK_ACCEPTS EVENTS AND CAPS AT CAPACITY +//------------------------------------------------------------------------------ procedure TLoggerSinksTests.InMemorySink_AcceptsEventsAndCapsAtCapacity; var Sink: TInMemorySink; @@ -64,6 +73,9 @@ procedure TLoggerSinksTests.InMemorySink_AcceptsEventsAndCapsAtCapacity; end; end; +//------------------------------------------------------------------------------ +// IN MEMORY SINK_ON EVENT CALLBACK FIRES +//------------------------------------------------------------------------------ procedure TLoggerSinksTests.InMemorySink_OnEventCallbackFires; var Sink: TInMemorySink; @@ -81,6 +93,9 @@ procedure TLoggerSinksTests.InMemorySink_OnEventCallbackFires; end; end; +//------------------------------------------------------------------------------ +// JSON LINE SINK_WRITES VALID JSON +//------------------------------------------------------------------------------ procedure TLoggerSinksTests.JsonLineSink_WritesValidJson; var Path: string; @@ -115,6 +130,9 @@ procedure TLoggerSinksTests.JsonLineSink_WritesValidJson; end; end; +//------------------------------------------------------------------------------ +// FILE ROTATION SINK_ROTATES WHEN SIZE EXCEEDED +//------------------------------------------------------------------------------ procedure TLoggerSinksTests.FileRotationSink_RotatesWhenSizeExceeded; var Path, Backup1: string; @@ -142,6 +160,9 @@ procedure TLoggerSinksTests.FileRotationSink_RotatesWhenSizeExceeded; TFile.Delete(Backup1); end; +//------------------------------------------------------------------------------ +// DAILY ROTATION SINK_NAMES FILE WITH DATE +//------------------------------------------------------------------------------ procedure TLoggerSinksTests.DailyRotationSink_NamesFileWithDate; var Dir, Expected: string; @@ -165,6 +186,9 @@ procedure TLoggerSinksTests.DailyRotationSink_NamesFileWithDate; TFile.Delete(Expected); end; +//------------------------------------------------------------------------------ +// LOG LEVEL NAME_RETURNS EXPECTED STRINGS +//------------------------------------------------------------------------------ procedure TLoggerSinksTests.LogLevelName_ReturnsExpectedStrings; begin Assert.AreEqual('DEBUG', LogLevelName(lsDebug)); diff --git a/tests/Tests.OBD.Helpers.pas b/tests/Tests.OBD.Helpers.pas index c190589c..8ec65b6d 100644 --- a/tests/Tests.OBD.Helpers.pas +++ b/tests/Tests.OBD.Helpers.pas @@ -52,6 +52,10 @@ implementation //============================================================================== // Readiness monitor //============================================================================== + +//------------------------------------------------------------------------------ +// DECODES ALL ZEROS CLEANLY +//------------------------------------------------------------------------------ procedure TReadinessMonitorTests.DecodesAllZerosCleanly; var Report: TOBDReadinessReport; @@ -64,6 +68,9 @@ procedure TReadinessMonitorTests.DecodesAllZerosCleanly; Assert.AreEqual(11, Length(Report.Monitors)); end; +//------------------------------------------------------------------------------ +// DECODES MILON AND DTC COUNT +//------------------------------------------------------------------------------ procedure TReadinessMonitorTests.DecodesMILOnAndDtcCount; var Report: TOBDReadinessReport; @@ -74,6 +81,9 @@ procedure TReadinessMonitorTests.DecodesMILOnAndDtcCount; Assert.AreEqual(Byte(5), Report.DtcCount); end; +//------------------------------------------------------------------------------ +// DECODES CONTINUOUS MONITOR READY +//------------------------------------------------------------------------------ procedure TReadinessMonitorTests.DecodesContinuousMonitorReady; var Report: TOBDReadinessReport; @@ -90,6 +100,9 @@ procedure TReadinessMonitorTests.DecodesContinuousMonitorReady; Assert.Fail('misfire monitor missing from report'); end; +//------------------------------------------------------------------------------ +// DECODES CONTINUOUS MONITOR NOT READY +//------------------------------------------------------------------------------ procedure TReadinessMonitorTests.DecodesContinuousMonitorNotReady; var Report: TOBDReadinessReport; @@ -105,6 +118,9 @@ procedure TReadinessMonitorTests.DecodesContinuousMonitorNotReady; end; end; +//------------------------------------------------------------------------------ +// DECODES GASOLINE NON CONTINUOUS READY +//------------------------------------------------------------------------------ procedure TReadinessMonitorTests.DecodesGasolineNonContinuousReady; var Report: TOBDReadinessReport; @@ -124,6 +140,9 @@ procedure TReadinessMonitorTests.DecodesGasolineNonContinuousReady; Assert.IsTrue(HasCatalyst, 'catalyst monitor should appear for SI engines'); end; +//------------------------------------------------------------------------------ +// DECODES DIESEL FLAG AND MONITORS +//------------------------------------------------------------------------------ procedure TReadinessMonitorTests.DecodesDieselFlagAndMonitors; var Report: TOBDReadinessReport; @@ -147,6 +166,9 @@ procedure TReadinessMonitorTests.DecodesDieselFlagAndMonitors; 'diesel report must NOT include the SI catalyst monitor'); end; +//------------------------------------------------------------------------------ +// REJECTS TOO SHORT +//------------------------------------------------------------------------------ procedure TReadinessMonitorTests.RejectsTooShort; begin Assert.WillRaise( @@ -154,6 +176,9 @@ procedure TReadinessMonitorTests.RejectsTooShort; EOBDReadinessError); end; +//------------------------------------------------------------------------------ +// SUMMARY FORMATS CORRECTLY +//------------------------------------------------------------------------------ procedure TReadinessMonitorTests.SummaryFormatsCorrectly; var Report: TOBDReadinessReport; @@ -167,6 +192,9 @@ procedure TReadinessMonitorTests.SummaryFormatsCorrectly; Assert.IsTrue(Pos('spark-ignition', Summary) > 0); end; +//------------------------------------------------------------------------------ +// MONITOR KIND NAMES ARE CANONICAL +//------------------------------------------------------------------------------ procedure TReadinessMonitorTests.MonitorKindNamesAreCanonical; begin Assert.AreEqual('misfire', MonitorKindName(monMisfire)); @@ -175,6 +203,9 @@ procedure TReadinessMonitorTests.MonitorKindNamesAreCanonical; Assert.AreEqual('pm_filter', MonitorKindName(monPMFilter)); end; +//------------------------------------------------------------------------------ +// MONITOR STATE NAMES ARE CANONICAL +//------------------------------------------------------------------------------ procedure TReadinessMonitorTests.MonitorStateNamesAreCanonical; begin Assert.AreEqual('not_supported', MonitorStateName(msNotSupported)); @@ -185,6 +216,10 @@ procedure TReadinessMonitorTests.MonitorStateNamesAreCanonical; //============================================================================== // Freeze frame //============================================================================== + +//------------------------------------------------------------------------------ +// BUILD REQUEST ENCODES PID AND FRAME +//------------------------------------------------------------------------------ procedure TFreezeFrameTests.BuildRequestEncodesPidAndFrame; var Req: TBytes; @@ -196,6 +231,9 @@ procedure TFreezeFrameTests.BuildRequestEncodesPidAndFrame; Assert.AreEqual(Byte($00), Req[2]); end; +//------------------------------------------------------------------------------ +// PARSE POSITIVE RESPONSE +//------------------------------------------------------------------------------ procedure TFreezeFrameTests.ParsePositiveResponse; var Entry: TOBDFreezeFrameEntry; @@ -209,6 +247,9 @@ procedure TFreezeFrameTests.ParsePositiveResponse; Assert.AreEqual(Byte($F8), Entry.Payload[1]); end; +//------------------------------------------------------------------------------ +// PARSE REJECTS TOO SHORT +//------------------------------------------------------------------------------ procedure TFreezeFrameTests.ParseRejectsTooShort; begin Assert.WillRaise( @@ -218,6 +259,9 @@ procedure TFreezeFrameTests.ParseRejectsTooShort; EOBDFreezeFrameError); end; +//------------------------------------------------------------------------------ +// PARSE REJECTS WRONG SID +//------------------------------------------------------------------------------ procedure TFreezeFrameTests.ParseRejectsWrongSID; begin Assert.WillRaise( @@ -227,6 +271,9 @@ procedure TFreezeFrameTests.ParseRejectsWrongSID; EOBDFreezeFrameError); end; +//------------------------------------------------------------------------------ +// PARSE REJECTS WRONG PID +//------------------------------------------------------------------------------ procedure TFreezeFrameTests.ParseRejectsWrongPID; begin Assert.WillRaise( @@ -236,6 +283,9 @@ procedure TFreezeFrameTests.ParseRejectsWrongPID; EOBDFreezeFrameError); end; +//------------------------------------------------------------------------------ +// PARSE HANDLES NEGATIVE NRC +//------------------------------------------------------------------------------ procedure TFreezeFrameTests.ParseHandlesNegativeNRC; begin Assert.WillRaise( @@ -245,6 +295,9 @@ procedure TFreezeFrameTests.ParseHandlesNegativeNRC; EOBDFreezeFrameError); end; +//------------------------------------------------------------------------------ +// FORMAT TRIGGER DTC ROUND TRIPS +//------------------------------------------------------------------------------ procedure TFreezeFrameTests.FormatTriggerDtcRoundTrips; begin // 0x03 0x01 → P0301 (cylinder 1 misfire). diff --git a/tests/Tests.OEM.AsiaPacific.pas b/tests/Tests.OEM.AsiaPacific.pas index 3c62c3d9..6fbaec6f 100644 --- a/tests/Tests.OEM.AsiaPacific.pas +++ b/tests/Tests.OEM.AsiaPacific.pas @@ -17,34 +17,91 @@ interface [TestFixture] TVINRoutingTests = class public + /// + /// Toyota vin routes. + /// [Test] procedure ToyotaVinRoutes; + /// + /// Honda vin routes. + /// [Test] procedure HondaVinRoutes; + /// + /// Hyundai kia vin routes. + /// [Test] procedure HyundaiKiaVinRoutes; + /// + /// Nissan vin routes. + /// [Test] procedure NissanVinRoutes; + /// + /// Subaru vin routes. + /// [Test] procedure SubaruVinRoutes; + /// + /// Mazda vin routes. + /// [Test] procedure MazdaVinRoutes; + /// + /// Unknown vin returns nil. + /// [Test] procedure UnknownVinReturnsNil; end; [TestFixture] TAsiaPacificCatalogTests = class public + /// + /// Toyota catalog includes engine e c u. + /// [Test] procedure ToyotaCatalogIncludesEngineECU; + /// + /// Honda seed key has starter. + /// [Test] procedure HondaSeedKeyHasStarter; + /// + /// Hyundai kia heartbeat is1500ms. + /// [Test] procedure HyundaiKiaHeartbeatIs1500ms; + /// + /// Nissan catalog ships consult e c u map. + /// [Test] procedure NissanCatalogShipsConsultECUMap; + /// + /// Subaru catalog includes a w d controller. + /// [Test] procedure SubaruCatalogIncludesAWDController; + /// + /// Mazda catalog includes r b c m. + /// [Test] procedure MazdaCatalogIncludesRBCM; end; [TestFixture] TAsiaPacificDecoderTests = class public + /// + /// Toyota decodes vin. + /// [Test] procedure ToyotaDecodesVin; + /// + /// Honda decodes chassis code. + /// [Test] procedure HondaDecodesChassisCode; + /// + /// Hyundai kia decodes rom id. + /// [Test] procedure HyundaiKiaDecodesRomId; + /// + /// Nissan decodes chassis code. + /// [Test] procedure NissanDecodesChassisCode; + /// + /// Subaru decodes chassis code. + /// [Test] procedure SubaruDecodesChassisCode; + /// + /// Mazda decodes as built code. + /// [Test] procedure MazdaDecodesAsBuiltCode; end; @@ -56,6 +113,9 @@ implementation OBD.OEM.Toyota, OBD.OEM.Honda, OBD.OEM.HyundaiKia, OBD.OEM.Nissan, OBD.OEM.Subaru, OBD.OEM.Mazda; +//------------------------------------------------------------------------------ +// MAKE VIN +//------------------------------------------------------------------------------ function MakeVin(const Prefix: string): string; begin Result := Prefix + '00000000000000'; // 17-char total @@ -65,6 +125,10 @@ function MakeVin(const Prefix: string): string; //============================================================================== // VIN routing //============================================================================== + +//------------------------------------------------------------------------------ +// TOYOTA VIN ROUTES +//------------------------------------------------------------------------------ procedure TVINRoutingTests.ToyotaVinRoutes; var Ext: IOBDOEMExtension; @@ -76,6 +140,9 @@ procedure TVINRoutingTests.ToyotaVinRoutes; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('JN1'))); // Nissan end; +//------------------------------------------------------------------------------ +// HONDA VIN ROUTES +//------------------------------------------------------------------------------ procedure TVINRoutingTests.HondaVinRoutes; var Ext: IOBDOEMExtension; @@ -87,6 +154,9 @@ procedure TVINRoutingTests.HondaVinRoutes; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('JTD'))); // Toyota end; +//------------------------------------------------------------------------------ +// HYUNDAI KIA VIN ROUTES +//------------------------------------------------------------------------------ procedure TVINRoutingTests.HyundaiKiaVinRoutes; var Ext: IOBDOEMExtension; @@ -99,6 +169,9 @@ procedure TVINRoutingTests.HyundaiKiaVinRoutes; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('JN1'))); end; +//------------------------------------------------------------------------------ +// NISSAN VIN ROUTES +//------------------------------------------------------------------------------ procedure TVINRoutingTests.NissanVinRoutes; var Ext: IOBDOEMExtension; @@ -110,6 +183,9 @@ procedure TVINRoutingTests.NissanVinRoutes; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('JF1'))); // Subaru end; +//------------------------------------------------------------------------------ +// SUBARU VIN ROUTES +//------------------------------------------------------------------------------ procedure TVINRoutingTests.SubaruVinRoutes; var Ext: IOBDOEMExtension; @@ -120,6 +196,9 @@ procedure TVINRoutingTests.SubaruVinRoutes; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('JM1'))); // Mazda end; +//------------------------------------------------------------------------------ +// MAZDA VIN ROUTES +//------------------------------------------------------------------------------ procedure TVINRoutingTests.MazdaVinRoutes; var Ext: IOBDOEMExtension; @@ -131,6 +210,9 @@ procedure TVINRoutingTests.MazdaVinRoutes; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('JF1'))); end; +//------------------------------------------------------------------------------ +// UNKNOWN VIN RETURNS NIL +//------------------------------------------------------------------------------ procedure TVINRoutingTests.UnknownVinReturnsNil; var Ext: IOBDOEMExtension; @@ -143,6 +225,10 @@ procedure TVINRoutingTests.UnknownVinReturnsNil; //============================================================================== // Catalog spot-checks //============================================================================== + +//------------------------------------------------------------------------------ +// TOYOTA CATALOG INCLUDES ENGINE ECU +//------------------------------------------------------------------------------ procedure TAsiaPacificCatalogTests.ToyotaCatalogIncludesEngineECU; var Ext: IOBDOEMExtension; @@ -156,6 +242,9 @@ procedure TAsiaPacificCatalogTests.ToyotaCatalogIncludesEngineECU; Assert.IsTrue(Found, 'Toyota must expose engine ECU at 0x7E0'); end; +//------------------------------------------------------------------------------ +// HONDA SEED KEY HAS STARTER +//------------------------------------------------------------------------------ procedure TAsiaPacificCatalogTests.HondaSeedKeyHasStarter; var Ext: IOBDOEMExtension; @@ -164,6 +253,9 @@ procedure TAsiaPacificCatalogTests.HondaSeedKeyHasStarter; Assert.IsNotNull(Ext.SeedKeyRegistry.Find($01)); end; +//------------------------------------------------------------------------------ +// HYUNDAI KIA HEARTBEAT IS1500MS +//------------------------------------------------------------------------------ procedure TAsiaPacificCatalogTests.HyundaiKiaHeartbeatIs1500ms; var Ext: IOBDOEMExtension; @@ -173,6 +265,9 @@ procedure TAsiaPacificCatalogTests.HyundaiKiaHeartbeatIs1500ms; Ext.SessionNegotiator.DefaultTesterPresentMs); end; +//------------------------------------------------------------------------------ +// NISSAN CATALOG SHIPS CONSULT ECUMAP +//------------------------------------------------------------------------------ procedure TAsiaPacificCatalogTests.NissanCatalogShipsConsultECUMap; var Ext: IOBDOEMExtension; @@ -186,6 +281,9 @@ procedure TAsiaPacificCatalogTests.NissanCatalogShipsConsultECUMap; Assert.IsTrue(HasIPDM, 'Nissan must expose IPDM at 0x745'); end; +//------------------------------------------------------------------------------ +// SUBARU CATALOG INCLUDES AWDCONTROLLER +//------------------------------------------------------------------------------ procedure TAsiaPacificCatalogTests.SubaruCatalogIncludesAWDController; var Ext: IOBDOEMExtension; @@ -199,6 +297,9 @@ procedure TAsiaPacificCatalogTests.SubaruCatalogIncludesAWDController; Assert.IsTrue(HasAWD, 'Subaru must expose the AWD/ATV controller'); end; +//------------------------------------------------------------------------------ +// MAZDA CATALOG INCLUDES RBCM +//------------------------------------------------------------------------------ procedure TAsiaPacificCatalogTests.MazdaCatalogIncludesRBCM; var Ext: IOBDOEMExtension; @@ -215,6 +316,10 @@ procedure TAsiaPacificCatalogTests.MazdaCatalogIncludesRBCM; //============================================================================== // Decoder spot-checks (golden vectors against the custom DecodeDID overrides) //============================================================================== + +//------------------------------------------------------------------------------ +// TOYOTA DECODES VIN +//------------------------------------------------------------------------------ procedure TAsiaPacificDecoderTests.ToyotaDecodesVin; var Ext: IOBDOEMExtension; @@ -226,6 +331,9 @@ procedure TAsiaPacificDecoderTests.ToyotaDecodesVin; Assert.IsTrue(Pos('vin = JTDKARFU2L1234567', Output) > 0); end; +//------------------------------------------------------------------------------ +// HONDA DECODES CHASSIS CODE +//------------------------------------------------------------------------------ procedure TAsiaPacificDecoderTests.HondaDecodesChassisCode; var Ext: IOBDOEMExtension; @@ -237,6 +345,9 @@ procedure TAsiaPacificDecoderTests.HondaDecodesChassisCode; Assert.IsTrue(Pos('FK7', Output) > 0); end; +//------------------------------------------------------------------------------ +// HYUNDAI KIA DECODES ROM ID +//------------------------------------------------------------------------------ procedure TAsiaPacificDecoderTests.HyundaiKiaDecodesRomId; var Ext: IOBDOEMExtension; @@ -247,6 +358,9 @@ procedure TAsiaPacificDecoderTests.HyundaiKiaDecodesRomId; Assert.IsTrue(Pos('hmg_rom_id', Output) > 0); end; +//------------------------------------------------------------------------------ +// NISSAN DECODES CHASSIS CODE +//------------------------------------------------------------------------------ procedure TAsiaPacificDecoderTests.NissanDecodesChassisCode; var Ext: IOBDOEMExtension; @@ -257,6 +371,9 @@ procedure TAsiaPacificDecoderTests.NissanDecodesChassisCode; Assert.IsTrue(Pos('nissan_chassis_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// SUBARU DECODES CHASSIS CODE +//------------------------------------------------------------------------------ procedure TAsiaPacificDecoderTests.SubaruDecodesChassisCode; var Ext: IOBDOEMExtension; @@ -267,6 +384,9 @@ procedure TAsiaPacificDecoderTests.SubaruDecodesChassisCode; Assert.IsTrue(Pos('subaru_chassis_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// MAZDA DECODES AS BUILT CODE +//------------------------------------------------------------------------------ procedure TAsiaPacificDecoderTests.MazdaDecodesAsBuiltCode; var Ext: IOBDOEMExtension; diff --git a/tests/Tests.OEM.Captures.pas b/tests/Tests.OEM.Captures.pas index 87a4cb2d..d9228885 100644 --- a/tests/Tests.OEM.Captures.pas +++ b/tests/Tests.OEM.Captures.pas @@ -13,22 +13,58 @@ interface [TestFixture] TCaptureExtractTests = class public + /// + /// Normalize strips e l m framing. + /// [Test] procedure NormalizeStripsELMFraming; + /// + /// Normalize strips prompt and searching. + /// [Test] procedure NormalizeStripsPromptAndSearching; + /// + /// Extract pairs requests with responses. + /// [Test] procedure ExtractPairsRequestsWithResponses; + /// + /// Extract identifies read data by identifier. + /// [Test] procedure ExtractIdentifiesReadDataByIdentifier; + /// + /// Extract captures negative response. + /// [Test] procedure ExtractCapturesNegativeResponse; + /// + /// Extract strips response echo. + /// [Test] procedure ExtractStripsResponseEcho; + /// + /// Hanging request emits empty response. + /// [Test] procedure HangingRequestEmitsEmptyResponse; end; [TestFixture] TCaptureValidatorTests = class public + /// + /// V w capture produces decoded fields. + /// [Test] procedure VWCaptureProducesDecodedFields; + /// + /// B m w capture recognises i stufe and mileage. + /// [Test] procedure BMWCaptureRecognisesIStufeAndMileage; + /// + /// Mercedes capture decodes programming status. + /// [Test] procedure MercedesCaptureDecodesProgrammingStatus; + /// + /// Ford capture decodes calibration id. + /// [Test] procedure FordCaptureDecodesCalibrationId; + /// + /// Negative responses are reported. + /// [Test] procedure NegativeResponsesAreReported; end; @@ -39,6 +75,9 @@ implementation OBD.OEM, OBD.OEM.Captures, OBD.Service.Recorder, OBD.OEM.VW, OBD.OEM.BMW, OBD.OEM.Mercedes, OBD.OEM.Ford; +//------------------------------------------------------------------------------ +// FIXTURE PATH +//------------------------------------------------------------------------------ function FixturePath(const FileName: string): string; var Candidate: string; @@ -60,6 +99,9 @@ function FixturePath(const FileName: string): string; Result := TPath.GetFullPath(Candidate); end; +//------------------------------------------------------------------------------ +// MAKE ENTRY +//------------------------------------------------------------------------------ function MakeEntry(const D: TOBDRecorderDirection; const Text: string): TOBDRecordedEntry; begin @@ -71,6 +113,10 @@ function MakeEntry(const D: TOBDRecorderDirection; //============================================================================== // Extract / normalize //============================================================================== + +//------------------------------------------------------------------------------ +// NORMALIZE STRIPS ELMFRAMING +//------------------------------------------------------------------------------ procedure TCaptureExtractTests.NormalizeStripsELMFraming; begin Assert.AreEqual( @@ -80,6 +126,9 @@ procedure TCaptureExtractTests.NormalizeStripsELMFraming; '1: 38 5A 31 32 33 34 35 36 37')); end; +//------------------------------------------------------------------------------ +// NORMALIZE STRIPS PROMPT AND SEARCHING +//------------------------------------------------------------------------------ procedure TCaptureExtractTests.NormalizeStripsPromptAndSearching; begin Assert.AreEqual('62 F1 86 03', @@ -89,6 +138,9 @@ procedure TCaptureExtractTests.NormalizeStripsPromptAndSearching; '>')); end; +//------------------------------------------------------------------------------ +// EXTRACT PAIRS REQUESTS WITH RESPONSES +//------------------------------------------------------------------------------ procedure TCaptureExtractTests.ExtractPairsRequestsWithResponses; var Pairs: TArray; @@ -103,6 +155,9 @@ procedure TCaptureExtractTests.ExtractPairsRequestsWithResponses; Assert.AreEqual('22 F1 90', Pairs[0].RequestText); end; +//------------------------------------------------------------------------------ +// EXTRACT IDENTIFIES READ DATA BY IDENTIFIER +//------------------------------------------------------------------------------ procedure TCaptureExtractTests.ExtractIdentifiesReadDataByIdentifier; var Pairs: TArray; @@ -117,6 +172,9 @@ procedure TCaptureExtractTests.ExtractIdentifiesReadDataByIdentifier; Assert.AreEqual(Byte($31), Pairs[0].PayloadBytes[0]); end; +//------------------------------------------------------------------------------ +// EXTRACT CAPTURES NEGATIVE RESPONSE +//------------------------------------------------------------------------------ procedure TCaptureExtractTests.ExtractCapturesNegativeResponse; var Pairs: TArray; @@ -129,6 +187,9 @@ procedure TCaptureExtractTests.ExtractCapturesNegativeResponse; Assert.AreEqual(Byte($31), Pairs[0].NegativeResponseCode); end; +//------------------------------------------------------------------------------ +// EXTRACT STRIPS RESPONSE ECHO +//------------------------------------------------------------------------------ procedure TCaptureExtractTests.ExtractStripsResponseEcho; var Pairs: TArray; @@ -143,6 +204,9 @@ procedure TCaptureExtractTests.ExtractStripsResponseEcho; Assert.AreEqual(Byte($03), Pairs[0].PayloadBytes[0]); end; +//------------------------------------------------------------------------------ +// HANGING REQUEST EMITS EMPTY RESPONSE +//------------------------------------------------------------------------------ procedure TCaptureExtractTests.HangingRequestEmitsEmptyResponse; var Pairs: TArray; @@ -159,6 +223,10 @@ procedure TCaptureExtractTests.HangingRequestEmitsEmptyResponse; //============================================================================== // Validator against real OEM extensions //============================================================================== + +//------------------------------------------------------------------------------ +// VWCAPTURE PRODUCES DECODED FIELDS +//------------------------------------------------------------------------------ procedure TCaptureValidatorTests.VWCaptureProducesDecodedFields; var Decoded: TArray; @@ -186,6 +254,9 @@ procedure TCaptureValidatorTests.VWCaptureProducesDecodedFields; Assert.IsTrue(HasNegative, 'VW capture should include a negative reply'); end; +//------------------------------------------------------------------------------ +// BMWCAPTURE RECOGNISES ISTUFE AND MILEAGE +//------------------------------------------------------------------------------ procedure TCaptureValidatorTests.BMWCaptureRecognisesIStufeAndMileage; var Decoded: TArray; @@ -210,6 +281,9 @@ procedure TCaptureValidatorTests.BMWCaptureRecognisesIStufeAndMileage; Assert.IsTrue(HasMileage); end; +//------------------------------------------------------------------------------ +// MERCEDES CAPTURE DECODES PROGRAMMING STATUS +//------------------------------------------------------------------------------ procedure TCaptureValidatorTests.MercedesCaptureDecodesProgrammingStatus; var Decoded: TArray; @@ -228,6 +302,9 @@ procedure TCaptureValidatorTests.MercedesCaptureDecodesProgrammingStatus; Assert.IsTrue(Found, 'Mercedes capture should include F19E'); end; +//------------------------------------------------------------------------------ +// FORD CAPTURE DECODES CALIBRATION ID +//------------------------------------------------------------------------------ procedure TCaptureValidatorTests.FordCaptureDecodesCalibrationId; var Decoded: TArray; @@ -246,6 +323,9 @@ procedure TCaptureValidatorTests.FordCaptureDecodesCalibrationId; Assert.IsTrue(HasCal); end; +//------------------------------------------------------------------------------ +// NEGATIVE RESPONSES ARE REPORTED +//------------------------------------------------------------------------------ procedure TCaptureValidatorTests.NegativeResponsesAreReported; var Decoded: TArray; diff --git a/tests/Tests.OEM.Catalog.pas b/tests/Tests.OEM.Catalog.pas index 409edce2..637db877 100644 --- a/tests/Tests.OEM.Catalog.pas +++ b/tests/Tests.OEM.Catalog.pas @@ -13,38 +13,107 @@ interface [TestFixture] TJSONCatalogTests = class public + /// + /// Parses minimal catalog. + /// [Test] procedure ParsesMinimalCatalog; + /// + /// Parses all decoder kinds. + /// [Test] procedure ParsesAllDecoderKinds; + /// + /// Decode u int8 with scale. + /// [Test] procedure DecodeUInt8WithScale; + /// + /// Decode u int16 b e reversed. + /// [Test] procedure DecodeUInt16BEReversed; + /// + /// Decode bcd date. + /// [Test] procedure DecodeBcdDate; + /// + /// Decode enum known value. + /// [Test] procedure DecodeEnumKnownValue; + /// + /// Decode enum unknown value falls back to hex. + /// [Test] procedure DecodeEnumUnknownValueFallsBackToHex; + /// + /// Decode bitmask. + /// [Test] procedure DecodeBitmask; + /// + /// Decode ascii. + /// [Test] procedure DecodeAscii; + /// + /// Find d i d returns false for unknown. + /// [Test] procedure FindDIDReturnsFalseForUnknown; + /// + /// Default source propagates to entries. + /// [Test] procedure DefaultSourcePropagatesToEntries; + /// + /// Verified flag defaults to false. + /// [Test] procedure VerifiedFlagDefaultsToFalse; end; [TestFixture] TCSVImporterTests = class public + /// + /// Round trips basic c s v. + /// [Test] procedure RoundTripsBasicCSV; + /// + /// Handles quoted decoder j s o n. + /// [Test] procedure HandlesQuotedDecoderJSON; + /// + /// Rejects missing mandatory column. + /// [Test] procedure RejectsMissingMandatoryColumn; + /// + /// Skips comment lines. + /// [Test] procedure SkipsCommentLines; end; [TestFixture] TPerECUTests = class public + /// + /// Loads e c u list. + /// [Test] procedure LoadsECUList; + /// + /// Parses per d i d ecu address. + /// [Test] procedure ParsesPerDIDEcuAddress; + /// + /// Default ecu address propagates. + /// [Test] procedure DefaultEcuAddressPropagates; + /// + /// Explicit address overrides default. + /// [Test] procedure ExplicitAddressOverridesDefault; + /// + /// Routine ecu address loaded. + /// [Test] procedure RoutineEcuAddressLoaded; + /// + /// Extension filters by e c u. + /// [Test] procedure ExtensionFiltersByECU; + /// + /// Extension globals flow to all e c us. + /// [Test] procedure ExtensionGlobalsFlowToAllECUs; end; @@ -71,8 +140,12 @@ implementation ']' + '}'; +//------------------------------------------------------------------------------ +// PARSES MINIMAL CATALOG +//------------------------------------------------------------------------------ procedure TJSONCatalogTests.ParsesMinimalCatalog; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := TOBDOEMJSONCatalog.CreateFromText(MINIMAL_CATALOG); try @@ -90,6 +163,9 @@ procedure TJSONCatalogTests.ParsesMinimalCatalog; end; end; +//------------------------------------------------------------------------------ +// PARSES ALL DECODER KINDS +//------------------------------------------------------------------------------ procedure TJSONCatalogTests.ParsesAllDecoderKinds; const ALL_KINDS: string = @@ -107,7 +183,8 @@ procedure TJSONCatalogTests.ParsesAllDecoderKinds; ' {"did": "0x0A", "name": "j", "description": "enum", "decoder": {"kind": "enum", "size": 1, "values": {"0x01": "ON", "0x02": "OFF"}}},' + ' {"did": "0x0B", "name": "k", "description": "bits", "decoder": {"kind": "bitmask", "size": 1, "bits": {"0": "ready", "3": "fault"}}}' + ']}'; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := TOBDOEMJSONCatalog.CreateFromText(ALL_KINDS); try @@ -117,6 +194,9 @@ procedure TJSONCatalogTests.ParsesAllDecoderKinds; end; end; +//------------------------------------------------------------------------------ +// DECODE UINT8 WITH SCALE +//------------------------------------------------------------------------------ procedure TJSONCatalogTests.DecodeUInt8WithScale; const Spec: string = @@ -136,6 +216,9 @@ procedure TJSONCatalogTests.DecodeUInt8WithScale; end; end; +//------------------------------------------------------------------------------ +// DECODE UINT16 BEREVERSED +//------------------------------------------------------------------------------ procedure TJSONCatalogTests.DecodeUInt16BEReversed; const Spec: string = @@ -154,6 +237,9 @@ procedure TJSONCatalogTests.DecodeUInt16BEReversed; end; end; +//------------------------------------------------------------------------------ +// DECODE BCD DATE +//------------------------------------------------------------------------------ procedure TJSONCatalogTests.DecodeBcdDate; const Spec: string = @@ -172,6 +258,9 @@ procedure TJSONCatalogTests.DecodeBcdDate; end; end; +//------------------------------------------------------------------------------ +// DECODE ENUM KNOWN VALUE +//------------------------------------------------------------------------------ procedure TJSONCatalogTests.DecodeEnumKnownValue; const Spec: string = @@ -179,7 +268,8 @@ procedure TJSONCatalogTests.DecodeEnumKnownValue; ' "applicable_wmis": [], "dids": [{"did": "0x01", "name": "s", "description": "x",' + ' "decoder": {"kind": "enum", "size": 1,' + ' "values": {"0x01": "default", "0x03": "extended"}}}]}'; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := TOBDOEMJSONCatalog.CreateFromText(Spec); try @@ -190,13 +280,17 @@ procedure TJSONCatalogTests.DecodeEnumKnownValue; end; end; +//------------------------------------------------------------------------------ +// DECODE ENUM UNKNOWN VALUE FALLS BACK TO HEX +//------------------------------------------------------------------------------ procedure TJSONCatalogTests.DecodeEnumUnknownValueFallsBackToHex; const Spec: string = '{"version": 1, "manufacturer_key": "X", "display_name": "X",' + ' "applicable_wmis": [], "dids": [{"did": "0x01", "name": "s", "description": "x",' + ' "decoder": {"kind": "enum", "size": 1, "values": {"0x01": "default"}}}]}'; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := TOBDOEMJSONCatalog.CreateFromText(Spec); try @@ -206,6 +300,9 @@ procedure TJSONCatalogTests.DecodeEnumUnknownValueFallsBackToHex; end; end; +//------------------------------------------------------------------------------ +// DECODE BITMASK +//------------------------------------------------------------------------------ procedure TJSONCatalogTests.DecodeBitmask; const Spec: string = @@ -213,7 +310,8 @@ procedure TJSONCatalogTests.DecodeBitmask; ' "applicable_wmis": [], "dids": [{"did": "0x01", "name": "b", "description": "x",' + ' "decoder": {"kind": "bitmask", "size": 1,' + ' "bits": {"0": "ready", "1": "running", "3": "fault"}}}]}'; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := TOBDOEMJSONCatalog.CreateFromText(Spec); try @@ -225,6 +323,9 @@ procedure TJSONCatalogTests.DecodeBitmask; end; end; +//------------------------------------------------------------------------------ +// DECODE ASCII +//------------------------------------------------------------------------------ procedure TJSONCatalogTests.DecodeAscii; const Spec: string = @@ -243,6 +344,9 @@ procedure TJSONCatalogTests.DecodeAscii; end; end; +//------------------------------------------------------------------------------ +// FIND DIDRETURNS FALSE FOR UNKNOWN +//------------------------------------------------------------------------------ procedure TJSONCatalogTests.FindDIDReturnsFalseForUnknown; var Cat: TOBDOEMJSONCatalog; @@ -256,6 +360,9 @@ procedure TJSONCatalogTests.FindDIDReturnsFalseForUnknown; end; end; +//------------------------------------------------------------------------------ +// DEFAULT SOURCE PROPAGATES TO ENTRIES +//------------------------------------------------------------------------------ procedure TJSONCatalogTests.DefaultSourcePropagatesToEntries; var Cat: TOBDOEMJSONCatalog; @@ -270,6 +377,9 @@ procedure TJSONCatalogTests.DefaultSourcePropagatesToEntries; end; end; +//------------------------------------------------------------------------------ +// VERIFIED FLAG DEFAULTS TO FALSE +//------------------------------------------------------------------------------ procedure TJSONCatalogTests.VerifiedFlagDefaultsToFalse; var Cat: TOBDOEMJSONCatalog; @@ -289,6 +399,10 @@ procedure TJSONCatalogTests.VerifiedFlagDefaultsToFalse; //============================================================================== // CSV importer //============================================================================== + +//------------------------------------------------------------------------------ +// ROUND TRIPS BASIC CSV +//------------------------------------------------------------------------------ procedure TCSVImporterTests.RoundTripsBasicCSV; var Importer: TOBDCatalogCSVImporter; @@ -321,6 +435,9 @@ procedure TCSVImporterTests.RoundTripsBasicCSV; end; end; +//------------------------------------------------------------------------------ +// HANDLES QUOTED DECODER JSON +//------------------------------------------------------------------------------ procedure TCSVImporterTests.HandlesQuotedDecoderJSON; var Importer: TOBDCatalogCSVImporter; @@ -351,6 +468,9 @@ procedure TCSVImporterTests.HandlesQuotedDecoderJSON; end; end; +//------------------------------------------------------------------------------ +// REJECTS MISSING MANDATORY COLUMN +//------------------------------------------------------------------------------ procedure TCSVImporterTests.RejectsMissingMandatoryColumn; var Importer: TOBDCatalogCSVImporter; @@ -368,6 +488,9 @@ procedure TCSVImporterTests.RejectsMissingMandatoryColumn; end; end; +//------------------------------------------------------------------------------ +// SKIPS COMMENT LINES +//------------------------------------------------------------------------------ procedure TCSVImporterTests.SkipsCommentLines; var Importer: TOBDCatalogCSVImporter; @@ -443,7 +566,8 @@ procedure TCSVImporterTests.SkipsCommentLines; TTestECUExtension = class(TOBDOEMExtensionBase) protected procedure BuildCatalog(var DIDs: TArray; - var Routines: TArray; + var + Routines: TArray; var ECUs: TArray); override; public function ManufacturerKey: string; override; @@ -451,10 +575,16 @@ TTestECUExtension = class(TOBDOEMExtensionBase) function ApplicableToVIN(const VIN: string): Boolean; override; end; +//------------------------------------------------------------------------------ +// BUILD CATALOG +//------------------------------------------------------------------------------ procedure TTestECUExtension.BuildCatalog( - var DIDs: TArray; - var Routines: TArray; - var ECUs: TArray); + var + DIDs: TArray; + var + Routines: TArray; + var + ECUs: TArray); var Cat: TOBDOEMJSONCatalog; begin @@ -468,13 +598,30 @@ procedure TTestECUExtension.BuildCatalog( end; end; +//------------------------------------------------------------------------------ +// MANUFACTURER KEY +//------------------------------------------------------------------------------ function TTestECUExtension.ManufacturerKey: string; begin Result := 'T'; end; + +//------------------------------------------------------------------------------ +// DISPLAY NAME +//------------------------------------------------------------------------------ function TTestECUExtension.DisplayName: string; begin Result := 'Test'; end; + +//------------------------------------------------------------------------------ +// APPLICABLE TO VIN +//------------------------------------------------------------------------------ function TTestECUExtension.ApplicableToVIN(const VIN: string): Boolean; -begin Result := False; end; +begin + Result := False; +end; +//------------------------------------------------------------------------------ +// LOADS ECULIST +//------------------------------------------------------------------------------ procedure TPerECUTests.LoadsECUList; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := TOBDOEMJSONCatalog.CreateFromText(ECU_CATALOG); try @@ -487,6 +634,9 @@ procedure TPerECUTests.LoadsECUList; end; end; +//------------------------------------------------------------------------------ +// PARSES PER DIDECU ADDRESS +//------------------------------------------------------------------------------ procedure TPerECUTests.ParsesPerDIDEcuAddress; var Cat: TOBDOEMJSONCatalog; @@ -501,6 +651,9 @@ procedure TPerECUTests.ParsesPerDIDEcuAddress; end; end; +//------------------------------------------------------------------------------ +// DEFAULT ECU ADDRESS PROPAGATES +//------------------------------------------------------------------------------ procedure TPerECUTests.DefaultEcuAddressPropagates; var Cat: TOBDOEMJSONCatalog; @@ -516,6 +669,9 @@ procedure TPerECUTests.DefaultEcuAddressPropagates; end; end; +//------------------------------------------------------------------------------ +// EXPLICIT ADDRESS OVERRIDES DEFAULT +//------------------------------------------------------------------------------ procedure TPerECUTests.ExplicitAddressOverridesDefault; var Cat: TOBDOEMJSONCatalog; @@ -530,6 +686,9 @@ procedure TPerECUTests.ExplicitAddressOverridesDefault; end; end; +//------------------------------------------------------------------------------ +// ROUTINE ECU ADDRESS LOADED +//------------------------------------------------------------------------------ procedure TPerECUTests.RoutineEcuAddressLoaded; var Cat: TOBDOEMJSONCatalog; @@ -554,6 +713,9 @@ procedure TPerECUTests.RoutineEcuAddressLoaded; end; end; +//------------------------------------------------------------------------------ +// EXTENSION FILTERS BY ECU +//------------------------------------------------------------------------------ procedure TPerECUTests.ExtensionFiltersByECU; var Ext: IOBDOEMExtension; @@ -580,6 +742,9 @@ procedure TPerECUTests.ExtensionFiltersByECU; Assert.IsFalse(HasRPM, 'rpm (engine) should NOT be in 0x7E1 sub-catalog'); end; +//------------------------------------------------------------------------------ +// EXTENSION GLOBALS FLOW TO ALL ECUS +//------------------------------------------------------------------------------ procedure TPerECUTests.ExtensionGlobalsFlowToAllECUs; var Ext: IOBDOEMExtension; diff --git a/tests/Tests.OEM.CatalogIntegrity.pas b/tests/Tests.OEM.CatalogIntegrity.pas index d1fee518..f06b3eb4 100644 --- a/tests/Tests.OEM.CatalogIntegrity.pas +++ b/tests/Tests.OEM.CatalogIntegrity.pas @@ -34,8 +34,17 @@ interface [TestFixture] TCatalogIntegrityTests = class public + /// + /// Coding block bit fields fit within payload. + /// [Test] procedure CodingBlockBitFieldsFitWithinPayload; + /// + /// Cross section ecu references resolve. + /// [Test] procedure CrossSectionEcuReferencesResolve; + /// + /// No duplicate primary keys. + /// [Test] procedure NoDuplicatePrimaryKeys; end; @@ -61,12 +70,18 @@ function CatalogsRoot: string; Result := ''; end; -/// Collect every OEM catalog JSON path under catalogs/, -/// recursing into vehicle-class subdirectories. Excludes -/// dtc-*.json (different schema), iso-* / -/// uds-* / obd2-* universal catalogs, the -/// _schema/ directory, and any test-*.json -/// fixture. +/// +/// Collect every OEM catalog JSON path under catalogs/, +/// recursing into vehicle-class subdirectories. Excludes +/// dtc-*.json (different schema), iso-* / +/// uds-* / obd2-* universal catalogs, the +/// _schema/ directory, and any test-*.json +/// fixture. +/// + +//------------------------------------------------------------------------------ +// COLLECT OEM CATALOGS +//------------------------------------------------------------------------------ function CollectOemCatalogs(const Root: string): TArray; var All: TArray; @@ -96,6 +111,9 @@ function CollectOemCatalogs(const Root: string): TArray; end; end; +//------------------------------------------------------------------------------ +// PARSE ADDRESS +//------------------------------------------------------------------------------ function ParseAddress(const S: string): Integer; var Tmp: string; @@ -113,8 +131,14 @@ function ParseAddress(const S: string): Integer; Result := -1; end; -/// Decode a JSON address-shaped value (int or "0xHHHH") to -/// an integer, or -1 if absent / malformed. +/// +/// Decode a JSON address-shaped value (int or "0xHHHH") to +/// an integer, or -1 if absent / malformed. +/// + +//------------------------------------------------------------------------------ +// JSON ADDR FROM OBJECT +//------------------------------------------------------------------------------ function JsonAddrFromObject(Obj: TJSONObject; const Field: string): Integer; var @@ -134,9 +158,15 @@ function JsonAddrFromObject(Obj: TJSONObject; end; end; -/// Conservative bit width per coding-field kind. Mirrors -/// the JSON loader's ParseCodingFieldKind mapping but expressed -/// here so this test doesn't depend on the loader for sizing. +/// +/// Conservative bit width per coding-field kind. Mirrors +/// the JSON loader's ParseCodingFieldKind mapping but expressed +/// here so this test doesn't depend on the loader for sizing. +/// + +//------------------------------------------------------------------------------ +// FIELD BIT WIDTH +//------------------------------------------------------------------------------ function FieldBitWidth(const KindStr: string; BitWidthOverride: Integer): Integer; begin @@ -312,6 +342,9 @@ procedure TCatalogIntegrityTests.CrossSectionEcuReferencesResolve; type TIntSet = TList; +//------------------------------------------------------------------------------ +// CHECK NO DUPLICATE ADDR FIELD +//------------------------------------------------------------------------------ procedure CheckNoDuplicateAddrField( RootObj: TJSONObject; const Name, SectionName, KeyField: string); var @@ -340,6 +373,9 @@ procedure CheckNoDuplicateAddrField( end; end; +//------------------------------------------------------------------------------ +// NO DUPLICATE PRIMARY KEYS +//------------------------------------------------------------------------------ procedure TCatalogIntegrityTests.NoDuplicatePrimaryKeys; var Root, Path, Name: string; diff --git a/tests/Tests.OEM.CatalogSmoke.pas b/tests/Tests.OEM.CatalogSmoke.pas index 59e56ee0..3e038503 100644 --- a/tests/Tests.OEM.CatalogSmoke.pas +++ b/tests/Tests.OEM.CatalogSmoke.pas @@ -22,67 +22,230 @@ interface [TestFixture] TCatalogLoadSmokeTests = class public + /// + /// Universal u d s catalog loads. + /// [Test] procedure UniversalUDSCatalogLoads; + /// + /// Universal o b d pids catalog loads. + /// [Test] procedure UniversalOBDPidsCatalogLoads; + /// + /// V w catalog loads. + /// [Test] procedure VWCatalogLoads; + /// + /// B m w catalog loads. + /// [Test] procedure BMWCatalogLoads; + /// + /// Mercedes catalog loads. + /// [Test] procedure MercedesCatalogLoads; + /// + /// Ford catalog loads. + /// [Test] procedure FordCatalogLoads; + /// + /// G m catalog loads. + /// [Test] procedure GMCatalogLoads; + /// + /// Stellantis catalog loads. + /// [Test] procedure StellantisCatalogLoads; + /// + /// Toyota catalog loads. + /// [Test] procedure ToyotaCatalogLoads; + /// + /// Honda catalog loads. + /// [Test] procedure HondaCatalogLoads; + /// + /// H m g catalog loads. + /// [Test] procedure HMGCatalogLoads; + /// + /// Nissan catalog loads. + /// [Test] procedure NissanCatalogLoads; + /// + /// Subaru catalog loads. + /// [Test] procedure SubaruCatalogLoads; + /// + /// Mazda catalog loads. + /// [Test] procedure MazdaCatalogLoads; + /// + /// Renault catalog loads. + /// [Test] procedure RenaultCatalogLoads; + /// + /// Volvo catalog loads. + /// [Test] procedure VolvoCatalogLoads; + /// + /// Tesla catalog loads. + /// [Test] procedure TeslaCatalogLoads; + /// + /// Suzuki catalog loads. + /// [Test] procedure SuzukiCatalogLoads; + /// + /// Mitsubishi catalog loads. + /// [Test] procedure MitsubishiCatalogLoads; + /// + /// Cummins catalog loads. + /// [Test] procedure CumminsCatalogLoads; + /// + /// Detroit catalog loads. + /// [Test] procedure DetroitCatalogLoads; + /// + /// P a c c a r catalog loads. + /// [Test] procedure PACCARCatalogLoads; + /// + /// Volvo trucks catalog loads. + /// [Test] procedure VolvoTrucksCatalogLoads; + /// + /// Scania catalog loads. + /// [Test] procedure ScaniaCatalogLoads; + /// + /// M a n catalog loads. + /// [Test] procedure MANCatalogLoads; + /// + /// B y d catalog loads. + /// [Test] procedure BYDCatalogLoads; + /// + /// Geely catalog loads. + /// [Test] procedure GeelyCatalogLoads; + /// + /// N i o catalog loads. + /// [Test] procedure NIOCatalogLoads; + /// + /// Xpeng catalog loads. + /// [Test] procedure XpengCatalogLoads; + /// + /// G w m catalog loads. + /// [Test] procedure GWMCatalogLoads; + /// + /// J l r catalog loads. + /// [Test] procedure JLRCatalogLoads; + /// + /// Porsche catalog loads. + /// [Test] procedure PorscheCatalogLoads; + /// + /// Polestar catalog loads. + /// [Test] procedure PolestarCatalogLoads; + /// + /// M i n i catalog loads. + /// [Test] procedure MINICatalogLoads; + /// + /// Smart catalog loads. + /// [Test] procedure SmartCatalogLoads; + /// + /// Dacia catalog loads. + /// [Test] procedure DaciaCatalogLoads; + /// + /// Lada catalog loads. + /// [Test] procedure LadaCatalogLoads; + /// + /// Mahindra catalog loads. + /// [Test] procedure MahindraCatalogLoads; + /// + /// Tata catalog loads. + /// [Test] procedure TataCatalogLoads; + /// + /// Aston martin catalog loads. + /// [Test] procedure AstonMartinCatalogLoads; + /// + /// Bentley catalog loads. + /// [Test] procedure BentleyCatalogLoads; + /// + /// Rolls royce catalog loads. + /// [Test] procedure RollsRoyceCatalogLoads; + /// + /// Ferrari catalog loads. + /// [Test] procedure FerrariCatalogLoads; + /// + /// Mc laren catalog loads. + /// [Test] procedure McLarenCatalogLoads; + /// + /// Rivian catalog loads. + /// [Test] procedure RivianCatalogLoads; + /// + /// Lucid catalog loads. + /// [Test] procedure LucidCatalogLoads; + /// + /// Isuzu catalog loads. + /// [Test] procedure IsuzuCatalogLoads; + /// + /// Iveco catalog loads. + /// [Test] procedure IvecoCatalogLoads; + /// + /// D t c i s o15031 catalog loads. + /// [Test] procedure DTCISO15031CatalogLoads; - /// Data-driven sweep: every catalogs/*.json that - /// isn't a DTC, ISO standard, or test fixture must load without - /// raising. Auto-picks up new catalogs (motorcycles, agricultural, - /// marine, etc.) without test-file edits. + /// + /// Data-driven sweep: every catalogs/*.json that + /// isn't a DTC, ISO standard, or test fixture must load without + /// raising. Auto-picks up new catalogs (motorcycles, agricultural, + /// marine, etc.) without test-file edits. + /// [Test] procedure AllOEMCatalogsLoadFromDirectory; - /// Every shipped catalogs/dtc-*.json must load and - /// declare at least one DTC entry. + /// + /// Every shipped catalogs/dtc-*.json must load and + /// declare at least one DTC entry. + /// [Test] procedure AllDtcCatalogsLoadFromDirectory; // -------- Phase B vehicle-class subdirectories -------- + /// + /// All motorcycle catalogs load. + /// [Test] procedure AllMotorcycleCatalogsLoad; + /// + /// All agricultural catalogs load. + /// [Test] procedure AllAgriculturalCatalogsLoad; + /// + /// All marine catalogs load. + /// [Test] procedure AllMarineCatalogsLoad; + /// + /// All powersports catalogs load. + /// [Test] procedure AllPowersportsCatalogsLoad; end; @@ -98,6 +261,10 @@ implementation // (including the v3.77 vehicle-class subdirectories) as production // code does. Retained as a thin wrapper for the loader miss case // where Tests want to surface a clearer skip message. + +//------------------------------------------------------------------------------ +// LOCATE CATALOG OR SKIP +//------------------------------------------------------------------------------ function LocateCatalogOrSkip(const FileName: string): string; begin Result := ResolveCatalogPath(FileName); @@ -105,6 +272,9 @@ function LocateCatalogOrSkip(const FileName: string): string; Assert.Pass(Format('catalog %s not on path; skipping', [FileName])); end; +//------------------------------------------------------------------------------ +// SMOKE LOAD +//------------------------------------------------------------------------------ procedure SmokeLoad(const FileName: string; const RequireManufacturerKey: Boolean = True); var @@ -124,6 +294,9 @@ procedure SmokeLoad(const FileName: string; end; end; +//------------------------------------------------------------------------------ +// SMOKE LOAD DTC +//------------------------------------------------------------------------------ procedure SmokeLoadDtc(const FileName: string); var Cat: TOBDDtcCatalog; @@ -140,60 +313,260 @@ procedure SmokeLoadDtc(const FileName: string); end; end; +//------------------------------------------------------------------------------ +// UNIVERSAL UDSCATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.UniversalUDSCatalogLoads; -begin SmokeLoad('uds-standard.json', False); end; +begin + SmokeLoad('uds-standard.json', False); +end; +//------------------------------------------------------------------------------ +// UNIVERSAL OBDPIDS CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.UniversalOBDPidsCatalogLoads; -begin SmokeLoad('obd2-pids.json', False); end; +begin + SmokeLoad('obd2-pids.json', False); +end; +//------------------------------------------------------------------------------ +// VWCATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.VWCatalogLoads; begin SmokeLoad('vw.json'); end; + +//------------------------------------------------------------------------------ +// BMWCATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.BMWCatalogLoads; begin SmokeLoad('bmw.json'); end; + +//------------------------------------------------------------------------------ +// MERCEDES CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.MercedesCatalogLoads; begin SmokeLoad('mercedes.json'); end; + +//------------------------------------------------------------------------------ +// FORD CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.FordCatalogLoads; begin SmokeLoad('ford.json'); end; + +//------------------------------------------------------------------------------ +// GMCATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.GMCatalogLoads; begin SmokeLoad('gm.json'); end; + +//------------------------------------------------------------------------------ +// STELLANTIS CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.StellantisCatalogLoads; begin SmokeLoad('stellantis.json'); end; + +//------------------------------------------------------------------------------ +// TOYOTA CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.ToyotaCatalogLoads; begin SmokeLoad('toyota.json'); end; + +//------------------------------------------------------------------------------ +// HONDA CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.HondaCatalogLoads; begin SmokeLoad('honda.json'); end; + +//------------------------------------------------------------------------------ +// HMGCATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.HMGCatalogLoads; begin SmokeLoad('hmg.json'); end; + +//------------------------------------------------------------------------------ +// NISSAN CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.NissanCatalogLoads; begin SmokeLoad('nissan.json'); end; + +//------------------------------------------------------------------------------ +// SUBARU CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.SubaruCatalogLoads; begin SmokeLoad('subaru.json'); end; + +//------------------------------------------------------------------------------ +// MAZDA CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.MazdaCatalogLoads; begin SmokeLoad('mazda.json'); end; + +//------------------------------------------------------------------------------ +// RENAULT CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.RenaultCatalogLoads; begin SmokeLoad('renault.json'); end; + +//------------------------------------------------------------------------------ +// VOLVO CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.VolvoCatalogLoads; begin SmokeLoad('volvo.json'); end; + +//------------------------------------------------------------------------------ +// TESLA CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.TeslaCatalogLoads; begin SmokeLoad('tesla.json'); end; + +//------------------------------------------------------------------------------ +// SUZUKI CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.SuzukiCatalogLoads; begin SmokeLoad('suzuki.json'); end; + +//------------------------------------------------------------------------------ +// MITSUBISHI CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.MitsubishiCatalogLoads; begin SmokeLoad('mitsubishi.json'); end; + +//------------------------------------------------------------------------------ +// CUMMINS CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.CumminsCatalogLoads; begin SmokeLoad('cummins.json'); end; + +//------------------------------------------------------------------------------ +// DETROIT CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.DetroitCatalogLoads; begin SmokeLoad('detroit.json'); end; + +//------------------------------------------------------------------------------ +// PACCARCATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.PACCARCatalogLoads; begin SmokeLoad('paccar.json'); end; + +//------------------------------------------------------------------------------ +// VOLVO TRUCKS CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.VolvoTrucksCatalogLoads; begin SmokeLoad('volvotrucks.json'); end; + +//------------------------------------------------------------------------------ +// SCANIA CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.ScaniaCatalogLoads; begin SmokeLoad('scania.json'); end; + +//------------------------------------------------------------------------------ +// MANCATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.MANCatalogLoads; begin SmokeLoad('man.json'); end; + +//------------------------------------------------------------------------------ +// BYDCATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.BYDCatalogLoads; begin SmokeLoad('byd.json'); end; + +//------------------------------------------------------------------------------ +// GEELY CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.GeelyCatalogLoads; begin SmokeLoad('geely.json'); end; + +//------------------------------------------------------------------------------ +// NIOCATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.NIOCatalogLoads; begin SmokeLoad('nio.json'); end; + +//------------------------------------------------------------------------------ +// XPENG CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.XpengCatalogLoads; begin SmokeLoad('xpeng.json'); end; + +//------------------------------------------------------------------------------ +// GWMCATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.GWMCatalogLoads; begin SmokeLoad('gwm.json'); end; + +//------------------------------------------------------------------------------ +// JLRCATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.JLRCatalogLoads; begin SmokeLoad('jlr.json'); end; + +//------------------------------------------------------------------------------ +// PORSCHE CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.PorscheCatalogLoads; begin SmokeLoad('porsche.json'); end; + +//------------------------------------------------------------------------------ +// POLESTAR CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.PolestarCatalogLoads; begin SmokeLoad('polestar.json'); end; + +//------------------------------------------------------------------------------ +// MINICATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.MINICatalogLoads; begin SmokeLoad('mini.json'); end; + +//------------------------------------------------------------------------------ +// SMART CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.SmartCatalogLoads; begin SmokeLoad('smart.json'); end; + +//------------------------------------------------------------------------------ +// DACIA CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.DaciaCatalogLoads; begin SmokeLoad('dacia.json'); end; + +//------------------------------------------------------------------------------ +// LADA CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.LadaCatalogLoads; begin SmokeLoad('lada.json'); end; + +//------------------------------------------------------------------------------ +// MAHINDRA CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.MahindraCatalogLoads; begin SmokeLoad('mahindra.json'); end; + +//------------------------------------------------------------------------------ +// TATA CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.TataCatalogLoads; begin SmokeLoad('tata.json'); end; + +//------------------------------------------------------------------------------ +// ASTON MARTIN CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.AstonMartinCatalogLoads; begin SmokeLoad('aston-martin.json'); end; + +//------------------------------------------------------------------------------ +// BENTLEY CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.BentleyCatalogLoads; begin SmokeLoad('bentley.json'); end; + +//------------------------------------------------------------------------------ +// ROLLS ROYCE CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.RollsRoyceCatalogLoads; begin SmokeLoad('rolls-royce.json'); end; + +//------------------------------------------------------------------------------ +// FERRARI CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.FerrariCatalogLoads; begin SmokeLoad('ferrari.json'); end; + +//------------------------------------------------------------------------------ +// MC LAREN CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.McLarenCatalogLoads; begin SmokeLoad('mclaren.json'); end; + +//------------------------------------------------------------------------------ +// RIVIAN CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.RivianCatalogLoads; begin SmokeLoad('rivian.json'); end; + +//------------------------------------------------------------------------------ +// LUCID CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.LucidCatalogLoads; begin SmokeLoad('lucid.json'); end; + +//------------------------------------------------------------------------------ +// ISUZU CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.IsuzuCatalogLoads; begin SmokeLoad('isuzu.json'); end; + +//------------------------------------------------------------------------------ +// IVECO CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.IvecoCatalogLoads; begin SmokeLoad('iveco.json'); end; + +//------------------------------------------------------------------------------ +// DTCISO15031 CATALOG LOADS +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.DTCISO15031CatalogLoads; begin SmokeLoadDtc('dtc-iso-15031.json'); end; +//------------------------------------------------------------------------------ +// CATALOGS DIRECTORY +//------------------------------------------------------------------------------ function CatalogsDirectory: string; var Candidate: string; @@ -206,6 +579,9 @@ function CatalogsDirectory: string; Result := ''; end; +//------------------------------------------------------------------------------ +// ALL OEMCATALOGS LOAD FROM DIRECTORY +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.AllOEMCatalogsLoadFromDirectory; var Dir, FilePath, Name, RelDir: string; @@ -257,6 +633,9 @@ procedure TCatalogLoadSmokeTests.AllOEMCatalogsLoadFromDirectory; [Loaded, Skipped])); end; +//------------------------------------------------------------------------------ +// ALL DTC CATALOGS LOAD FROM DIRECTORY +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.AllDtcCatalogsLoadFromDirectory; var Dir, FilePath, Name: string; @@ -277,6 +656,9 @@ procedure TCatalogLoadSmokeTests.AllDtcCatalogsLoadFromDirectory; Format('expected >=30 DTC catalogs loaded, got %d', [Loaded])); end; +//------------------------------------------------------------------------------ +// SWEEP SUBDIR +//------------------------------------------------------------------------------ procedure SweepSubdir(const Subdir: string; MinCount: Integer); var Dir, FilePath: string; @@ -310,17 +692,37 @@ procedure SweepSubdir(const Subdir: string; MinCount: Integer); [Subdir, MinCount, Loaded])); end; +//------------------------------------------------------------------------------ +// ALL MOTORCYCLE CATALOGS LOAD +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.AllMotorcycleCatalogsLoad; -begin SweepSubdir('motorcycle', 14); end; +begin + SweepSubdir('motorcycle', 14); +end; +//------------------------------------------------------------------------------ +// ALL AGRICULTURAL CATALOGS LOAD +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.AllAgriculturalCatalogsLoad; -begin SweepSubdir('agricultural', 8); end; +begin + SweepSubdir('agricultural', 8); +end; +//------------------------------------------------------------------------------ +// ALL MARINE CATALOGS LOAD +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.AllMarineCatalogsLoad; -begin SweepSubdir('marine', 6); end; +begin + SweepSubdir('marine', 6); +end; +//------------------------------------------------------------------------------ +// ALL POWERSPORTS CATALOGS LOAD +//------------------------------------------------------------------------------ procedure TCatalogLoadSmokeTests.AllPowersportsCatalogsLoad; -begin SweepSubdir('powersports', 5); end; +begin + SweepSubdir('powersports', 5); +end; initialization TDUnitX.RegisterTestFixture(TCatalogLoadSmokeTests); diff --git a/tests/Tests.OEM.China.pas b/tests/Tests.OEM.China.pas index 89a59b11..1d043208 100644 --- a/tests/Tests.OEM.China.pas +++ b/tests/Tests.OEM.China.pas @@ -15,31 +15,79 @@ interface [TestFixture] TChinaVINTests = class public + /// + /// B y d matches all plants. + /// [Test] procedure BYDMatchesAllPlants; + /// + /// Geely matches lynk and zeekr. + /// [Test] procedure GeelyMatchesLynkAndZeekr; + /// + /// N i o matches hefei. + /// [Test] procedure NIOMatchesHefei; + /// + /// Xpeng matches guangzhou and zhaoqing. + /// [Test] procedure XpengMatchesGuangzhouAndZhaoqing; + /// + /// Great wall matches all sub brands. + /// [Test] procedure GreatWallMatchesAllSubBrands; + /// + /// Chinese o e ms do not collide with volvo cars. + /// [Test] procedure ChineseOEMsDoNotCollideWithVolvoCars; end; [TestFixture] TChinaCatalogTests = class public + /// + /// B y d exposes blade battery b m s. + /// [Test] procedure BYDExposesBladeBatteryBMS; + /// + /// N i o exposes aquila sensor suite. + /// [Test] procedure NIOExposesAquilaSensorSuite; + /// + /// Xpeng exposes x p i l o t computer. + /// [Test] procedure XpengExposesXPILOTComputer; + /// + /// Great wall exposes hi4 hybrid. + /// [Test] procedure GreatWallExposesHi4Hybrid; + /// + /// Geely exposes evcc for geometry zeekr. + /// [Test] procedure GeelyExposesEvccForGeometryZeekr; end; [TestFixture] TChinaDecoderTests = class public + /// + /// B y d decodes model code. + /// [Test] procedure BYDDecodesModelCode; + /// + /// Geely decodes platform code. + /// [Test] procedure GeelyDecodesPlatformCode; + /// + /// N i o decodes battery swap id. + /// [Test] procedure NIODecodesBatterySwapId; + /// + /// Xpeng decodes x p i l o t version. + /// [Test] procedure XpengDecodesXPILOTVersion; + /// + /// Great wall decodes brand code. + /// [Test] procedure GreatWallDecodesBrandCode; end; @@ -51,6 +99,9 @@ implementation OBD.OEM.BYD, OBD.OEM.Geely, OBD.OEM.NIO, OBD.OEM.Xpeng, OBD.OEM.GreatWall, OBD.OEM.Volvo; +//------------------------------------------------------------------------------ +// MAKE VIN +//------------------------------------------------------------------------------ function MakeVin(const Prefix: string): string; begin Result := (Prefix + '00000000000000'); @@ -60,6 +111,10 @@ function MakeVin(const Prefix: string): string; //============================================================================== // VIN routing //============================================================================== + +//------------------------------------------------------------------------------ +// BYDMATCHES ALL PLANTS +//------------------------------------------------------------------------------ procedure TChinaVINTests.BYDMatchesAllPlants; var Ext: IOBDOEMExtension; @@ -71,6 +126,9 @@ procedure TChinaVINTests.BYDMatchesAllPlants; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('LJN')), 'NIO Hefei'); end; +//------------------------------------------------------------------------------ +// GEELY MATCHES LYNK AND ZEEKR +//------------------------------------------------------------------------------ procedure TChinaVINTests.GeelyMatchesLynkAndZeekr; var Ext: IOBDOEMExtension; @@ -82,6 +140,9 @@ procedure TChinaVINTests.GeelyMatchesLynkAndZeekr; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('LGZ')), 'Zeekr'); end; +//------------------------------------------------------------------------------ +// NIOMATCHES HEFEI +//------------------------------------------------------------------------------ procedure TChinaVINTests.NIOMatchesHefei; var Ext: IOBDOEMExtension; @@ -92,6 +153,9 @@ procedure TChinaVINTests.NIOMatchesHefei; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('LJY')), 'should not claim Xpeng'); end; +//------------------------------------------------------------------------------ +// XPENG MATCHES GUANGZHOU AND ZHAOQING +//------------------------------------------------------------------------------ procedure TChinaVINTests.XpengMatchesGuangzhouAndZhaoqing; var Ext: IOBDOEMExtension; @@ -101,6 +165,9 @@ procedure TChinaVINTests.XpengMatchesGuangzhouAndZhaoqing; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('LMZ'))); end; +//------------------------------------------------------------------------------ +// GREAT WALL MATCHES ALL SUB BRANDS +//------------------------------------------------------------------------------ procedure TChinaVINTests.GreatWallMatchesAllSubBrands; var Ext: IOBDOEMExtension; @@ -112,6 +179,9 @@ procedure TChinaVINTests.GreatWallMatchesAllSubBrands; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('X9X')), 'GWM Russia / export'); end; +//------------------------------------------------------------------------------ +// CHINESE OEMS DO NOT COLLIDE WITH VOLVO CARS +//------------------------------------------------------------------------------ procedure TChinaVINTests.ChineseOEMsDoNotCollideWithVolvoCars; var Volvo, Geely: IOBDOEMExtension; @@ -129,6 +199,10 @@ procedure TChinaVINTests.ChineseOEMsDoNotCollideWithVolvoCars; //============================================================================== // Catalog spot-checks //============================================================================== + +//------------------------------------------------------------------------------ +// BYDEXPOSES BLADE BATTERY BMS +//------------------------------------------------------------------------------ procedure TChinaCatalogTests.BYDExposesBladeBatteryBMS; var Ext: IOBDOEMExtension; @@ -142,6 +216,9 @@ procedure TChinaCatalogTests.BYDExposesBladeBatteryBMS; Assert.IsTrue(Found, 'BYD must expose Blade-battery BMS at 0x782'); end; +//------------------------------------------------------------------------------ +// NIOEXPOSES AQUILA SENSOR SUITE +//------------------------------------------------------------------------------ procedure TChinaCatalogTests.NIOExposesAquilaSensorSuite; var Ext: IOBDOEMExtension; @@ -155,6 +232,9 @@ procedure TChinaCatalogTests.NIOExposesAquilaSensorSuite; Assert.IsTrue(HasAquila, 'NIO must expose the Aquila autonomous-driving sensor suite'); end; +//------------------------------------------------------------------------------ +// XPENG EXPOSES XPILOTCOMPUTER +//------------------------------------------------------------------------------ procedure TChinaCatalogTests.XpengExposesXPILOTComputer; var Ext: IOBDOEMExtension; @@ -168,6 +248,9 @@ procedure TChinaCatalogTests.XpengExposesXPILOTComputer; Assert.IsTrue(HasXpilot, 'Xpeng must expose the XPILOT ADAS computer'); end; +//------------------------------------------------------------------------------ +// GREAT WALL EXPOSES HI4 HYBRID +//------------------------------------------------------------------------------ procedure TChinaCatalogTests.GreatWallExposesHi4Hybrid; var Ext: IOBDOEMExtension; @@ -181,6 +264,9 @@ procedure TChinaCatalogTests.GreatWallExposesHi4Hybrid; Assert.IsTrue(HasHybrid, 'GWM must expose the Hi4 hybrid controller'); end; +//------------------------------------------------------------------------------ +// GEELY EXPOSES EVCC FOR GEOMETRY ZEEKR +//------------------------------------------------------------------------------ procedure TChinaCatalogTests.GeelyExposesEvccForGeometryZeekr; var Ext: IOBDOEMExtension; @@ -197,6 +283,10 @@ procedure TChinaCatalogTests.GeelyExposesEvccForGeometryZeekr; //============================================================================== // Decoder spot-checks //============================================================================== + +//------------------------------------------------------------------------------ +// BYDDECODES MODEL CODE +//------------------------------------------------------------------------------ procedure TChinaDecoderTests.BYDDecodesModelCode; var Ext: IOBDOEMExtension; @@ -207,6 +297,9 @@ procedure TChinaDecoderTests.BYDDecodesModelCode; Assert.IsTrue(Pos('byd_model_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// GEELY DECODES PLATFORM CODE +//------------------------------------------------------------------------------ procedure TChinaDecoderTests.GeelyDecodesPlatformCode; var Ext: IOBDOEMExtension; @@ -217,6 +310,9 @@ procedure TChinaDecoderTests.GeelyDecodesPlatformCode; Assert.IsTrue(Pos('geely_platform_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// NIODECODES BATTERY SWAP ID +//------------------------------------------------------------------------------ procedure TChinaDecoderTests.NIODecodesBatterySwapId; var Ext: IOBDOEMExtension; @@ -227,6 +323,9 @@ procedure TChinaDecoderTests.NIODecodesBatterySwapId; Assert.IsTrue(Pos('nio_battery_swap_id', Output) > 0); end; +//------------------------------------------------------------------------------ +// XPENG DECODES XPILOTVERSION +//------------------------------------------------------------------------------ procedure TChinaDecoderTests.XpengDecodesXPILOTVersion; var Ext: IOBDOEMExtension; @@ -237,6 +336,9 @@ procedure TChinaDecoderTests.XpengDecodesXPILOTVersion; Assert.IsTrue(Pos('xpeng_xpilot_version', Output) > 0); end; +//------------------------------------------------------------------------------ +// GREAT WALL DECODES BRAND CODE +//------------------------------------------------------------------------------ procedure TChinaDecoderTests.GreatWallDecodesBrandCode; var Ext: IOBDOEMExtension; diff --git a/tests/Tests.OEM.Coding.AuditLog.pas b/tests/Tests.OEM.Coding.AuditLog.pas new file mode 100644 index 00000000..1b5d6c7a --- /dev/null +++ b/tests/Tests.OEM.Coding.AuditLog.pas @@ -0,0 +1,261 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.OEM.Coding.AuditLog.pas +// CONTENTS : Tests for OBD.OEM.Coding.AuditLog +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.OEM.Coding.AuditLog; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TCodingAuditLogTests = class + strict private + FPath: string; + FKey: TBytes; + public + [Setup] procedure Setup; + [TearDown] procedure TearDown; + + /// + /// Append creates verifiable single record. + /// + [Test] procedure AppendCreatesVerifiableSingleRecord; + /// + /// Append chains across multiple records. + /// + [Test] procedure AppendChainsAcrossMultipleRecords; + /// + /// Tampering byte flip flags correct line. + /// + [Test] procedure TamperingByteFlipFlagsCorrectLine; + /// + /// Tampering delete flags the next line. + /// + [Test] procedure TamperingDeleteFlagsTheNextLine; + /// + /// Restart from existing file continues chain. + /// + [Test] procedure RestartFromExistingFileContinuesChain; + /// + /// Empty key at construction raises. + /// + [Test] procedure EmptyKeyAtConstructionRaises; + end; + +implementation + +uses + System.SysUtils, System.Classes, System.IOUtils, + OBD.OEM.Coding.AuditLog; + +//------------------------------------------------------------------------------ +// SETUP +//------------------------------------------------------------------------------ +procedure TCodingAuditLogTests.Setup; +begin + FPath := TPath.Combine(TPath.GetTempPath, + 'obd-audit-' + TGUID.NewGuid.ToString + '.log'); + FKey := TEncoding.UTF8.GetBytes('test-key-32-bytes-long-padding-x'); +end; + +//------------------------------------------------------------------------------ +// TEAR DOWN +//------------------------------------------------------------------------------ +procedure TCodingAuditLogTests.TearDown; +begin + if TFile.Exists(FPath) then + TFile.Delete(FPath); +end; + +//------------------------------------------------------------------------------ +// MAKE REC +//------------------------------------------------------------------------------ +function MakeRec(const VIN: string; const Index: Integer): TOBDCodingAuditRecord; +begin + Result := Default(TOBDCodingAuditRecord); + Result.Timestamp := EncodeDate(2026, 5, 9) + EncodeTime(12, 0, Index, 0); + Result.VIN := VIN; + Result.ECU := 'BCM'; + Result.Block := 'LongCoding'; + Result.BeforeHex := '0102030405'; + Result.AfterHex := '01020304FF'; + Result.Operator := 'tester'; + Result.Reason := 'test#' + IntToStr(Index); +end; + +//------------------------------------------------------------------------------ +// APPEND CREATES VERIFIABLE SINGLE RECORD +//------------------------------------------------------------------------------ +procedure TCodingAuditLogTests.AppendCreatesVerifiableSingleRecord; +var + Log: TOBDCodingAuditLog; + Res: TOBDCodingAuditChainResult; +begin + Log := TOBDCodingAuditLog.Create(FPath, FKey); + try + Log.Append(MakeRec('VIN-001', 1)); + Res := Log.Verify; + Assert.IsTrue(Res.Verified, Res.Reason); + Assert.AreEqual(1, Res.TotalRecords); + finally + Log.Free; + end; +end; + +//------------------------------------------------------------------------------ +// APPEND CHAINS ACROSS MULTIPLE RECORDS +//------------------------------------------------------------------------------ +procedure TCodingAuditLogTests.AppendChainsAcrossMultipleRecords; +var + Log: TOBDCodingAuditLog; + Res: TOBDCodingAuditChainResult; + I: Integer; +begin + Log := TOBDCodingAuditLog.Create(FPath, FKey); + try + for I := 1 to 5 do + Log.Append(MakeRec('VIN-00' + IntToStr(I), I)); + Res := Log.Verify; + Assert.IsTrue(Res.Verified, Res.Reason); + Assert.AreEqual(5, Res.TotalRecords); + finally + Log.Free; + end; +end; + +//------------------------------------------------------------------------------ +// TAMPERING BYTE FLIP FLAGS CORRECT LINE +//------------------------------------------------------------------------------ +procedure TCodingAuditLogTests.TamperingByteFlipFlagsCorrectLine; +var + Log: TOBDCodingAuditLog; + Lines: TStringList; + Res: TOBDCodingAuditChainResult; + I: Integer; +begin + Log := TOBDCodingAuditLog.Create(FPath, FKey); + try + for I := 1 to 3 do + Log.Append(MakeRec('VIN-' + IntToStr(I), I)); + finally + Log.Free; + end; + + Lines := TStringList.Create; + try + Lines.LoadFromFile(FPath, TEncoding.UTF8); + // Flip a digit in line 2's "after" field; the JSON stays valid but + // the body no longer matches the stored hmac. + Lines[1] := StringReplace(Lines[1], '"01020304FF"', + '"01020304FE"', [rfReplaceAll]); + Lines.SaveToFile(FPath, TEncoding.UTF8); + finally + Lines.Free; + end; + + Log := TOBDCodingAuditLog.Create(FPath, FKey); + try + Res := Log.Verify; + Assert.IsFalse(Res.Verified); + Assert.AreEqual(2, Res.FirstTamperLine); + finally + Log.Free; + end; +end; + +//------------------------------------------------------------------------------ +// TAMPERING DELETE FLAGS THE NEXT LINE +//------------------------------------------------------------------------------ +procedure TCodingAuditLogTests.TamperingDeleteFlagsTheNextLine; +var + Log: TOBDCodingAuditLog; + Lines: TStringList; + Res: TOBDCodingAuditChainResult; + I: Integer; +begin + Log := TOBDCodingAuditLog.Create(FPath, FKey); + try + for I := 1 to 3 do + Log.Append(MakeRec('VIN-' + IntToStr(I), I)); + finally + Log.Free; + end; + + Lines := TStringList.Create; + try + Lines.LoadFromFile(FPath, TEncoding.UTF8); + Lines.Delete(1); // remove the second record + Lines.SaveToFile(FPath, TEncoding.UTF8); + finally + Lines.Free; + end; + + Log := TOBDCodingAuditLog.Create(FPath, FKey); + try + Res := Log.Verify; + // After deletion, line 2 (formerly line 3) chains against the wrong + // previous hmac. + Assert.IsFalse(Res.Verified); + Assert.AreEqual(2, Res.FirstTamperLine); + finally + Log.Free; + end; +end; + +//------------------------------------------------------------------------------ +// RESTART FROM EXISTING FILE CONTINUES CHAIN +//------------------------------------------------------------------------------ +procedure TCodingAuditLogTests.RestartFromExistingFileContinuesChain; +var + Log: TOBDCodingAuditLog; + Res: TOBDCodingAuditChainResult; +begin + Log := TOBDCodingAuditLog.Create(FPath, FKey); + try + Log.Append(MakeRec('VIN-A', 1)); + Log.Append(MakeRec('VIN-B', 2)); + finally + Log.Free; + end; + // New instance reads the last hmac and chains the next record onto it. + Log := TOBDCodingAuditLog.Create(FPath, FKey); + try + Log.Append(MakeRec('VIN-C', 3)); + Res := Log.Verify; + Assert.IsTrue(Res.Verified, Res.Reason); + Assert.AreEqual(3, Res.TotalRecords); + finally + Log.Free; + end; +end; + +//------------------------------------------------------------------------------ +// EMPTY KEY AT CONSTRUCTION RAISES +//------------------------------------------------------------------------------ +procedure TCodingAuditLogTests.EmptyKeyAtConstructionRaises; +begin + Assert.WillRaise( + procedure + var + Log: TOBDCodingAuditLog; + begin + Log := TOBDCodingAuditLog.Create(FPath, nil); + Log.Free; + end, + EOBDCodingAuditLog); +end; + +initialization + TDUnitX.RegisterTestFixture(TCodingAuditLogTests); + +end. diff --git a/tests/Tests.OEM.Coding.Diff.pas b/tests/Tests.OEM.Coding.Diff.pas new file mode 100644 index 00000000..c6d20204 --- /dev/null +++ b/tests/Tests.OEM.Coding.Diff.pas @@ -0,0 +1,243 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.OEM.Coding.Diff.pas +// CONTENTS : Tests for OBD.OEM.Coding.Diff +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.OEM.Coding.Diff; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TCodingDiffTests = class + public + /// + /// No op when current equals target. + /// + [Test] procedure NoOpWhenCurrentEqualsTarget; + /// + /// Byte level diff spots changed bytes. + /// + [Test] procedure ByteLevelDiffSpotsChangedBytes; + /// + /// Field schema produces named diff. + /// + [Test] procedure FieldSchemaProducesNamedDiff; + /// + /// Apply without confirm raises. + /// + [Test] procedure ApplyWithoutConfirmRaises; + /// + /// Apply with confirm invokes writer. + /// + [Test] procedure ApplyWithConfirmInvokesWriter; + /// + /// No op apply does not invoke writer. + /// + [Test] procedure NoOpApplyDoesNotInvokeWriter; + /// + /// Mismatched length raises. + /// + [Test] procedure MismatchedLengthRaises; + /// + /// U int16 field diffs correctly. + /// + [Test] procedure UInt16FieldDiffsCorrectly; + end; + +implementation + +uses + System.SysUtils, OBD.OEM.Coding, OBD.OEM.Coding.Diff; + +//------------------------------------------------------------------------------ +// NO OP WHEN CURRENT EQUALS TARGET +//------------------------------------------------------------------------------ +procedure TCodingDiffTests.NoOpWhenCurrentEqualsTarget; +var + Bytes: TBytes; + Plan: TOBDCodingPlan; +begin + Bytes := TBytes.Create($01, $02, $03, $04); + Plan := TOBDCodingPlan.Create(Bytes, Bytes); + try + Assert.IsTrue(Plan.IsNoOp); + Assert.AreEqual(0, Length(Plan.Diff)); + finally + Plan.Free; + end; +end; + +//------------------------------------------------------------------------------ +// BYTE LEVEL DIFF SPOTS CHANGED BYTES +//------------------------------------------------------------------------------ +procedure TCodingDiffTests.ByteLevelDiffSpotsChangedBytes; +var + A, B: TBytes; + Plan: TOBDCodingPlan; +begin + A := TBytes.Create($00, $00, $00, $00); + B := TBytes.Create($00, $FF, $00, $7F); + Plan := TOBDCodingPlan.Create(A, B); + try + Assert.AreEqual(2, Length(Plan.Diff)); + Assert.AreEqual(1, Plan.Diff[0].ByteIndex); + Assert.AreEqual(UInt32($FF), Plan.Diff[0].AfterValue); + Assert.AreEqual(3, Plan.Diff[1].ByteIndex); + Assert.AreEqual(UInt32($7F), Plan.Diff[1].AfterValue); + finally + Plan.Free; + end; +end; + +//------------------------------------------------------------------------------ +// FIELD SCHEMA PRODUCES NAMED DIFF +//------------------------------------------------------------------------------ +procedure TCodingDiffTests.FieldSchemaProducesNamedDiff; +var + A, B: TBytes; + Schema: TOBDCodingSchema; + Plan: TOBDCodingPlan; +begin + A := TBytes.Create($00, $00); + B := TBytes.Create($00, $08); // bit 3 of byte 1 flipped + SetLength(Schema, 1); + Schema[0].Name := 'CornerLights'; + Schema[0].Description := 'Enable corner lighting on low beam'; + Schema[0].Kind := cfkBit; + Schema[0].ByteIndex := 1; + Schema[0].BitIndex := 3; + Plan := TOBDCodingPlan.Create(A, B, Schema); + try + Assert.AreEqual(1, Length(Plan.Diff)); + Assert.AreEqual('CornerLights', Plan.Diff[0].FieldName); + Assert.AreEqual(UInt32(0), Plan.Diff[0].BeforeValue); + Assert.AreEqual(UInt32(1), Plan.Diff[0].AfterValue); + Assert.IsTrue(Plan.AsText.Contains('CornerLights')); + finally + Plan.Free; + end; +end; + +//------------------------------------------------------------------------------ +// APPLY WITHOUT CONFIRM RAISES +//------------------------------------------------------------------------------ +procedure TCodingDiffTests.ApplyWithoutConfirmRaises; +var + Plan: TOBDCodingPlan; +begin + Plan := TOBDCodingPlan.Create( + TBytes.Create($00), TBytes.Create($01)); + try + Assert.WillRaise( + procedure begin Plan.Apply(False, procedure(const B: TBytes) begin end); end, + EOBDCodingDiffError); + Assert.IsFalse(Plan.Applied); + finally + Plan.Free; + end; +end; + +//------------------------------------------------------------------------------ +// APPLY WITH CONFIRM INVOKES WRITER +//------------------------------------------------------------------------------ +procedure TCodingDiffTests.ApplyWithConfirmInvokesWriter; +var + Plan: TOBDCodingPlan; + Captured: TBytes; + Called: Boolean; +begin + Called := False; + Plan := TOBDCodingPlan.Create( + TBytes.Create($00, $00), TBytes.Create($AA, $BB)); + try + Plan.Apply(True, + procedure(const Bytes: TBytes) + begin + Called := True; + Captured := Copy(Bytes); + end); + Assert.IsTrue(Called); + Assert.IsTrue(Plan.Applied); + Assert.AreEqual($AA, Integer(Captured[0])); + Assert.AreEqual($BB, Integer(Captured[1])); + finally + Plan.Free; + end; +end; + +//------------------------------------------------------------------------------ +// NO OP APPLY DOES NOT INVOKE WRITER +//------------------------------------------------------------------------------ +procedure TCodingDiffTests.NoOpApplyDoesNotInvokeWriter; +var + Plan: TOBDCodingPlan; + Called: Boolean; +begin + Called := False; + Plan := TOBDCodingPlan.Create(TBytes.Create($00), TBytes.Create($00)); + try + Plan.Apply(True, + procedure(const Bytes: TBytes) begin Called := True; end); + Assert.IsFalse(Called, 'Writer must not run for a no-op plan'); + Assert.IsTrue(Plan.Applied); + finally + Plan.Free; + end; +end; + +//------------------------------------------------------------------------------ +// MISMATCHED LENGTH RAISES +//------------------------------------------------------------------------------ +procedure TCodingDiffTests.MismatchedLengthRaises; +begin + Assert.WillRaise( + procedure + var + P: TOBDCodingPlan; + begin + P := TOBDCodingPlan.Create(TBytes.Create($00), + TBytes.Create($00, $00)); + P.Free; + end, + EOBDCodingDiffError); +end; + +//------------------------------------------------------------------------------ +// UINT16 FIELD DIFFS CORRECTLY +//------------------------------------------------------------------------------ +procedure TCodingDiffTests.UInt16FieldDiffsCorrectly; +var + A, B: TBytes; + Schema: TOBDCodingSchema; + Plan: TOBDCodingPlan; +begin + A := TBytes.Create($01, $02, $00, $00); // field@0 = 0x0102 + B := TBytes.Create($AB, $CD, $00, $00); // field@0 = 0xABCD + SetLength(Schema, 1); + Schema[0].Name := 'TopSpeedLimit'; + Schema[0].Kind := cfkUInt16; + Schema[0].ByteIndex := 0; + Plan := TOBDCodingPlan.Create(A, B, Schema); + try + Assert.AreEqual(1, Length(Plan.Diff)); + Assert.AreEqual(UInt32($0102), Plan.Diff[0].BeforeValue); + Assert.AreEqual(UInt32($ABCD), Plan.Diff[0].AfterValue); + finally + Plan.Free; + end; +end; + +initialization + TDUnitX.RegisterTestFixture(TCodingDiffTests); + +end. diff --git a/tests/Tests.OEM.Coding.NewOEMs.pas b/tests/Tests.OEM.Coding.NewOEMs.pas new file mode 100644 index 00000000..cd11da8c --- /dev/null +++ b/tests/Tests.OEM.Coding.NewOEMs.pas @@ -0,0 +1,187 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.OEM.Coding.NewOEMs.pas +// CONTENTS : Tests for Toyota/Honda/HMG/Stellantis coding wrappers +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.OEM.Coding.NewOEMs; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TNewOEMCodingTests = class + public + /// + /// Toyota hex round trip. + /// + [Test] procedure Toyota_HexRoundTrip; + /// + /// Toyota bit flip persists. + /// + [Test] procedure Toyota_BitFlipPersists; + /// + /// Honda hex round trip. + /// + [Test] procedure Honda_HexRoundTrip; + /// + /// H m g out of range byte raises. + /// + [Test] procedure HMG_OutOfRangeByteRaises; + /// + /// Stellantis bit and byte access. + /// + [Test] procedure Stellantis_BitAndByteAccess; + /// + /// Stellantis compute checksum raises for gap. + /// + [Test] procedure Stellantis_ComputeChecksumRaisesForGap; + /// + /// Stellantis set checksum writes two bytes. + /// + [Test] procedure Stellantis_SetChecksumWritesTwoBytes; + /// + /// Zero length construction raises. + /// + [Test] procedure ZeroLengthConstructionRaises; + end; + +implementation + +uses + System.SysUtils, + OBD.OEM.Coding, + OBD.OEM.Coding.Toyota, + OBD.OEM.Coding.Honda, + OBD.OEM.Coding.HMG, + OBD.OEM.Coding.Stellantis; + +//------------------------------------------------------------------------------ +// TOYOTA_HEX ROUND TRIP +//------------------------------------------------------------------------------ +procedure TNewOEMCodingTests.Toyota_HexRoundTrip; +var + C: TOBDToyotaCustomize; +begin + C := TOBDToyotaCustomize.CreateFromHex('0102030405'); + try + Assert.AreEqual('0102030405', C.ToHex); + Assert.AreEqual(5, C.ByteCount); + finally C.Free; end; +end; + +//------------------------------------------------------------------------------ +// TOYOTA_BIT FLIP PERSISTS +//------------------------------------------------------------------------------ +procedure TNewOEMCodingTests.Toyota_BitFlipPersists; +var + C: TOBDToyotaCustomize; +begin + C := TOBDToyotaCustomize.Create(1); + try + Assert.IsFalse(C.GetBit(0, 3)); + C.SetBit(0, 3, True); + Assert.IsTrue(C.GetBit(0, 3)); + Assert.AreEqual($08, Integer(C.GetByte(0))); + finally C.Free; end; +end; + +//------------------------------------------------------------------------------ +// HONDA_HEX ROUND TRIP +//------------------------------------------------------------------------------ +procedure TNewOEMCodingTests.Honda_HexRoundTrip; +var + H: TOBDHondaOptionByte; +begin + H := TOBDHondaOptionByte.CreateFromHex('AABBCC'); + try + Assert.AreEqual('AABBCC', H.ToHex); + Assert.AreEqual($BB, Integer(H.GetByte(1))); + finally H.Free; end; +end; + +//------------------------------------------------------------------------------ +// HMG_OUT OF RANGE BYTE RAISES +//------------------------------------------------------------------------------ +procedure TNewOEMCodingTests.HMG_OutOfRangeByteRaises; +var + V: TOBDHMGVariantCoding; +begin + V := TOBDHMGVariantCoding.Create(2); + try + Assert.WillRaise( + procedure begin V.SetByte(99, $FF); end, + EOBDCodingError); + finally V.Free; end; +end; + +//------------------------------------------------------------------------------ +// STELLANTIS_BIT AND BYTE ACCESS +//------------------------------------------------------------------------------ +procedure TNewOEMCodingTests.Stellantis_BitAndByteAccess; +var + P: TOBDStellantisProxi; +begin + P := TOBDStellantisProxi.Create(4); + try + P.SetByte(2, $80); + Assert.IsTrue(P.GetBit(2, 7)); + Assert.IsFalse(P.GetBit(2, 6)); + finally P.Free; end; +end; + +//------------------------------------------------------------------------------ +// STELLANTIS_COMPUTE CHECKSUM RAISES FOR GAP +//------------------------------------------------------------------------------ +procedure TNewOEMCodingTests.Stellantis_ComputeChecksumRaisesForGap; +var + P: TOBDStellantisProxi; +begin + P := TOBDStellantisProxi.Create(4); + try + Assert.WillRaise( + procedure begin P.ComputeChecksum; end, + EOBDStellantisProxi); + finally P.Free; end; +end; + +//------------------------------------------------------------------------------ +// STELLANTIS_SET CHECKSUM WRITES TWO BYTES +//------------------------------------------------------------------------------ +procedure TNewOEMCodingTests.Stellantis_SetChecksumWritesTwoBytes; +var + P: TOBDStellantisProxi; +begin + P := TOBDStellantisProxi.Create(4); + try + P.SetChecksum($ABCD, 2); + Assert.AreEqual($AB, Integer(P.GetByte(2))); + Assert.AreEqual($CD, Integer(P.GetByte(3))); + finally P.Free; end; +end; + +//------------------------------------------------------------------------------ +// ZERO LENGTH CONSTRUCTION RAISES +//------------------------------------------------------------------------------ +procedure TNewOEMCodingTests.ZeroLengthConstructionRaises; +begin + Assert.WillRaise( + procedure + var + C: TOBDToyotaCustomize; + begin C := TOBDToyotaCustomize.Create(0); C.Free; end, + EOBDCodingError); +end; + +initialization + TDUnitX.RegisterTestFixture(TNewOEMCodingTests); + +end. diff --git a/tests/Tests.OEM.Coding.pas b/tests/Tests.OEM.Coding.pas index 8b416bb1..3a2194eb 100644 --- a/tests/Tests.OEM.Coding.pas +++ b/tests/Tests.OEM.Coding.pas @@ -13,69 +13,186 @@ interface [TestFixture] TCodingHexTests = class public + /// + /// Hex string round trip. + /// [Test] procedure HexStringRoundTrip; + /// + /// Hex string strips whitespace and separators. + /// [Test] procedure HexStringStripsWhitespaceAndSeparators; + /// + /// Hex string rejects odd length. + /// [Test] procedure HexStringRejectsOddLength; + /// + /// Hex string rejects bad character. + /// [Test] procedure HexStringRejectsBadCharacter; + /// + /// Bytes to hex uses upper case. + /// [Test] procedure BytesToHexUsesUpperCase; + /// + /// Bytes to hex with separator. + /// [Test] procedure BytesToHexWithSeparator; + /// + /// Bit ops read and write. + /// [Test] procedure BitOpsReadAndWrite; + /// + /// Bit ops reject out of range. + /// [Test] procedure BitOpsRejectOutOfRange; end; [TestFixture] TVWLongCodingTests = class public + /// + /// Construct from hex preserves bytes. + /// [Test] procedure ConstructFromHexPreservesBytes; + /// + /// Set byte and read back. + /// [Test] procedure SetByteAndReadBack; + /// + /// Set bit flips the right position. + /// [Test] procedure SetBitFlipsTheRightPosition; + /// + /// Has non zero byte detects all zeros. + /// [Test] procedure HasNonZeroByteDetectsAllZeros; + /// + /// To hex round trips constructor. + /// [Test] procedure ToHexRoundTripsConstructor; + /// + /// To bytes is an independent copy. + /// [Test] procedure ToBytesIsAnIndependentCopy; + /// + /// Set byte out of range raises. + /// [Test] procedure SetByteOutOfRangeRaises; end; [TestFixture] TBMWFATests = class public + /// + /// Parses comma separated. + /// [Test] procedure ParsesCommaSeparated; + /// + /// Deduplicates on add. + /// [Test] procedure DeduplicatesOnAdd; + /// + /// Normalises to upper case. + /// [Test] procedure NormalisesToUpperCase; + /// + /// Remove option works. + /// [Test] procedure RemoveOptionWorks; + /// + /// To string sorts ascending. + /// [Test] procedure ToStringSortsAscending; + /// + /// Has option is case insensitive. + /// [Test] procedure HasOptionIsCaseInsensitive; + /// + /// Rejects empty code. + /// [Test] procedure RejectsEmptyCode; end; [TestFixture] TBMWIStufeTests = class public + /// + /// Parse round trip. + /// [Test] procedure ParseRoundTrip; + /// + /// Parse rejects wrong part count. + /// [Test] procedure ParseRejectsWrongPartCount; + /// + /// Parse rejects bad month. + /// [Test] procedure ParseRejectsBadMonth; + /// + /// Compare orders by year month build. + /// [Test] procedure CompareOrdersByYearMonthBuild; + /// + /// At least different project is false. + /// [Test] procedure AtLeastDifferentProjectIsFalse; + /// + /// To string pads zeros. + /// [Test] procedure ToStringPadsZeros; end; [TestFixture] TMercedesSCNTests = class public + /// + /// Parses three segments. + /// [Test] procedure ParsesThreeSegments; + /// + /// Rejects two segments. + /// [Test] procedure RejectsTwoSegments; + /// + /// Rejects illegal character. + /// [Test] procedure RejectsIllegalCharacter; + /// + /// Normalizes to upper. + /// [Test] procedure NormalizesToUpper; + /// + /// To string round trips. + /// [Test] procedure ToStringRoundTrips; end; [TestFixture] TFordAsBuiltTests = class public + /// + /// Checksum matches spec. + /// [Test] procedure ChecksumMatchesSpec; + /// + /// Parse line extracts fields. + /// [Test] procedure ParseLineExtractsFields; + /// + /// Parse rejects missing checksum. + /// [Test] procedure ParseRejectsMissingChecksum; + /// + /// Reseal recomputes checksum. + /// [Test] procedure ResealRecomputesChecksum; + /// + /// Parse text skips comments and blanks. + /// [Test] procedure ParseTextSkipsCommentsAndBlanks; + /// + /// To string round trips. + /// [Test] procedure ToStringRoundTrips; end; @@ -89,6 +206,10 @@ implementation //============================================================================== // Hex + bit helpers //============================================================================== + +//------------------------------------------------------------------------------ +// HEX STRING ROUND TRIP +//------------------------------------------------------------------------------ procedure TCodingHexTests.HexStringRoundTrip; var B: TBytes; @@ -99,6 +220,9 @@ procedure TCodingHexTests.HexStringRoundTrip; Assert.AreEqual(Byte($00), B[3]); end; +//------------------------------------------------------------------------------ +// HEX STRING STRIPS WHITESPACE AND SEPARATORS +//------------------------------------------------------------------------------ procedure TCodingHexTests.HexStringStripsWhitespaceAndSeparators; var B: TBytes; @@ -108,6 +232,9 @@ procedure TCodingHexTests.HexStringStripsWhitespaceAndSeparators; Assert.AreEqual(Byte($30), B[4]); end; +//------------------------------------------------------------------------------ +// HEX STRING REJECTS ODD LENGTH +//------------------------------------------------------------------------------ procedure TCodingHexTests.HexStringRejectsOddLength; begin Assert.WillRaise( @@ -115,6 +242,9 @@ procedure TCodingHexTests.HexStringRejectsOddLength; EOBDCodingError); end; +//------------------------------------------------------------------------------ +// HEX STRING REJECTS BAD CHARACTER +//------------------------------------------------------------------------------ procedure TCodingHexTests.HexStringRejectsBadCharacter; begin Assert.WillRaise( @@ -122,17 +252,26 @@ procedure TCodingHexTests.HexStringRejectsBadCharacter; EOBDCodingError); end; +//------------------------------------------------------------------------------ +// BYTES TO HEX USES UPPER CASE +//------------------------------------------------------------------------------ procedure TCodingHexTests.BytesToHexUsesUpperCase; begin Assert.AreEqual('AB12', BytesToHexString(TBytes.Create($AB, $12))); end; +//------------------------------------------------------------------------------ +// BYTES TO HEX WITH SEPARATOR +//------------------------------------------------------------------------------ procedure TCodingHexTests.BytesToHexWithSeparator; begin Assert.AreEqual('AB 12 CD', BytesToHexString(TBytes.Create($AB, $12, $CD), ' ')); end; +//------------------------------------------------------------------------------ +// BIT OPS READ AND WRITE +//------------------------------------------------------------------------------ procedure TCodingHexTests.BitOpsReadAndWrite; var B: TBytes; @@ -146,6 +285,9 @@ procedure TCodingHexTests.BitOpsReadAndWrite; Assert.AreEqual(Byte($00), B[0]); end; +//------------------------------------------------------------------------------ +// BIT OPS REJECT OUT OF RANGE +//------------------------------------------------------------------------------ procedure TCodingHexTests.BitOpsRejectOutOfRange; var B: TBytes; @@ -160,6 +302,10 @@ procedure TCodingHexTests.BitOpsRejectOutOfRange; //============================================================================== // VW Long Coding //============================================================================== + +//------------------------------------------------------------------------------ +// CONSTRUCT FROM HEX PRESERVES BYTES +//------------------------------------------------------------------------------ procedure TVWLongCodingTests.ConstructFromHexPreservesBytes; var LC: TOBDVWLongCoding; @@ -174,6 +320,9 @@ procedure TVWLongCodingTests.ConstructFromHexPreservesBytes; end; end; +//------------------------------------------------------------------------------ +// SET BYTE AND READ BACK +//------------------------------------------------------------------------------ procedure TVWLongCodingTests.SetByteAndReadBack; var LC: TOBDVWLongCoding; @@ -187,6 +336,9 @@ procedure TVWLongCodingTests.SetByteAndReadBack; end; end; +//------------------------------------------------------------------------------ +// SET BIT FLIPS THE RIGHT POSITION +//------------------------------------------------------------------------------ procedure TVWLongCodingTests.SetBitFlipsTheRightPosition; var LC: TOBDVWLongCoding; @@ -202,6 +354,9 @@ procedure TVWLongCodingTests.SetBitFlipsTheRightPosition; end; end; +//------------------------------------------------------------------------------ +// HAS NON ZERO BYTE DETECTS ALL ZEROS +//------------------------------------------------------------------------------ procedure TVWLongCodingTests.HasNonZeroByteDetectsAllZeros; var LC: TOBDVWLongCoding; @@ -216,6 +371,9 @@ procedure TVWLongCodingTests.HasNonZeroByteDetectsAllZeros; end; end; +//------------------------------------------------------------------------------ +// TO HEX ROUND TRIPS CONSTRUCTOR +//------------------------------------------------------------------------------ procedure TVWLongCodingTests.ToHexRoundTripsConstructor; var LC: TOBDVWLongCoding; @@ -228,6 +386,9 @@ procedure TVWLongCodingTests.ToHexRoundTripsConstructor; end; end; +//------------------------------------------------------------------------------ +// TO BYTES IS AN INDEPENDENT COPY +//------------------------------------------------------------------------------ procedure TVWLongCodingTests.ToBytesIsAnIndependentCopy; var LC: TOBDVWLongCoding; @@ -244,6 +405,9 @@ procedure TVWLongCodingTests.ToBytesIsAnIndependentCopy; end; end; +//------------------------------------------------------------------------------ +// SET BYTE OUT OF RANGE RAISES +//------------------------------------------------------------------------------ procedure TVWLongCodingTests.SetByteOutOfRangeRaises; var LC: TOBDVWLongCoding; @@ -261,6 +425,10 @@ procedure TVWLongCodingTests.SetByteOutOfRangeRaises; //============================================================================== // BMW FA //============================================================================== + +//------------------------------------------------------------------------------ +// PARSES COMMA SEPARATED +//------------------------------------------------------------------------------ procedure TBMWFATests.ParsesCommaSeparated; var FA: TOBDBMWFA; @@ -275,6 +443,9 @@ procedure TBMWFATests.ParsesCommaSeparated; end; end; +//------------------------------------------------------------------------------ +// DEDUPLICATES ON ADD +//------------------------------------------------------------------------------ procedure TBMWFATests.DeduplicatesOnAdd; var FA: TOBDBMWFA; @@ -289,6 +460,9 @@ procedure TBMWFATests.DeduplicatesOnAdd; end; end; +//------------------------------------------------------------------------------ +// NORMALISES TO UPPER CASE +//------------------------------------------------------------------------------ procedure TBMWFATests.NormalisesToUpperCase; var FA: TOBDBMWFA; @@ -302,6 +476,9 @@ procedure TBMWFATests.NormalisesToUpperCase; end; end; +//------------------------------------------------------------------------------ +// REMOVE OPTION WORKS +//------------------------------------------------------------------------------ procedure TBMWFATests.RemoveOptionWorks; var FA: TOBDBMWFA; @@ -316,6 +493,9 @@ procedure TBMWFATests.RemoveOptionWorks; end; end; +//------------------------------------------------------------------------------ +// TO STRING SORTS ASCENDING +//------------------------------------------------------------------------------ procedure TBMWFATests.ToStringSortsAscending; var FA: TOBDBMWFA; @@ -328,6 +508,9 @@ procedure TBMWFATests.ToStringSortsAscending; end; end; +//------------------------------------------------------------------------------ +// HAS OPTION IS CASE INSENSITIVE +//------------------------------------------------------------------------------ procedure TBMWFATests.HasOptionIsCaseInsensitive; var FA: TOBDBMWFA; @@ -340,6 +523,9 @@ procedure TBMWFATests.HasOptionIsCaseInsensitive; end; end; +//------------------------------------------------------------------------------ +// REJECTS EMPTY CODE +//------------------------------------------------------------------------------ procedure TBMWFATests.RejectsEmptyCode; var FA: TOBDBMWFA; @@ -357,6 +543,10 @@ procedure TBMWFATests.RejectsEmptyCode; //============================================================================== // BMW I-Stufe //============================================================================== + +//------------------------------------------------------------------------------ +// PARSE ROUND TRIP +//------------------------------------------------------------------------------ procedure TBMWIStufeTests.ParseRoundTrip; var S: TOBDBMWIStufe; @@ -369,6 +559,9 @@ procedure TBMWIStufeTests.ParseRoundTrip; Assert.AreEqual('F020-21-03-630', S.ToString); end; +//------------------------------------------------------------------------------ +// PARSE REJECTS WRONG PART COUNT +//------------------------------------------------------------------------------ procedure TBMWIStufeTests.ParseRejectsWrongPartCount; begin Assert.WillRaise( @@ -376,6 +569,9 @@ procedure TBMWIStufeTests.ParseRejectsWrongPartCount; EOBDCodingError); end; +//------------------------------------------------------------------------------ +// PARSE REJECTS BAD MONTH +//------------------------------------------------------------------------------ procedure TBMWIStufeTests.ParseRejectsBadMonth; begin Assert.WillRaise( @@ -383,6 +579,9 @@ procedure TBMWIStufeTests.ParseRejectsBadMonth; EOBDCodingError); end; +//------------------------------------------------------------------------------ +// COMPARE ORDERS BY YEAR MONTH BUILD +//------------------------------------------------------------------------------ procedure TBMWIStufeTests.CompareOrdersByYearMonthBuild; var A, B, C: TOBDBMWIStufe; @@ -395,6 +594,9 @@ procedure TBMWIStufeTests.CompareOrdersByYearMonthBuild; Assert.AreEqual(0, A.CompareTo(A)); end; +//------------------------------------------------------------------------------ +// AT LEAST DIFFERENT PROJECT IS FALSE +//------------------------------------------------------------------------------ procedure TBMWIStufeTests.AtLeastDifferentProjectIsFalse; var A, B: TOBDBMWIStufe; @@ -405,6 +607,9 @@ procedure TBMWIStufeTests.AtLeastDifferentProjectIsFalse; Assert.IsFalse(B.AtLeast(A)); end; +//------------------------------------------------------------------------------ +// TO STRING PADS ZEROS +//------------------------------------------------------------------------------ procedure TBMWIStufeTests.ToStringPadsZeros; var S: TOBDBMWIStufe; @@ -416,6 +621,10 @@ procedure TBMWIStufeTests.ToStringPadsZeros; //============================================================================== // Mercedes SCN //============================================================================== + +//------------------------------------------------------------------------------ +// PARSES THREE SEGMENTS +//------------------------------------------------------------------------------ procedure TMercedesSCNTests.ParsesThreeSegments; var SCN: TOBDMercedesSCN; @@ -426,6 +635,9 @@ procedure TMercedesSCNTests.ParsesThreeSegments; Assert.AreEqual('A1B2C3', SCN.BuildSegment); end; +//------------------------------------------------------------------------------ +// REJECTS TWO SEGMENTS +//------------------------------------------------------------------------------ procedure TMercedesSCNTests.RejectsTwoSegments; begin Assert.WillRaise( @@ -433,6 +645,9 @@ procedure TMercedesSCNTests.RejectsTwoSegments; EOBDCodingError); end; +//------------------------------------------------------------------------------ +// REJECTS ILLEGAL CHARACTER +//------------------------------------------------------------------------------ procedure TMercedesSCNTests.RejectsIllegalCharacter; begin Assert.WillRaise( @@ -440,6 +655,9 @@ procedure TMercedesSCNTests.RejectsIllegalCharacter; EOBDCodingError); end; +//------------------------------------------------------------------------------ +// NORMALIZES TO UPPER +//------------------------------------------------------------------------------ procedure TMercedesSCNTests.NormalizesToUpper; var SCN: TOBDMercedesSCN; @@ -448,6 +666,9 @@ procedure TMercedesSCNTests.NormalizesToUpper; Assert.AreEqual('A1B2C3', SCN.BuildSegment); end; +//------------------------------------------------------------------------------ +// TO STRING ROUND TRIPS +//------------------------------------------------------------------------------ procedure TMercedesSCNTests.ToStringRoundTrips; var SCN: TOBDMercedesSCN; @@ -459,6 +680,10 @@ procedure TMercedesSCNTests.ToStringRoundTrips; //============================================================================== // Ford AsBuilt //============================================================================== + +//------------------------------------------------------------------------------ +// CHECKSUM MATCHES SPEC +//------------------------------------------------------------------------------ procedure TFordAsBuiltTests.ChecksumMatchesSpec; var Block: TOBDFordAsBuiltBlock; @@ -471,6 +696,9 @@ procedure TFordAsBuiltTests.ChecksumMatchesSpec; Assert.AreEqual(Byte($77), Block.ComputeChecksum); end; +//------------------------------------------------------------------------------ +// PARSE LINE EXTRACTS FIELDS +//------------------------------------------------------------------------------ procedure TFordAsBuiltTests.ParseLineExtractsFields; var Block: TOBDFordAsBuiltBlock; @@ -481,6 +709,9 @@ procedure TFordAsBuiltTests.ParseLineExtractsFields; Assert.IsTrue(Block.IsValid); end; +//------------------------------------------------------------------------------ +// PARSE REJECTS MISSING CHECKSUM +//------------------------------------------------------------------------------ procedure TFordAsBuiltTests.ParseRejectsMissingChecksum; begin Assert.WillRaise( @@ -488,6 +719,9 @@ procedure TFordAsBuiltTests.ParseRejectsMissingChecksum; EOBDCodingError); end; +//------------------------------------------------------------------------------ +// RESEAL RECOMPUTES CHECKSUM +//------------------------------------------------------------------------------ procedure TFordAsBuiltTests.ResealRecomputesChecksum; var Block: TOBDFordAsBuiltBlock; @@ -500,6 +734,9 @@ procedure TFordAsBuiltTests.ResealRecomputesChecksum; Assert.IsTrue(Block.IsValid); end; +//------------------------------------------------------------------------------ +// PARSE TEXT SKIPS COMMENTS AND BLANKS +//------------------------------------------------------------------------------ procedure TFordAsBuiltTests.ParseTextSkipsCommentsAndBlanks; var Blocks: TArray; @@ -515,6 +752,9 @@ procedure TFordAsBuiltTests.ParseTextSkipsCommentsAndBlanks; Assert.AreEqual(2, Length(Blocks)); end; +//------------------------------------------------------------------------------ +// TO STRING ROUND TRIPS +//------------------------------------------------------------------------------ procedure TFordAsBuiltTests.ToStringRoundTrips; var Block: TOBDFordAsBuiltBlock; diff --git a/tests/Tests.OEM.CodingCommon.pas b/tests/Tests.OEM.CodingCommon.pas index 04738cef..ee4cbf05 100644 --- a/tests/Tests.OEM.CodingCommon.pas +++ b/tests/Tests.OEM.CodingCommon.pas @@ -15,34 +15,91 @@ interface [TestFixture] TCodingRegistryTests = class public + /// + /// Name matches kind is case insensitive. + /// [Test] procedure NameMatchesKindIsCaseInsensitive; + /// + /// Classify vehicle order tokens. + /// [Test] procedure ClassifyVehicleOrderTokens; + /// + /// Classify as built code tokens. + /// [Test] procedure ClassifyAsBuiltCodeTokens; + /// + /// Classify fca proxi tokens. + /// [Test] procedure ClassifyFcaProxiTokens; + /// + /// Classify market region tokens. + /// [Test] procedure ClassifyMarketRegionTokens; + /// + /// Classify starlight tokens. + /// [Test] procedure ClassifyStarlightTokens; + /// + /// Classify unknown returns cf unknown. + /// [Test] procedure ClassifyUnknownReturnsCfUnknown; end; [TestFixture] TCodingLookupTests = class public + /// + /// Rolls royce resolves vehicle order. + /// [Test] procedure RollsRoyceResolvesVehicleOrder; + /// + /// Rolls royce resolves starlight pattern. + /// [Test] procedure RollsRoyceResolvesStarlightPattern; + /// + /// Mazda resolves as built code. + /// [Test] procedure MazdaResolvesAsBuiltCode; + /// + /// Mazda resolves market region. + /// [Test] procedure MazdaResolvesMarketRegion; + /// + /// Unsupported kind returns false. + /// [Test] procedure UnsupportedKindReturnsFalse; + /// + /// Nil extension returns false. + /// [Test] procedure NilExtensionReturnsFalse; end; [TestFixture] TCodingFrameTests = class public + /// + /// Write data by identifier wraps sid and d i d. + /// [Test] procedure WriteDataByIdentifierWrapsSidAndDID; + /// + /// Write data by identifier appends payload. + /// [Test] procedure WriteDataByIdentifierAppendsPayload; + /// + /// Parse accepts positive response. + /// [Test] procedure ParseAcceptsPositiveResponse; + /// + /// Parse rejects wrong sid. + /// [Test] procedure ParseRejectsWrongSid; + /// + /// Parse rejects wrong d i d. + /// [Test] procedure ParseRejectsWrongDID; + /// + /// Kind name produces human label. + /// [Test] procedure KindNameProducesHumanLabel; end; @@ -53,6 +110,9 @@ implementation OBD.OEM, OBD.OEM.Coding.Common, OBD.OEM.RollsRoyce, OBD.OEM.Mazda; +//------------------------------------------------------------------------------ +// NAME MATCHES KIND IS CASE INSENSITIVE +//------------------------------------------------------------------------------ procedure TCodingRegistryTests.NameMatchesKindIsCaseInsensitive; begin Assert.IsTrue(TOBDCodingFunctionRegistry.NameMatchesKind( @@ -61,6 +121,9 @@ procedure TCodingRegistryTests.NameMatchesKindIsCaseInsensitive; 'Fa_Assembly', cfVehicleOrder)); end; +//------------------------------------------------------------------------------ +// CLASSIFY VEHICLE ORDER TOKENS +//------------------------------------------------------------------------------ procedure TCodingRegistryTests.ClassifyVehicleOrderTokens; begin Assert.AreEqual(Ord(cfVehicleOrder), Ord( @@ -71,6 +134,9 @@ procedure TCodingRegistryTests.ClassifyVehicleOrderTokens; TOBDCodingFunctionRegistry.ClassifyName('vehicle_order'))); end; +//------------------------------------------------------------------------------ +// CLASSIFY AS BUILT CODE TOKENS +//------------------------------------------------------------------------------ procedure TCodingRegistryTests.ClassifyAsBuiltCodeTokens; begin Assert.AreEqual(Ord(cfAsBuiltCode), Ord( @@ -79,12 +145,18 @@ procedure TCodingRegistryTests.ClassifyAsBuiltCodeTokens; TOBDCodingFunctionRegistry.ClassifyName('ford_as_built_block'))); end; +//------------------------------------------------------------------------------ +// CLASSIFY FCA PROXI TOKENS +//------------------------------------------------------------------------------ procedure TCodingRegistryTests.ClassifyFcaProxiTokens; begin Assert.AreEqual(Ord(cfFcaProxi), Ord( TOBDCodingFunctionRegistry.ClassifyName('witech_proxi_align'))); end; +//------------------------------------------------------------------------------ +// CLASSIFY MARKET REGION TOKENS +//------------------------------------------------------------------------------ procedure TCodingRegistryTests.ClassifyMarketRegionTokens; begin Assert.AreEqual(Ord(cfMarketRegion), Ord( @@ -93,12 +165,18 @@ procedure TCodingRegistryTests.ClassifyMarketRegionTokens; TOBDCodingFunctionRegistry.ClassifyName('subaru_market_code'))); end; +//------------------------------------------------------------------------------ +// CLASSIFY STARLIGHT TOKENS +//------------------------------------------------------------------------------ procedure TCodingRegistryTests.ClassifyStarlightTokens; begin Assert.AreEqual(Ord(cfStarlightPattern), Ord( TOBDCodingFunctionRegistry.ClassifyName('rr_starlight_pattern'))); end; +//------------------------------------------------------------------------------ +// CLASSIFY UNKNOWN RETURNS CF UNKNOWN +//------------------------------------------------------------------------------ procedure TCodingRegistryTests.ClassifyUnknownReturnsCfUnknown; begin Assert.AreEqual(Ord(cfUnknown), Ord( @@ -110,6 +188,10 @@ procedure TCodingRegistryTests.ClassifyUnknownReturnsCfUnknown; //============================================================================== // Lookup against shipped OEM catalogs //============================================================================== + +//------------------------------------------------------------------------------ +// ROLLS ROYCE RESOLVES VEHICLE ORDER +//------------------------------------------------------------------------------ procedure TCodingLookupTests.RollsRoyceResolvesVehicleOrder; var Ext: IOBDOEMExtension; @@ -121,6 +203,9 @@ procedure TCodingLookupTests.RollsRoyceResolvesVehicleOrder; Assert.AreEqual('fa_assembly', Func.DidName); end; +//------------------------------------------------------------------------------ +// ROLLS ROYCE RESOLVES STARLIGHT PATTERN +//------------------------------------------------------------------------------ procedure TCodingLookupTests.RollsRoyceResolvesStarlightPattern; var Ext: IOBDOEMExtension; @@ -131,6 +216,9 @@ procedure TCodingLookupTests.RollsRoyceResolvesStarlightPattern; Assert.AreEqual('rr_starlight_pattern', Func.DidName); end; +//------------------------------------------------------------------------------ +// MAZDA RESOLVES AS BUILT CODE +//------------------------------------------------------------------------------ procedure TCodingLookupTests.MazdaResolvesAsBuiltCode; var Ext: IOBDOEMExtension; @@ -141,6 +229,9 @@ procedure TCodingLookupTests.MazdaResolvesAsBuiltCode; Assert.AreEqual('mazda_as_built_code', Func.DidName); end; +//------------------------------------------------------------------------------ +// MAZDA RESOLVES MARKET REGION +//------------------------------------------------------------------------------ procedure TCodingLookupTests.MazdaResolvesMarketRegion; var Ext: IOBDOEMExtension; @@ -151,6 +242,9 @@ procedure TCodingLookupTests.MazdaResolvesMarketRegion; Assert.AreEqual('mazda_market_code', Func.DidName); end; +//------------------------------------------------------------------------------ +// UNSUPPORTED KIND RETURNS FALSE +//------------------------------------------------------------------------------ procedure TCodingLookupTests.UnsupportedKindReturnsFalse; var Ext: IOBDOEMExtension; @@ -162,6 +256,9 @@ procedure TCodingLookupTests.UnsupportedKindReturnsFalse; Assert.AreEqual(Ord(cfUnknown), Ord(Func.Kind)); end; +//------------------------------------------------------------------------------ +// NIL EXTENSION RETURNS FALSE +//------------------------------------------------------------------------------ procedure TCodingLookupTests.NilExtensionReturnsFalse; var Func: TOBDCodingFunction; @@ -172,6 +269,10 @@ procedure TCodingLookupTests.NilExtensionReturnsFalse; //============================================================================== // Frame builder + display labels //============================================================================== + +//------------------------------------------------------------------------------ +// WRITE DATA BY IDENTIFIER WRAPS SID AND DID +//------------------------------------------------------------------------------ procedure TCodingFrameTests.WriteDataByIdentifierWrapsSidAndDID; var Frame: TBytes; @@ -183,6 +284,9 @@ procedure TCodingFrameTests.WriteDataByIdentifierWrapsSidAndDID; Assert.AreEqual($A2, Integer(Frame[2]), 'DID lo byte'); end; +//------------------------------------------------------------------------------ +// WRITE DATA BY IDENTIFIER APPENDS PAYLOAD +//------------------------------------------------------------------------------ procedure TCodingFrameTests.WriteDataByIdentifierAppendsPayload; var Frame, Data: TBytes; @@ -196,6 +300,9 @@ procedure TCodingFrameTests.WriteDataByIdentifierAppendsPayload; Assert.AreEqual($DD, Integer(Frame[6])); end; +//------------------------------------------------------------------------------ +// PARSE ACCEPTS POSITIVE RESPONSE +//------------------------------------------------------------------------------ procedure TCodingFrameTests.ParseAcceptsPositiveResponse; var Resp: TBytes; @@ -205,6 +312,9 @@ procedure TCodingFrameTests.ParseAcceptsPositiveResponse; Assert.IsTrue(ParseCodingResponse(Resp, $F1A2)); end; +//------------------------------------------------------------------------------ +// PARSE REJECTS WRONG SID +//------------------------------------------------------------------------------ procedure TCodingFrameTests.ParseRejectsWrongSid; var Resp: TBytes; @@ -213,6 +323,9 @@ procedure TCodingFrameTests.ParseRejectsWrongSid; Assert.IsFalse(ParseCodingResponse(Resp, $F1A2)); end; +//------------------------------------------------------------------------------ +// PARSE REJECTS WRONG DID +//------------------------------------------------------------------------------ procedure TCodingFrameTests.ParseRejectsWrongDID; var Resp: TBytes; @@ -221,6 +334,9 @@ procedure TCodingFrameTests.ParseRejectsWrongDID; Assert.IsFalse(ParseCodingResponse(Resp, $F1A2)); end; +//------------------------------------------------------------------------------ +// KIND NAME PRODUCES HUMAN LABEL +//------------------------------------------------------------------------------ procedure TCodingFrameTests.KindNameProducesHumanLabel; begin Assert.AreEqual('Vehicle Order / FA / Commission', diff --git a/tests/Tests.OEM.ComponentProtection.VAG.pas b/tests/Tests.OEM.ComponentProtection.VAG.pas new file mode 100644 index 00000000..070b1220 --- /dev/null +++ b/tests/Tests.OEM.ComponentProtection.VAG.pas @@ -0,0 +1,173 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.OEM.ComponentProtection.VAG.pas +// CONTENTS : Tests for OBD.OEM.ComponentProtection.VAG +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.OEM.ComponentProtection.VAG; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TVAGCPTests = class + public + /// + /// Request round trip. + /// + [Test] procedure RequestRoundTrip; + /// + /// Response round trip. + /// + [Test] procedure ResponseRoundTrip; + /// + /// Request rejects bad v i n. + /// + [Test] procedure RequestRejectsBadVIN; + /// + /// Request decode rejects truncated serial. + /// + [Test] procedure RequestDecodeRejectsTruncatedSerial; + /// + /// Request decode rejects bad v i n length. + /// + [Test] procedure RequestDecodeRejectsBadVINLength; + /// + /// Response decode rejects truncated response. + /// + [Test] procedure ResponseDecodeRejectsTruncatedResponse; + /// + /// Default solver fails closed. + /// + [Test] procedure DefaultSolverFailsClosed; + end; + +implementation + +uses + System.SysUtils, OBD.OEM.ComponentProtection.VAG; + +//------------------------------------------------------------------------------ +// REQUEST ROUND TRIP +//------------------------------------------------------------------------------ +procedure TVAGCPTests.RequestRoundTrip; +var + In_, Out_: TVAGCPRequest; + Bytes: TBytes; +begin + In_.ECUType := $0042; + In_.ComponentSerial := TBytes.Create($AA, $BB, $CC, $DD); + In_.VIN := 'WVWZZZ8N8Z1234567'; + In_.Nonce := TBytes.Create($11, $22, $33, $44, $55); + Bytes := EncodeVAGCPRequest(In_); + Out_ := DecodeVAGCPRequest(Bytes); + Assert.AreEqual(Word($0042), Out_.ECUType); + Assert.AreEqual(4, Length(Out_.ComponentSerial)); + Assert.AreEqual(Integer($AA), Integer(Out_.ComponentSerial[0])); + Assert.AreEqual('WVWZZZ8N8Z1234567', Out_.VIN); + Assert.AreEqual(5, Length(Out_.Nonce)); + Assert.AreEqual(Integer($55), Integer(Out_.Nonce[4])); +end; + +//------------------------------------------------------------------------------ +// RESPONSE ROUND TRIP +//------------------------------------------------------------------------------ +procedure TVAGCPTests.ResponseRoundTrip; +var + In_, Out_: TVAGCPResponse; + Bytes: TBytes; +begin + In_.Response := TBytes.Create($DE, $AD); + In_.Signature := TBytes.Create($BE, $EF, $00, $11); + Bytes := EncodeVAGCPResponse(In_); + Out_ := DecodeVAGCPResponse(Bytes); + Assert.AreEqual(2, Length(Out_.Response)); + Assert.AreEqual(4, Length(Out_.Signature)); + Assert.AreEqual(Integer($DE), Integer(Out_.Response[0])); + Assert.AreEqual(Integer($11), Integer(Out_.Signature[3])); +end; + +//------------------------------------------------------------------------------ +// REQUEST REJECTS BAD VIN +//------------------------------------------------------------------------------ +procedure TVAGCPTests.RequestRejectsBadVIN; +var + Req: TVAGCPRequest; +begin + Req.ECUType := 0; + Req.VIN := 'TOO-SHORT'; + Assert.WillRaise( + procedure begin EncodeVAGCPRequest(Req); end, EOBDVAGCP); +end; + +//------------------------------------------------------------------------------ +// REQUEST DECODE REJECTS TRUNCATED SERIAL +//------------------------------------------------------------------------------ +procedure TVAGCPTests.RequestDecodeRejectsTruncatedSerial; +var + Bytes: TBytes; +begin + // ECUType=0x0042, serial-len=0x0010, but no body bytes + Bytes := TBytes.Create($00, $42, $00, $10); + Assert.WillRaise( + procedure begin DecodeVAGCPRequest(Bytes); end, EOBDVAGCP); +end; + +//------------------------------------------------------------------------------ +// REQUEST DECODE REJECTS BAD VINLENGTH +//------------------------------------------------------------------------------ +procedure TVAGCPTests.RequestDecodeRejectsBadVINLength; +var + Bytes: TBytes; + I: Integer; +begin + // Serial empty, VIN length byte declares 16 (must be 17) + SetLength(Bytes, 5); + Bytes[0] := $00; Bytes[1] := $00; + Bytes[2] := $00; Bytes[3] := $00; + Bytes[4] := $10; + for I := 0 to 15 do Bytes := Bytes + [Byte(Ord('A'))]; + Assert.WillRaise( + procedure begin DecodeVAGCPRequest(Bytes); end, EOBDVAGCP); +end; + +//------------------------------------------------------------------------------ +// RESPONSE DECODE REJECTS TRUNCATED RESPONSE +//------------------------------------------------------------------------------ +procedure TVAGCPTests.ResponseDecodeRejectsTruncatedResponse; +begin + // Declares 4 response bytes but only 2 follow + Assert.WillRaise( + procedure + begin + DecodeVAGCPResponse(TBytes.Create($00, $04, $AA, $BB)); + end, + EOBDVAGCP); +end; + +//------------------------------------------------------------------------------ +// DEFAULT SOLVER FAILS CLOSED +//------------------------------------------------------------------------------ +procedure TVAGCPTests.DefaultSolverFailsClosed; +var + Solver: IVAGCPSolver; + Req: TVAGCPRequest; +begin + Solver := TVAGCPSolverNotAvailable.Create; + Req.VIN := 'WVWZZZ8N8Z1234567'; + Assert.WillRaise( + procedure begin Solver.Solve(Req); end, EOBDVAGCPNoSolver); +end; + +initialization + TDUnitX.RegisterTestFixture(TVAGCPTests); + +end. diff --git a/tests/Tests.OEM.DTC.Schema.pas b/tests/Tests.OEM.DTC.Schema.pas index df6c9acd..558809fd 100644 --- a/tests/Tests.OEM.DTC.Schema.pas +++ b/tests/Tests.OEM.DTC.Schema.pas @@ -24,14 +24,41 @@ interface [TestFixture] TDtcSchemaExtensionTests = class public + /// + /// Loads new fields from inline j s o n. + /// [Test] procedure LoadsNewFieldsFromInlineJSON; + /// + /// Backward compatible with old format. + /// [Test] procedure BackwardCompatibleWithOldFormat; + /// + /// Parses monitor type strings. + /// [Test] procedure ParsesMonitorTypeStrings; + /// + /// Shipped i s o15031 has monitor type. + /// [Test] procedure ShippedISO15031HasMonitorType; + /// + /// Shipped i s o15031 has related d i ds. + /// [Test] procedure ShippedISO15031HasRelatedDIDs; + /// + /// Shipped i s o15031 freeze frame on misfires. + /// [Test] procedure ShippedISO15031FreezeFrameOnMisfires; + /// + /// Sample p codes found by lookup. + /// [Test] procedure SamplePCodesFoundByLookup; + /// + /// Sample u codes found by lookup. + /// [Test] procedure SampleUCodesFoundByLookup; + /// + /// Related routines point at catalog routines. + /// [Test] procedure RelatedRoutinesPointAtCatalogRoutines; end; @@ -66,6 +93,9 @@ implementation DTC_OLD_FORMAT = '{"dtcs": [{"code": "P0420", "description": "Cat efficiency"}]}'; +//------------------------------------------------------------------------------ +// LOADS NEW FIELDS FROM INLINE JSON +//------------------------------------------------------------------------------ procedure TDtcSchemaExtensionTests.LoadsNewFieldsFromInlineJSON; var Cat: TOBDDtcCatalog; @@ -91,6 +121,9 @@ procedure TDtcSchemaExtensionTests.LoadsNewFieldsFromInlineJSON; end; end; +//------------------------------------------------------------------------------ +// BACKWARD COMPATIBLE WITH OLD FORMAT +//------------------------------------------------------------------------------ procedure TDtcSchemaExtensionTests.BackwardCompatibleWithOldFormat; var Cat: TOBDDtcCatalog; @@ -114,6 +147,9 @@ procedure TDtcSchemaExtensionTests.BackwardCompatibleWithOldFormat; end; end; +//------------------------------------------------------------------------------ +// PARSES MONITOR TYPE STRINGS +//------------------------------------------------------------------------------ procedure TDtcSchemaExtensionTests.ParsesMonitorTypeStrings; begin Assert.AreEqual(dmtContinuous, @@ -137,6 +173,10 @@ procedure TDtcSchemaExtensionTests.ParsesMonitorTypeStrings; // G9 (closed): use the loader's exported ResolveCatalogPath // rather than re-implementing the search-path logic here. Keeps // tests in lock-step with production. + +//------------------------------------------------------------------------------ +// LOAD SHIPPED DTC CATALOG +//------------------------------------------------------------------------------ procedure LoadShippedDtcCatalog(const FileName: string; Cat: TOBDDtcCatalog); var @@ -148,6 +188,9 @@ procedure LoadShippedDtcCatalog(const FileName: string; Cat.LoadFromFile(Path); end; +//------------------------------------------------------------------------------ +// SHIPPED ISO15031 HAS MONITOR TYPE +//------------------------------------------------------------------------------ procedure TDtcSchemaExtensionTests.ShippedISO15031HasMonitorType; var Cat: TOBDDtcCatalog; @@ -167,6 +210,9 @@ procedure TDtcSchemaExtensionTests.ShippedISO15031HasMonitorType; end; end; +//------------------------------------------------------------------------------ +// SHIPPED ISO15031 HAS RELATED DIDS +//------------------------------------------------------------------------------ procedure TDtcSchemaExtensionTests.ShippedISO15031HasRelatedDIDs; var Cat: TOBDDtcCatalog; @@ -188,6 +234,9 @@ procedure TDtcSchemaExtensionTests.ShippedISO15031HasRelatedDIDs; end; end; +//------------------------------------------------------------------------------ +// SHIPPED ISO15031 FREEZE FRAME ON MISFIRES +//------------------------------------------------------------------------------ procedure TDtcSchemaExtensionTests.ShippedISO15031FreezeFrameOnMisfires; var Cat: TOBDDtcCatalog; @@ -205,6 +254,9 @@ procedure TDtcSchemaExtensionTests.ShippedISO15031FreezeFrameOnMisfires; end; end; +//------------------------------------------------------------------------------ +// SAMPLE PCODES FOUND BY LOOKUP +//------------------------------------------------------------------------------ procedure TDtcSchemaExtensionTests.SamplePCodesFoundByLookup; var Cat: TOBDDtcCatalog; @@ -224,6 +276,9 @@ procedure TDtcSchemaExtensionTests.SamplePCodesFoundByLookup; end; end; +//------------------------------------------------------------------------------ +// SAMPLE UCODES FOUND BY LOOKUP +//------------------------------------------------------------------------------ procedure TDtcSchemaExtensionTests.SampleUCodesFoundByLookup; var Cat: TOBDDtcCatalog; @@ -240,6 +295,9 @@ procedure TDtcSchemaExtensionTests.SampleUCodesFoundByLookup; end; end; +//------------------------------------------------------------------------------ +// RELATED ROUTINES POINT AT CATALOG ROUTINES +//------------------------------------------------------------------------------ procedure TDtcSchemaExtensionTests.RelatedRoutinesPointAtCatalogRoutines; var Cat: TOBDDtcCatalog; diff --git a/tests/Tests.OEM.DTC.pas b/tests/Tests.OEM.DTC.pas index df55e3ae..cf67a097 100644 --- a/tests/Tests.OEM.DTC.pas +++ b/tests/Tests.OEM.DTC.pas @@ -13,30 +13,90 @@ interface [TestFixture] TDtcEncodingTests = class public + /// + /// Format powertrain code. + /// [Test] procedure FormatPowertrainCode; + /// + /// Format chassis code. + /// [Test] procedure FormatChassisCode; + /// + /// Format body code. + /// [Test] procedure FormatBodyCode; + /// + /// Format network code. + /// [Test] procedure FormatNetworkCode; + /// + /// Format manufacturer code. + /// [Test] procedure FormatManufacturerCode; + /// + /// Encode round trips p0301. + /// [Test] procedure EncodeRoundTripsP0301; + /// + /// Encode round trips manufacturer. + /// [Test] procedure EncodeRoundTripsManufacturer; + /// + /// Encode rejects short input. + /// [Test] procedure EncodeRejectsShortInput; + /// + /// Encode rejects bad letter. + /// [Test] procedure EncodeRejectsBadLetter; + /// + /// Encode rejects bad group digit. + /// [Test] procedure EncodeRejectsBadGroupDigit; + /// + /// Is manufacturer dtc recognises p1 and p3. + /// [Test] procedure IsManufacturerDtcRecognisesP1AndP3; + /// + /// Is manufacturer dtc rejects s a e. + /// [Test] procedure IsManufacturerDtcRejectsSAE; + /// + /// Severity round trip. + /// [Test] procedure SeverityRoundTrip; end; [TestFixture] TDtcCatalogTests = class public + /// + /// Loads top level dtc array. + /// [Test] procedure LoadsTopLevelDtcArray; + /// + /// Loads bare j s o n array. + /// [Test] procedure LoadsBareJSONArray; + /// + /// Lookup is case insensitive. + /// [Test] procedure LookupIsCaseInsensitive; + /// + /// Replaces duplicate code. + /// [Test] procedure ReplacesDuplicateCode; + /// + /// Captures possible causes and hints. + /// [Test] procedure CapturesPossibleCausesAndHints; + /// + /// Default source propagates to entries. + /// [Test] procedure DefaultSourcePropagatesToEntries; + /// + /// Verified flag defaults to false. + /// [Test] procedure VerifiedFlagDefaultsToFalse; end; @@ -49,27 +109,43 @@ implementation //============================================================================== // Encoding //============================================================================== + +//------------------------------------------------------------------------------ +// FORMAT POWERTRAIN CODE +//------------------------------------------------------------------------------ procedure TDtcEncodingTests.FormatPowertrainCode; begin // 0x03 0x01 → P0301 (cylinder 1 misfire). Assert.AreEqual('P0301', FormatDtc($03, $01)); end; +//------------------------------------------------------------------------------ +// FORMAT CHASSIS CODE +//------------------------------------------------------------------------------ procedure TDtcEncodingTests.FormatChassisCode; begin Assert.AreEqual('C0561', FormatDtc($45, $61)); end; +//------------------------------------------------------------------------------ +// FORMAT BODY CODE +//------------------------------------------------------------------------------ procedure TDtcEncodingTests.FormatBodyCode; begin Assert.AreEqual('B1318', FormatDtc($93, $18)); end; +//------------------------------------------------------------------------------ +// FORMAT NETWORK CODE +//------------------------------------------------------------------------------ procedure TDtcEncodingTests.FormatNetworkCode; begin Assert.AreEqual('U0100', FormatDtc($C1, $00)); end; +//------------------------------------------------------------------------------ +// FORMAT MANUFACTURER CODE +//------------------------------------------------------------------------------ procedure TDtcEncodingTests.FormatManufacturerCode; begin // P1296 — first digit 1 (manufacturer): bit5=0, bit4=1 → high byte 0x12. @@ -78,6 +154,9 @@ procedure TDtcEncodingTests.FormatManufacturerCode; Assert.AreEqual('P3000', FormatDtc($30, $00)); end; +//------------------------------------------------------------------------------ +// ENCODE ROUND TRIPS P0301 +//------------------------------------------------------------------------------ procedure TDtcEncodingTests.EncodeRoundTripsP0301; var B: TBytes; @@ -87,6 +166,9 @@ procedure TDtcEncodingTests.EncodeRoundTripsP0301; Assert.AreEqual(Byte($01), B[1]); end; +//------------------------------------------------------------------------------ +// ENCODE ROUND TRIPS MANUFACTURER +//------------------------------------------------------------------------------ procedure TDtcEncodingTests.EncodeRoundTripsManufacturer; var B: TBytes; @@ -95,36 +177,54 @@ procedure TDtcEncodingTests.EncodeRoundTripsManufacturer; Assert.AreEqual('P1296', FormatDtc(B)); end; +//------------------------------------------------------------------------------ +// ENCODE REJECTS SHORT INPUT +//------------------------------------------------------------------------------ procedure TDtcEncodingTests.EncodeRejectsShortInput; begin Assert.WillRaise( procedure begin EncodeDtc('P030'); end, EOBDDtcError); end; +//------------------------------------------------------------------------------ +// ENCODE REJECTS BAD LETTER +//------------------------------------------------------------------------------ procedure TDtcEncodingTests.EncodeRejectsBadLetter; begin Assert.WillRaise( procedure begin EncodeDtc('Z0301'); end, EOBDDtcError); end; +//------------------------------------------------------------------------------ +// ENCODE REJECTS BAD GROUP DIGIT +//------------------------------------------------------------------------------ procedure TDtcEncodingTests.EncodeRejectsBadGroupDigit; begin Assert.WillRaise( procedure begin EncodeDtc('P9999'); end, EOBDDtcError); end; +//------------------------------------------------------------------------------ +// IS MANUFACTURER DTC RECOGNISES P1 AND P3 +//------------------------------------------------------------------------------ procedure TDtcEncodingTests.IsManufacturerDtcRecognisesP1AndP3; begin Assert.IsTrue(IsManufacturerDtc('P1296')); Assert.IsTrue(IsManufacturerDtc('B3000')); end; +//------------------------------------------------------------------------------ +// IS MANUFACTURER DTC REJECTS SAE +//------------------------------------------------------------------------------ procedure TDtcEncodingTests.IsManufacturerDtcRejectsSAE; begin Assert.IsFalse(IsManufacturerDtc('P0301')); Assert.IsFalse(IsManufacturerDtc('P2173')); end; +//------------------------------------------------------------------------------ +// SEVERITY ROUND TRIP +//------------------------------------------------------------------------------ procedure TDtcEncodingTests.SeverityRoundTrip; begin Assert.AreEqual(Ord(dtcSeverityCritical), Ord(ParseSeverity('critical'))); @@ -152,6 +252,9 @@ procedure TDtcEncodingTests.SeverityRoundTrip; '{"code": "U0100", "description": "Lost comm with ECM"}' + ']'; +//------------------------------------------------------------------------------ +// LOADS TOP LEVEL DTC ARRAY +//------------------------------------------------------------------------------ procedure TDtcCatalogTests.LoadsTopLevelDtcArray; var Cat: TOBDDtcCatalog; @@ -170,6 +273,9 @@ procedure TDtcCatalogTests.LoadsTopLevelDtcArray; end; end; +//------------------------------------------------------------------------------ +// LOADS BARE JSONARRAY +//------------------------------------------------------------------------------ procedure TDtcCatalogTests.LoadsBareJSONArray; var Cat: TOBDDtcCatalog; @@ -183,6 +289,9 @@ procedure TDtcCatalogTests.LoadsBareJSONArray; end; end; +//------------------------------------------------------------------------------ +// LOOKUP IS CASE INSENSITIVE +//------------------------------------------------------------------------------ procedure TDtcCatalogTests.LookupIsCaseInsensitive; var Cat: TOBDDtcCatalog; @@ -198,6 +307,9 @@ procedure TDtcCatalogTests.LookupIsCaseInsensitive; end; end; +//------------------------------------------------------------------------------ +// REPLACES DUPLICATE CODE +//------------------------------------------------------------------------------ procedure TDtcCatalogTests.ReplacesDuplicateCode; var Cat: TOBDDtcCatalog; @@ -215,6 +327,9 @@ procedure TDtcCatalogTests.ReplacesDuplicateCode; end; end; +//------------------------------------------------------------------------------ +// CAPTURES POSSIBLE CAUSES AND HINTS +//------------------------------------------------------------------------------ procedure TDtcCatalogTests.CapturesPossibleCausesAndHints; var Cat: TOBDDtcCatalog; @@ -232,6 +347,9 @@ procedure TDtcCatalogTests.CapturesPossibleCausesAndHints; end; end; +//------------------------------------------------------------------------------ +// DEFAULT SOURCE PROPAGATES TO ENTRIES +//------------------------------------------------------------------------------ procedure TDtcCatalogTests.DefaultSourcePropagatesToEntries; var Cat: TOBDDtcCatalog; @@ -247,6 +365,9 @@ procedure TDtcCatalogTests.DefaultSourcePropagatesToEntries; end; end; +//------------------------------------------------------------------------------ +// VERIFIED FLAG DEFAULTS TO FALSE +//------------------------------------------------------------------------------ procedure TDtcCatalogTests.VerifiedFlagDefaultsToFalse; var Cat: TOBDDtcCatalog; diff --git a/tests/Tests.OEM.DiagSession.pas b/tests/Tests.OEM.DiagSession.pas index f83fa67a..e3d43b9e 100644 --- a/tests/Tests.OEM.DiagSession.pas +++ b/tests/Tests.OEM.DiagSession.pas @@ -20,7 +20,13 @@ interface [TestFixture] TDiagSessionConstructionTests = class public + /// + /// Rejects nil connection. + /// [Test] procedure RejectsNilConnection; + /// + /// Rejects nil extension. + /// [Test] procedure RejectsNilExtension; end; @@ -30,11 +36,15 @@ implementation System.SysUtils, OBD.OEM, OBD.OEM.DiagSession, OBD.OEM.VW; +//------------------------------------------------------------------------------ +// REJECTS NIL CONNECTION +//------------------------------------------------------------------------------ procedure TDiagSessionConstructionTests.RejectsNilConnection; begin Assert.WillRaise( procedure - var Session: TOBDDiagSession; + var + Session: TOBDDiagSession; begin Session := TOBDDiagSession.Create(nil, TOBDOEMExtensionVW.Create); Session.Free; @@ -42,11 +52,15 @@ procedure TDiagSessionConstructionTests.RejectsNilConnection; EOBDDiagSessionError); end; +//------------------------------------------------------------------------------ +// REJECTS NIL EXTENSION +//------------------------------------------------------------------------------ procedure TDiagSessionConstructionTests.RejectsNilExtension; begin Assert.WillRaise( procedure - var Session: TOBDDiagSession; + var + Session: TOBDDiagSession; begin // We can't easily build a real TOBDConnectionAsync without a // backing IOBDConnection, but the nil-check on the OEM is the diff --git a/tests/Tests.OEM.DoIP.pas b/tests/Tests.OEM.DoIP.pas index 4401dce5..62ae8175 100644 --- a/tests/Tests.OEM.DoIP.pas +++ b/tests/Tests.OEM.DoIP.pas @@ -13,40 +13,100 @@ interface [TestFixture] TDoIPHeaderTests = class public + /// + /// Build header emits version inversion. + /// [Test] procedure BuildHeaderEmitsVersionInversion; + /// + /// Build header encodes payload type and length big endian. + /// [Test] procedure BuildHeaderEncodesPayloadTypeAndLengthBigEndian; + /// + /// Parse header rejects bad inversion. + /// [Test] procedure ParseHeaderRejectsBadInversion; + /// + /// Parse header rejects short buffer. + /// [Test] procedure ParseHeaderRejectsShortBuffer; + /// + /// Parse header round trips all fields. + /// [Test] procedure ParseHeaderRoundTripsAllFields; end; [TestFixture] TDoIPRoutingTests = class public + /// + /// Build activation request emits19 bytes with default activation. + /// [Test] procedure BuildActivationRequestEmits19BytesWithDefaultActivation; + /// + /// Build activation request carries o e m specific. + /// [Test] procedure BuildActivationRequestCarriesOEMSpecific; + /// + /// Parse activation response v2010. + /// [Test] procedure ParseActivationResponseV2010; + /// + /// Parse activation response v2012 with o e m tail. + /// [Test] procedure ParseActivationResponseV2012WithOEMTail; + /// + /// Parse activation rejects truncated. + /// [Test] procedure ParseActivationRejectsTruncated; + /// + /// Parse activation returns false on wrong type. + /// [Test] procedure ParseActivationReturnsFalseOnWrongType; end; [TestFixture] TDoIPVehicleTests = class public + /// + /// Build vehicle ident empty payload. + /// [Test] procedure BuildVehicleIdentEmptyPayload; + /// + /// Build vehicle ident by v i n rejects bad length. + /// [Test] procedure BuildVehicleIdentByVINRejectsBadLength; + /// + /// Build vehicle ident by v i n round trips. + /// [Test] procedure BuildVehicleIdentByVINRoundTrips; + /// + /// Parse vehicle announcement extracts fields. + /// [Test] procedure ParseVehicleAnnouncementExtractsFields; end; [TestFixture] TDoIPDiagMessageTests = class public + /// + /// Build diag wraps u d s. + /// [Test] procedure BuildDiagWrapsUDS; + /// + /// Build diag rejects empty user data. + /// [Test] procedure BuildDiagRejectsEmptyUserData; + /// + /// Parse diag extracts addresses and user data. + /// [Test] procedure ParseDiagExtractsAddressesAndUserData; + /// + /// Round trips via build and parse. + /// [Test] procedure RoundTripsViaBuildAndParse; + /// + /// Alive check pair. + /// [Test] procedure AliveCheckPair; end; @@ -59,6 +119,10 @@ implementation //============================================================================== // Header //============================================================================== + +//------------------------------------------------------------------------------ +// BUILD HEADER EMITS VERSION INVERSION +//------------------------------------------------------------------------------ procedure TDoIPHeaderTests.BuildHeaderEmitsVersionInversion; var H: TBytes; @@ -69,6 +133,9 @@ procedure TDoIPHeaderTests.BuildHeaderEmitsVersionInversion; Assert.AreEqual(Byte($FD), H[1]); // not $02 = $FD end; +//------------------------------------------------------------------------------ +// BUILD HEADER ENCODES PAYLOAD TYPE AND LENGTH BIG ENDIAN +//------------------------------------------------------------------------------ procedure TDoIPHeaderTests.BuildHeaderEncodesPayloadTypeAndLengthBigEndian; var H: TBytes; @@ -83,6 +150,9 @@ procedure TDoIPHeaderTests.BuildHeaderEncodesPayloadTypeAndLengthBigEndian; Assert.AreEqual(Byte($04), H[7]); end; +//------------------------------------------------------------------------------ +// PARSE HEADER REJECTS BAD INVERSION +//------------------------------------------------------------------------------ procedure TDoIPHeaderTests.ParseHeaderRejectsBadInversion; var Header: TOBDDoIPHeader; @@ -94,6 +164,9 @@ procedure TDoIPHeaderTests.ParseHeaderRejectsBadInversion; EOBDDoIPError); end; +//------------------------------------------------------------------------------ +// PARSE HEADER REJECTS SHORT BUFFER +//------------------------------------------------------------------------------ procedure TDoIPHeaderTests.ParseHeaderRejectsShortBuffer; var Header: TOBDDoIPHeader; @@ -103,6 +176,9 @@ procedure TDoIPHeaderTests.ParseHeaderRejectsShortBuffer; EOBDDoIPError); end; +//------------------------------------------------------------------------------ +// PARSE HEADER ROUND TRIPS ALL FIELDS +//------------------------------------------------------------------------------ procedure TDoIPHeaderTests.ParseHeaderRoundTripsAllFields; var Built: TBytes; @@ -118,6 +194,10 @@ procedure TDoIPHeaderTests.ParseHeaderRoundTripsAllFields; //============================================================================== // Routing activation //============================================================================== + +//------------------------------------------------------------------------------ +// BUILD ACTIVATION REQUEST EMITS19 BYTES WITH DEFAULT ACTIVATION +//------------------------------------------------------------------------------ procedure TDoIPRoutingTests.BuildActivationRequestEmits19BytesWithDefaultActivation; var Bytes: TBytes; @@ -132,6 +212,9 @@ procedure TDoIPRoutingTests.BuildActivationRequestEmits19BytesWithDefaultActivat Assert.AreEqual(Byte($00), Bytes[10]); // activation type = default end; +//------------------------------------------------------------------------------ +// BUILD ACTIVATION REQUEST CARRIES OEMSPECIFIC +//------------------------------------------------------------------------------ procedure TDoIPRoutingTests.BuildActivationRequestCarriesOEMSpecific; var Bytes: TBytes; @@ -143,6 +226,9 @@ procedure TDoIPRoutingTests.BuildActivationRequestCarriesOEMSpecific; Assert.AreEqual(Byte($EF), Bytes[18]); end; +//------------------------------------------------------------------------------ +// PARSE ACTIVATION RESPONSE V2010 +//------------------------------------------------------------------------------ procedure TDoIPRoutingTests.ParseActivationResponseV2010; var Activation: TOBDDoIPRoutingActivation; @@ -160,6 +246,9 @@ procedure TDoIPRoutingTests.ParseActivationResponseV2010; Assert.AreEqual(Cardinal(0), Activation.OEMSpecific); end; +//------------------------------------------------------------------------------ +// PARSE ACTIVATION RESPONSE V2012 WITH OEMTAIL +//------------------------------------------------------------------------------ procedure TDoIPRoutingTests.ParseActivationResponseV2012WithOEMTail; var Activation: TOBDDoIPRoutingActivation; @@ -173,6 +262,9 @@ procedure TDoIPRoutingTests.ParseActivationResponseV2012WithOEMTail; Assert.AreEqual(Cardinal($CAFEBABE), Activation.OEMSpecific); end; +//------------------------------------------------------------------------------ +// PARSE ACTIVATION REJECTS TRUNCATED +//------------------------------------------------------------------------------ procedure TDoIPRoutingTests.ParseActivationRejectsTruncated; var Activation: TOBDDoIPRoutingActivation; @@ -186,6 +278,9 @@ procedure TDoIPRoutingTests.ParseActivationRejectsTruncated; EOBDDoIPError); end; +//------------------------------------------------------------------------------ +// PARSE ACTIVATION RETURNS FALSE ON WRONG TYPE +//------------------------------------------------------------------------------ procedure TDoIPRoutingTests.ParseActivationReturnsFalseOnWrongType; var Activation: TOBDDoIPRoutingActivation; @@ -198,6 +293,10 @@ procedure TDoIPRoutingTests.ParseActivationReturnsFalseOnWrongType; //============================================================================== // Vehicle ident //============================================================================== + +//------------------------------------------------------------------------------ +// BUILD VEHICLE IDENT EMPTY PAYLOAD +//------------------------------------------------------------------------------ procedure TDoIPVehicleTests.BuildVehicleIdentEmptyPayload; var Bytes: TBytes; @@ -207,6 +306,9 @@ procedure TDoIPVehicleTests.BuildVehicleIdentEmptyPayload; Assert.AreEqual(Byte($01), Bytes[3]); // payload type lo = 0x0001 end; +//------------------------------------------------------------------------------ +// BUILD VEHICLE IDENT BY VINREJECTS BAD LENGTH +//------------------------------------------------------------------------------ procedure TDoIPVehicleTests.BuildVehicleIdentByVINRejectsBadLength; begin Assert.WillRaise( @@ -214,6 +316,9 @@ procedure TDoIPVehicleTests.BuildVehicleIdentByVINRejectsBadLength; EOBDDoIPError); end; +//------------------------------------------------------------------------------ +// BUILD VEHICLE IDENT BY VINROUND TRIPS +//------------------------------------------------------------------------------ procedure TDoIPVehicleTests.BuildVehicleIdentByVINRoundTrips; var Bytes: TBytes; @@ -226,6 +331,9 @@ procedure TDoIPVehicleTests.BuildVehicleIdentByVINRoundTrips; Assert.AreEqual(Byte(Ord('W')), Bytes[8]); end; +//------------------------------------------------------------------------------ +// PARSE VEHICLE ANNOUNCEMENT EXTRACTS FIELDS +//------------------------------------------------------------------------------ procedure TDoIPVehicleTests.ParseVehicleAnnouncementExtractsFields; var Frame: TBytes; @@ -257,6 +365,10 @@ procedure TDoIPVehicleTests.ParseVehicleAnnouncementExtractsFields; //============================================================================== // Diagnostic message //============================================================================== + +//------------------------------------------------------------------------------ +// BUILD DIAG WRAPS UDS +//------------------------------------------------------------------------------ procedure TDoIPDiagMessageTests.BuildDiagWrapsUDS; var UDS, Built: TBytes; @@ -274,6 +386,9 @@ procedure TDoIPDiagMessageTests.BuildDiagWrapsUDS; Assert.AreEqual(Byte($22), Built[12]); end; +//------------------------------------------------------------------------------ +// BUILD DIAG REJECTS EMPTY USER DATA +//------------------------------------------------------------------------------ procedure TDoIPDiagMessageTests.BuildDiagRejectsEmptyUserData; begin Assert.WillRaise( @@ -281,6 +396,9 @@ procedure TDoIPDiagMessageTests.BuildDiagRejectsEmptyUserData; EOBDDoIPError); end; +//------------------------------------------------------------------------------ +// PARSE DIAG EXTRACTS ADDRESSES AND USER DATA +//------------------------------------------------------------------------------ procedure TDoIPDiagMessageTests.ParseDiagExtractsAddressesAndUserData; var Frame: TBytes; @@ -294,6 +412,9 @@ procedure TDoIPDiagMessageTests.ParseDiagExtractsAddressesAndUserData; Assert.AreEqual(Byte($22), Msg.UserData[0]); end; +//------------------------------------------------------------------------------ +// ROUND TRIPS VIA BUILD AND PARSE +//------------------------------------------------------------------------------ procedure TDoIPDiagMessageTests.RoundTripsViaBuildAndParse; var Original, Built: TBytes; @@ -306,6 +427,9 @@ procedure TDoIPDiagMessageTests.RoundTripsViaBuildAndParse; Assert.AreEqual(Byte($EF), Msg.UserData[High(Msg.UserData)]); end; +//------------------------------------------------------------------------------ +// ALIVE CHECK PAIR +//------------------------------------------------------------------------------ procedure TDoIPDiagMessageTests.AliveCheckPair; var Req, Resp: TBytes; diff --git a/tests/Tests.OEM.Extra.pas b/tests/Tests.OEM.Extra.pas index aa93dacb..e0b9a371 100644 --- a/tests/Tests.OEM.Extra.pas +++ b/tests/Tests.OEM.Extra.pas @@ -26,11 +26,29 @@ TOEMExtraRegistryTests = class [TestCase('Stellantis_Peugeot','VF36DRHE9HS123456,STLA')] procedure FindByVIN_RoutesToCorrectOEM(const VIN, ExpectedKey: string); + /// + /// Mercedes decode mileage. + /// [Test] procedure Mercedes_DecodeMileage; + /// + /// Mercedes decode programming status. + /// [Test] procedure Mercedes_DecodeProgrammingStatus; + /// + /// Ford decode battery voltage. + /// [Test] procedure Ford_DecodeBatteryVoltage; + /// + /// Ford decode fuel level. + /// [Test] procedure Ford_DecodeFuelLevel; + /// + /// G m decode mileage. + /// [Test] procedure GM_DecodeMileage; + /// + /// Stellantis decode programming date. + /// [Test] procedure Stellantis_DecodeProgrammingDate; end; @@ -40,6 +58,9 @@ implementation System.SysUtils, OBD.OEM, OBD.OEM.Mercedes, OBD.OEM.Ford, OBD.OEM.GM, OBD.OEM.Stellantis; +//------------------------------------------------------------------------------ +// FIND BY VIN_ROUTES TO CORRECT OEM +//------------------------------------------------------------------------------ procedure TOEMExtraRegistryTests.FindByVIN_RoutesToCorrectOEM( const VIN, ExpectedKey: string); var @@ -51,6 +72,9 @@ procedure TOEMExtraRegistryTests.FindByVIN_RoutesToCorrectOEM( Assert.AreEqual(ExpectedKey, Ext.ManufacturerKey); end; +//------------------------------------------------------------------------------ +// MERCEDES_DECODE MILEAGE +//------------------------------------------------------------------------------ procedure TOEMExtraRegistryTests.Mercedes_DecodeMileage; var Ext: IOBDOEMExtension; @@ -62,6 +86,9 @@ procedure TOEMExtraRegistryTests.Mercedes_DecodeMileage; Assert.AreEqual('mileage = 74565 km', Ext.DecodeDID($0202, Payload)); end; +//------------------------------------------------------------------------------ +// MERCEDES_DECODE PROGRAMMING STATUS +//------------------------------------------------------------------------------ procedure TOEMExtraRegistryTests.Mercedes_DecodeProgrammingStatus; var Ext: IOBDOEMExtension; @@ -75,8 +102,12 @@ procedure TOEMExtraRegistryTests.Mercedes_DecodeProgrammingStatus; Ext.DecodeDID($F19E, TBytes.Create($02))); end; +//------------------------------------------------------------------------------ +// FORD_DECODE BATTERY VOLTAGE +//------------------------------------------------------------------------------ procedure TOEMExtraRegistryTests.Ford_DecodeBatteryVoltage; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMRegistry.FindByKey('FORD'); // 12345 mV @@ -84,16 +115,24 @@ procedure TOEMExtraRegistryTests.Ford_DecodeBatteryVoltage; Ext.DecodeDID($DE02, TBytes.Create($30, $39))); end; +//------------------------------------------------------------------------------ +// FORD_DECODE FUEL LEVEL +//------------------------------------------------------------------------------ procedure TOEMExtraRegistryTests.Ford_DecodeFuelLevel; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMRegistry.FindByKey('FORD'); Assert.AreEqual('fuel_level = 75 %', Ext.DecodeDID($DE00, TBytes.Create($4B))); end; +//------------------------------------------------------------------------------ +// GM_DECODE MILEAGE +//------------------------------------------------------------------------------ procedure TOEMExtraRegistryTests.GM_DecodeMileage; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMRegistry.FindByKey('GM'); // 0x0001E240 = 123456 km, 4-byte big-endian @@ -101,8 +140,12 @@ procedure TOEMExtraRegistryTests.GM_DecodeMileage; Ext.DecodeDID($1981, TBytes.Create($00, $01, $E2, $40))); end; +//------------------------------------------------------------------------------ +// STELLANTIS_DECODE PROGRAMMING DATE +//------------------------------------------------------------------------------ procedure TOEMExtraRegistryTests.Stellantis_DecodeProgrammingDate; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMRegistry.FindByKey('STLA'); // BCD-encoded YY MM DD = 25 03 14 → 2025-03-14 diff --git a/tests/Tests.OEM.Extras2.pas b/tests/Tests.OEM.Extras2.pas index ce2104e8..0bddee37 100644 --- a/tests/Tests.OEM.Extras2.pas +++ b/tests/Tests.OEM.Extras2.pas @@ -16,40 +16,100 @@ interface [TestFixture] TExtras2VINTests = class public + /// + /// Renault claims v r1 not stellantis. + /// [Test] procedure RenaultClaimsVR1NotStellantis; + /// + /// Renault matches dacia and alpine. + /// [Test] procedure RenaultMatchesDaciaAndAlpine; + /// + /// Volvo matches y v1 and china built. + /// [Test] procedure VolvoMatchesYV1AndChinaBuilt; + /// + /// Tesla matches all factories. + /// [Test] procedure TeslaMatchesAllFactories; + /// + /// Suzuki matches maruti. + /// [Test] procedure SuzukiMatchesMaruti; + /// + /// Mitsubishi matches d s m historical. + /// [Test] procedure MitsubishiMatchesDSMHistorical; + /// + /// Stellantis no longer claims v r1. + /// [Test] procedure StellantisNoLongerClaimsVR1; end; [TestFixture] TExtras2CatalogTests = class public + /// + /// Renault e c u map has u c h. + /// [Test] procedure RenaultECUMapHasUCH; + /// + /// Volvo heartbeat is extended. + /// [Test] procedure VolvoHeartbeatIsExtended; + /// + /// Tesla e c u map includes autopilot. + /// [Test] procedure TeslaECUMapIncludesAutopilot; + /// + /// Suzuki has seed key starter. + /// [Test] procedure SuzukiHasSeedKeyStarter; + /// + /// Mitsubishi e c u map includes a w c. + /// [Test] procedure MitsubishiECUMapIncludesAWC; + /// + /// Renault exposes ev controller. + /// [Test] procedure RenaultExposesEvController; end; [TestFixture] TExtras2DecoderTests = class public + /// + /// Renault decodes calibration id. + /// [Test] procedure RenaultDecodesCalibrationId; + /// + /// Volvo decodes pno code. + /// [Test] procedure VolvoDecodesPnoCode; + /// + /// Tesla decodes firmware version. + /// [Test] procedure TeslaDecodesFirmwareVersion; + /// + /// Suzuki decodes chassis code. + /// [Test] procedure SuzukiDecodesChassisCode; + /// + /// Mitsubishi decodes chassis code. + /// [Test] procedure MitsubishiDecodesChassisCode; end; [TestFixture] TUniversalCatalogGrowthTests = class public + /// + /// Obd pid catalog includes new entries. + /// [Test] procedure ObdPidCatalogIncludesNewEntries; + /// + /// Dtc catalog includes p0017 and p2002. + /// [Test] procedure DtcCatalogIncludesP0017AndP2002; end; @@ -62,6 +122,9 @@ implementation OBD.OEM.Suzuki, OBD.OEM.Mitsubishi, OBD.OEM.Stellantis, OBD.OEM.Catalog.JSON, OBD.OEM.DTC; +//------------------------------------------------------------------------------ +// MAKE VIN +//------------------------------------------------------------------------------ function MakeVin(const Prefix: string): string; begin Result := (Prefix + '00000000000000'); @@ -71,6 +134,10 @@ function MakeVin(const Prefix: string): string; //============================================================================== // VIN routing //============================================================================== + +//------------------------------------------------------------------------------ +// RENAULT CLAIMS VR1 NOT STELLANTIS +//------------------------------------------------------------------------------ procedure TExtras2VINTests.RenaultClaimsVR1NotStellantis; var Renault, Stellantis: IOBDOEMExtension; @@ -83,6 +150,9 @@ procedure TExtras2VINTests.RenaultClaimsVR1NotStellantis; 'Stellantis must no longer claim VR1'); end; +//------------------------------------------------------------------------------ +// RENAULT MATCHES DACIA AND ALPINE +//------------------------------------------------------------------------------ procedure TExtras2VINTests.RenaultMatchesDaciaAndAlpine; var Ext: IOBDOEMExtension; @@ -93,6 +163,9 @@ procedure TExtras2VINTests.RenaultMatchesDaciaAndAlpine; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('KNM')), 'Renault Korea'); end; +//------------------------------------------------------------------------------ +// VOLVO MATCHES YV1 AND CHINA BUILT +//------------------------------------------------------------------------------ procedure TExtras2VINTests.VolvoMatchesYV1AndChinaBuilt; var Ext: IOBDOEMExtension; @@ -104,6 +177,9 @@ procedure TExtras2VINTests.VolvoMatchesYV1AndChinaBuilt; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('JF1')), 'should not claim Subaru'); end; +//------------------------------------------------------------------------------ +// TESLA MATCHES ALL FACTORIES +//------------------------------------------------------------------------------ procedure TExtras2VINTests.TeslaMatchesAllFactories; var Ext: IOBDOEMExtension; @@ -115,6 +191,9 @@ procedure TExtras2VINTests.TeslaMatchesAllFactories; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('7SA')), 'Austin'); end; +//------------------------------------------------------------------------------ +// SUZUKI MATCHES MARUTI +//------------------------------------------------------------------------------ procedure TExtras2VINTests.SuzukiMatchesMaruti; var Ext: IOBDOEMExtension; @@ -125,6 +204,9 @@ procedure TExtras2VINTests.SuzukiMatchesMaruti; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('TSM')), 'Suzuki Esztergom'); end; +//------------------------------------------------------------------------------ +// MITSUBISHI MATCHES DSMHISTORICAL +//------------------------------------------------------------------------------ procedure TExtras2VINTests.MitsubishiMatchesDSMHistorical; var Ext: IOBDOEMExtension; @@ -135,6 +217,9 @@ procedure TExtras2VINTests.MitsubishiMatchesDSMHistorical; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('MMB')), 'Laem Chabang Thailand'); end; +//------------------------------------------------------------------------------ +// STELLANTIS NO LONGER CLAIMS VR1 +//------------------------------------------------------------------------------ procedure TExtras2VINTests.StellantisNoLongerClaimsVR1; var Stellantis: IOBDOEMExtension; @@ -150,6 +235,10 @@ procedure TExtras2VINTests.StellantisNoLongerClaimsVR1; //============================================================================== // Catalog spot-checks //============================================================================== + +//------------------------------------------------------------------------------ +// RENAULT ECUMAP HAS UCH +//------------------------------------------------------------------------------ procedure TExtras2CatalogTests.RenaultECUMapHasUCH; var Ext: IOBDOEMExtension; @@ -163,6 +252,9 @@ procedure TExtras2CatalogTests.RenaultECUMapHasUCH; Assert.IsTrue(Found, 'Renault must expose UCH at 0x760'); end; +//------------------------------------------------------------------------------ +// VOLVO HEARTBEAT IS EXTENDED +//------------------------------------------------------------------------------ procedure TExtras2CatalogTests.VolvoHeartbeatIsExtended; var Ext: IOBDOEMExtension; @@ -173,6 +265,9 @@ procedure TExtras2CatalogTests.VolvoHeartbeatIsExtended; 'Volvo VIDA uses a 5-second heartbeat'); end; +//------------------------------------------------------------------------------ +// TESLA ECUMAP INCLUDES AUTOPILOT +//------------------------------------------------------------------------------ procedure TExtras2CatalogTests.TeslaECUMapIncludesAutopilot; var Ext: IOBDOEMExtension; @@ -186,6 +281,9 @@ procedure TExtras2CatalogTests.TeslaECUMapIncludesAutopilot; Assert.IsTrue(HasAP, 'Tesla must expose the Autopilot ECU'); end; +//------------------------------------------------------------------------------ +// SUZUKI HAS SEED KEY STARTER +//------------------------------------------------------------------------------ procedure TExtras2CatalogTests.SuzukiHasSeedKeyStarter; var Ext: IOBDOEMExtension; @@ -194,6 +292,9 @@ procedure TExtras2CatalogTests.SuzukiHasSeedKeyStarter; Assert.IsNotNull(Ext.SeedKeyRegistry.Find($01)); end; +//------------------------------------------------------------------------------ +// MITSUBISHI ECUMAP INCLUDES AWC +//------------------------------------------------------------------------------ procedure TExtras2CatalogTests.MitsubishiECUMapIncludesAWC; var Ext: IOBDOEMExtension; @@ -208,6 +309,9 @@ procedure TExtras2CatalogTests.MitsubishiECUMapIncludesAWC; 'Mitsubishi must expose the AWC for Outlander PHEV diagnostics'); end; +//------------------------------------------------------------------------------ +// RENAULT EXPOSES EV CONTROLLER +//------------------------------------------------------------------------------ procedure TExtras2CatalogTests.RenaultExposesEvController; var Ext: IOBDOEMExtension; @@ -224,6 +328,10 @@ procedure TExtras2CatalogTests.RenaultExposesEvController; //============================================================================== // Decoder spot-checks //============================================================================== + +//------------------------------------------------------------------------------ +// RENAULT DECODES CALIBRATION ID +//------------------------------------------------------------------------------ procedure TExtras2DecoderTests.RenaultDecodesCalibrationId; var Ext: IOBDOEMExtension; @@ -234,6 +342,9 @@ procedure TExtras2DecoderTests.RenaultDecodesCalibrationId; Assert.IsTrue(Pos('renault_calibration_id', Output) > 0); end; +//------------------------------------------------------------------------------ +// VOLVO DECODES PNO CODE +//------------------------------------------------------------------------------ procedure TExtras2DecoderTests.VolvoDecodesPnoCode; var Ext: IOBDOEMExtension; @@ -244,6 +355,9 @@ procedure TExtras2DecoderTests.VolvoDecodesPnoCode; Assert.IsTrue(Pos('volvo_pno_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// TESLA DECODES FIRMWARE VERSION +//------------------------------------------------------------------------------ procedure TExtras2DecoderTests.TeslaDecodesFirmwareVersion; var Ext: IOBDOEMExtension; @@ -255,6 +369,9 @@ procedure TExtras2DecoderTests.TeslaDecodesFirmwareVersion; Assert.IsTrue(Pos('2024.32.5', Output) > 0); end; +//------------------------------------------------------------------------------ +// SUZUKI DECODES CHASSIS CODE +//------------------------------------------------------------------------------ procedure TExtras2DecoderTests.SuzukiDecodesChassisCode; var Ext: IOBDOEMExtension; @@ -265,6 +382,9 @@ procedure TExtras2DecoderTests.SuzukiDecodesChassisCode; Assert.IsTrue(Pos('suzuki_chassis_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// MITSUBISHI DECODES CHASSIS CODE +//------------------------------------------------------------------------------ procedure TExtras2DecoderTests.MitsubishiDecodesChassisCode; var Ext: IOBDOEMExtension; @@ -278,6 +398,10 @@ procedure TExtras2DecoderTests.MitsubishiDecodesChassisCode; //============================================================================== // Universal catalog growth //============================================================================== + +//------------------------------------------------------------------------------ +// RESOLVE CATALOG +//------------------------------------------------------------------------------ function ResolveCatalog(const FileName: string): string; var Candidate: string; @@ -289,6 +413,9 @@ function ResolveCatalog(const FileName: string): string; Result := TPath.GetFullPath(Candidate); end; +//------------------------------------------------------------------------------ +// OBD PID CATALOG INCLUDES NEW ENTRIES +//------------------------------------------------------------------------------ procedure TUniversalCatalogGrowthTests.ObdPidCatalogIncludesNewEntries; var Cat: TOBDOEMJSONCatalog; @@ -307,6 +434,9 @@ procedure TUniversalCatalogGrowthTests.ObdPidCatalogIncludesNewEntries; end; end; +//------------------------------------------------------------------------------ +// DTC CATALOG INCLUDES P0017 AND P2002 +//------------------------------------------------------------------------------ procedure TUniversalCatalogGrowthTests.DtcCatalogIncludesP0017AndP2002; var Cat: TOBDDtcCatalog; diff --git a/tests/Tests.OEM.GoldenCheck.pas b/tests/Tests.OEM.GoldenCheck.pas index f6de62bb..4121a82c 100644 --- a/tests/Tests.OEM.GoldenCheck.pas +++ b/tests/Tests.OEM.GoldenCheck.pas @@ -13,9 +13,21 @@ interface [TestFixture] TGoldenCheckHelperTests = class public + /// + /// Reports empty list when all pass. + /// [Test] procedure ReportsEmptyListWhenAllPass; + /// + /// Reports failure on missing substring. + /// [Test] procedure ReportsFailureOnMissingSubstring; + /// + /// Reports failure on empty output. + /// [Test] procedure ReportsFailureOnEmptyOutput; + /// + /// Empty substring accepts any non empty. + /// [Test] procedure EmptySubstringAcceptsAnyNonEmpty; end; @@ -27,9 +39,21 @@ TGoldenCheckHelperTests = class [TestFixture] TPerOEMGoldenTests = class public + /// + /// V w golden vectors. + /// [Test] procedure VWGoldenVectors; + /// + /// B m w golden vectors. + /// [Test] procedure BMWGoldenVectors; + /// + /// Mercedes golden vectors. + /// [Test] procedure MercedesGoldenVectors; + /// + /// Ford golden vectors. + /// [Test] procedure FordGoldenVectors; end; @@ -40,6 +64,9 @@ implementation OBD.OEM, OBD.OEM.GoldenCheck, OBD.OEM.VW, OBD.OEM.BMW, OBD.OEM.Mercedes, OBD.OEM.Ford; +//------------------------------------------------------------------------------ +// REPORT FAILURES +//------------------------------------------------------------------------------ procedure ReportFailures(const Failures: TArray); var F: TOBDGoldenFailure; @@ -55,6 +82,10 @@ procedure ReportFailures(const Failures: TArray); //============================================================================== // Helper unit tests //============================================================================== + +//------------------------------------------------------------------------------ +// REPORTS EMPTY LIST WHEN ALL PASS +//------------------------------------------------------------------------------ procedure TGoldenCheckHelperTests.ReportsEmptyListWhenAllPass; var Failures: TArray; @@ -66,6 +97,9 @@ procedure TGoldenCheckHelperTests.ReportsEmptyListWhenAllPass; Assert.AreEqual(0, Length(Failures)); end; +//------------------------------------------------------------------------------ +// REPORTS FAILURE ON MISSING SUBSTRING +//------------------------------------------------------------------------------ procedure TGoldenCheckHelperTests.ReportsFailureOnMissingSubstring; var Failures: TArray; @@ -78,6 +112,9 @@ procedure TGoldenCheckHelperTests.ReportsFailureOnMissingSubstring; Assert.IsTrue(Pos('failing on purpose', Failures[0].Reason) > 0); end; +//------------------------------------------------------------------------------ +// REPORTS FAILURE ON EMPTY OUTPUT +//------------------------------------------------------------------------------ procedure TGoldenCheckHelperTests.ReportsFailureOnEmptyOutput; var Failures: TArray; @@ -98,6 +135,9 @@ procedure TGoldenCheckHelperTests.ReportsFailureOnEmptyOutput; Assert.AreEqual(0, Length(Failures)); end; +//------------------------------------------------------------------------------ +// EMPTY SUBSTRING ACCEPTS ANY NON EMPTY +//------------------------------------------------------------------------------ procedure TGoldenCheckHelperTests.EmptySubstringAcceptsAnyNonEmpty; var Failures: TArray; @@ -111,6 +151,10 @@ procedure TGoldenCheckHelperTests.EmptySubstringAcceptsAnyNonEmpty; //============================================================================== // Per-OEM golden vectors //============================================================================== + +//------------------------------------------------------------------------------ +// VWGOLDEN VECTORS +//------------------------------------------------------------------------------ procedure TPerOEMGoldenTests.VWGoldenVectors; begin ReportFailures(CheckGoldenVectors(TOBDOEMExtensionVW.Create, [ @@ -123,6 +167,9 @@ procedure TPerOEMGoldenTests.VWGoldenVectors; ])); end; +//------------------------------------------------------------------------------ +// BMWGOLDEN VECTORS +//------------------------------------------------------------------------------ procedure TPerOEMGoldenTests.BMWGoldenVectors; begin ReportFailures(CheckGoldenVectors(TOBDOEMExtensionBMW.Create, [ @@ -135,6 +182,9 @@ procedure TPerOEMGoldenTests.BMWGoldenVectors; ])); end; +//------------------------------------------------------------------------------ +// MERCEDES GOLDEN VECTORS +//------------------------------------------------------------------------------ procedure TPerOEMGoldenTests.MercedesGoldenVectors; begin ReportFailures(CheckGoldenVectors(TOBDOEMExtensionMercedes.Create, [ @@ -147,6 +197,9 @@ procedure TPerOEMGoldenTests.MercedesGoldenVectors; ])); end; +//------------------------------------------------------------------------------ +// FORD GOLDEN VECTORS +//------------------------------------------------------------------------------ procedure TPerOEMGoldenTests.FordGoldenVectors; begin ReportFailures(CheckGoldenVectors(TOBDOEMExtensionFord.Create, [ diff --git a/tests/Tests.OEM.HD.pas b/tests/Tests.OEM.HD.pas index 5840aa38..26236a52 100644 --- a/tests/Tests.OEM.HD.pas +++ b/tests/Tests.OEM.HD.pas @@ -16,43 +16,112 @@ interface [TestFixture] THDSpnFmiHelperTests = class public + /// + /// Format s p n f m i builds canonical form. + /// [Test] procedure FormatSPNFMIBuildsCanonicalForm; + /// + /// Parse d m1 d t c extracts s p n and f m i. + /// [Test] procedure ParseDM1DTCExtractsSPNAndFMI; + /// + /// Parse d m1 returns empty on truncated. + /// [Test] procedure ParseDM1ReturnsEmptyOnTruncated; end; [TestFixture] THDVINRoutingTests = class public + /// + /// Cummins has no v i n match. + /// [Test] procedure CumminsHasNoVINMatch; + /// + /// Detroit has no v i n match. + /// [Test] procedure DetroitHasNoVINMatch; + /// + /// P a c c a r matches peterbilt and kenworth. + /// [Test] procedure PACCARMatchesPeterbiltAndKenworth; + /// + /// P a c c a r matches d a f. + /// [Test] procedure PACCARMatchesDAF; + /// + /// Volvo trucks matches mack and renault trucks. + /// [Test] procedure VolvoTrucksMatchesMackAndRenaultTrucks; + /// + /// Volvo trucks does not claim volvo cars w m i. + /// [Test] procedure VolvoTrucksDoesNotClaimVolvoCarsWMI; + /// + /// Scania matches sweden and brazil. + /// [Test] procedure ScaniaMatchesSwedenAndBrazil; + /// + /// M a n matches w m a. + /// [Test] procedure MANMatchesWMA; end; [TestFixture] THDCatalogTests = class public + /// + /// Cummins exposes engine at j1939 address0. + /// [Test] procedure CumminsExposesEngineAtJ1939Address0; + /// + /// Detroit exposes aftertreatment e c us. + /// [Test] procedure DetroitExposesAftertreatmentECUs; + /// + /// P a c c a r session heartbeat is3000ms. + /// [Test] procedure PACCARSessionHeartbeatIs3000ms; + /// + /// Volvo trucks exposes i shift and m i d. + /// [Test] procedure VolvoTrucksExposesIShiftAndMID; + /// + /// Scania exposes opticruise. + /// [Test] procedure ScaniaExposesOpticruise; + /// + /// M a n exposes pri tarder retarder. + /// [Test] procedure MANExposesPriTarderRetarder; + /// + /// All h d extensions resolve by key. + /// [Test] procedure AllHDExtensionsResolveByKey; end; [TestFixture] THDDecoderTests = class public + /// + /// Cummins decodes engine serial. + /// [Test] procedure CumminsDecodesEngineSerial; + /// + /// P a c c a r decodes chassis code. + /// [Test] procedure PACCARDecodesChassisCode; + /// + /// Volvo trucks decodes chassis code. + /// [Test] procedure VolvoTrucksDecodesChassisCode; + /// + /// Scania decodes chassis number. + /// [Test] procedure ScaniaDecodesChassisNumber; + /// + /// M a n decodes chassis code. + /// [Test] procedure MANDecodesChassisCode; end; @@ -64,6 +133,9 @@ implementation OBD.OEM.Cummins, OBD.OEM.DetroitDiesel, OBD.OEM.PACCAR, OBD.OEM.VolvoTrucks, OBD.OEM.Scania, OBD.OEM.MAN, OBD.OEM.Volvo; +//------------------------------------------------------------------------------ +// MAKE VIN +//------------------------------------------------------------------------------ function MakeVin(const Prefix: string): string; begin Result := (Prefix + '00000000000000'); @@ -73,12 +145,19 @@ function MakeVin(const Prefix: string): string; //============================================================================== // SPN-FMI helpers //============================================================================== + +//------------------------------------------------------------------------------ +// FORMAT SPNFMIBUILDS CANONICAL FORM +//------------------------------------------------------------------------------ procedure THDSpnFmiHelperTests.FormatSPNFMIBuildsCanonicalForm; begin Assert.AreEqual('SPN0094-FMI4', FormatSPNFMI(94, 4)); Assert.AreEqual('SPN3251-FMI16', FormatSPNFMI(3251, 16)); end; +//------------------------------------------------------------------------------ +// PARSE DM1 DTCEXTRACTS SPNAND FMI +//------------------------------------------------------------------------------ procedure THDSpnFmiHelperTests.ParseDM1DTCExtractsSPNAndFMI; var Bytes: TBytes; @@ -92,6 +171,9 @@ procedure THDSpnFmiHelperTests.ParseDM1DTCExtractsSPNAndFMI; Assert.AreEqual('SPN0148-FMI4', ParseDM1DTC(Bytes, 0)); end; +//------------------------------------------------------------------------------ +// PARSE DM1 RETURNS EMPTY ON TRUNCATED +//------------------------------------------------------------------------------ procedure THDSpnFmiHelperTests.ParseDM1ReturnsEmptyOnTruncated; begin Assert.AreEqual('', ParseDM1DTC(TBytes.Create($94, $00), 0)); @@ -100,6 +182,10 @@ procedure THDSpnFmiHelperTests.ParseDM1ReturnsEmptyOnTruncated; //============================================================================== // VIN routing //============================================================================== + +//------------------------------------------------------------------------------ +// CUMMINS HAS NO VINMATCH +//------------------------------------------------------------------------------ procedure THDVINRoutingTests.CumminsHasNoVINMatch; var Ext: IOBDOEMExtension; @@ -110,6 +196,9 @@ procedure THDVINRoutingTests.CumminsHasNoVINMatch; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('JTD'))); end; +//------------------------------------------------------------------------------ +// DETROIT HAS NO VINMATCH +//------------------------------------------------------------------------------ procedure THDVINRoutingTests.DetroitHasNoVINMatch; var Ext: IOBDOEMExtension; @@ -118,6 +207,9 @@ procedure THDVINRoutingTests.DetroitHasNoVINMatch; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('4V4'))); end; +//------------------------------------------------------------------------------ +// PACCARMATCHES PETERBILT AND KENWORTH +//------------------------------------------------------------------------------ procedure THDVINRoutingTests.PACCARMatchesPeterbiltAndKenworth; var Ext: IOBDOEMExtension; @@ -128,6 +220,9 @@ procedure THDVINRoutingTests.PACCARMatchesPeterbiltAndKenworth; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('5KJ')), 'Peterbilt Mexico'); end; +//------------------------------------------------------------------------------ +// PACCARMATCHES DAF +//------------------------------------------------------------------------------ procedure THDVINRoutingTests.PACCARMatchesDAF; var Ext: IOBDOEMExtension; @@ -136,6 +231,9 @@ procedure THDVINRoutingTests.PACCARMatchesDAF; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('XLR')), 'DAF Eindhoven'); end; +//------------------------------------------------------------------------------ +// VOLVO TRUCKS MATCHES MACK AND RENAULT TRUCKS +//------------------------------------------------------------------------------ procedure THDVINRoutingTests.VolvoTrucksMatchesMackAndRenaultTrucks; var Ext: IOBDOEMExtension; @@ -147,6 +245,9 @@ procedure THDVINRoutingTests.VolvoTrucksMatchesMackAndRenaultTrucks; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('VG6')), 'Renault Trucks Lyon'); end; +//------------------------------------------------------------------------------ +// VOLVO TRUCKS DOES NOT CLAIM VOLVO CARS WMI +//------------------------------------------------------------------------------ procedure THDVINRoutingTests.VolvoTrucksDoesNotClaimVolvoCarsWMI; var Trucks, Cars: IOBDOEMExtension; @@ -159,6 +260,9 @@ procedure THDVINRoutingTests.VolvoTrucksDoesNotClaimVolvoCarsWMI; 'YV1 is Volvo Cars'); end; +//------------------------------------------------------------------------------ +// SCANIA MATCHES SWEDEN AND BRAZIL +//------------------------------------------------------------------------------ procedure THDVINRoutingTests.ScaniaMatchesSwedenAndBrazil; var Ext: IOBDOEMExtension; @@ -169,6 +273,9 @@ procedure THDVINRoutingTests.ScaniaMatchesSwedenAndBrazil; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('9BS')), 'Scania São Bernardo do Campo'); end; +//------------------------------------------------------------------------------ +// MANMATCHES WMA +//------------------------------------------------------------------------------ procedure THDVINRoutingTests.MANMatchesWMA; var Ext: IOBDOEMExtension; @@ -181,6 +288,10 @@ procedure THDVINRoutingTests.MANMatchesWMA; //============================================================================== // Catalog spot-checks //============================================================================== + +//------------------------------------------------------------------------------ +// CUMMINS EXPOSES ENGINE AT J1939 ADDRESS0 +//------------------------------------------------------------------------------ procedure THDCatalogTests.CumminsExposesEngineAtJ1939Address0; var Ext: IOBDOEMExtension; @@ -195,6 +306,9 @@ procedure THDCatalogTests.CumminsExposesEngineAtJ1939Address0; 'Cummins must expose engine ECM at J1939 source address 0'); end; +//------------------------------------------------------------------------------ +// DETROIT EXPOSES AFTERTREATMENT ECUS +//------------------------------------------------------------------------------ procedure THDCatalogTests.DetroitExposesAftertreatmentECUs; var Ext: IOBDOEMExtension; @@ -212,6 +326,9 @@ procedure THDCatalogTests.DetroitExposesAftertreatmentECUs; 'Detroit must expose both DPF + SCR aftertreatment ECUs'); end; +//------------------------------------------------------------------------------ +// PACCARSESSION HEARTBEAT IS3000MS +//------------------------------------------------------------------------------ procedure THDCatalogTests.PACCARSessionHeartbeatIs3000ms; var Ext: IOBDOEMExtension; @@ -222,6 +339,9 @@ procedure THDCatalogTests.PACCARSessionHeartbeatIs3000ms; 'HD negotiator runs 3000 ms heartbeat to coexist with J1939 broadcast'); end; +//------------------------------------------------------------------------------ +// VOLVO TRUCKS EXPOSES ISHIFT AND MID +//------------------------------------------------------------------------------ procedure THDCatalogTests.VolvoTrucksExposesIShiftAndMID; var Ext: IOBDOEMExtension; @@ -239,6 +359,9 @@ procedure THDCatalogTests.VolvoTrucksExposesIShiftAndMID; Assert.IsTrue(HasMID140, 'Volvo Trucks must expose MID 140'); end; +//------------------------------------------------------------------------------ +// SCANIA EXPOSES OPTICRUISE +//------------------------------------------------------------------------------ procedure THDCatalogTests.ScaniaExposesOpticruise; var Ext: IOBDOEMExtension; @@ -252,6 +375,9 @@ procedure THDCatalogTests.ScaniaExposesOpticruise; Assert.IsTrue(HasOPC, 'Scania must expose OPC (Opticruise)'); end; +//------------------------------------------------------------------------------ +// MANEXPOSES PRI TARDER RETARDER +//------------------------------------------------------------------------------ procedure THDCatalogTests.MANExposesPriTarderRetarder; var Ext: IOBDOEMExtension; @@ -265,6 +391,9 @@ procedure THDCatalogTests.MANExposesPriTarderRetarder; Assert.IsTrue(HasRetarder, 'MAN must expose the PriTarder retarder'); end; +//------------------------------------------------------------------------------ +// ALL HDEXTENSIONS RESOLVE BY KEY +//------------------------------------------------------------------------------ procedure THDCatalogTests.AllHDExtensionsResolveByKey; begin Assert.IsNotNull(TOBDOEMRegistry.FindByKey('CUMMINS')); @@ -278,6 +407,10 @@ procedure THDCatalogTests.AllHDExtensionsResolveByKey; //============================================================================== // Decoder spot-checks //============================================================================== + +//------------------------------------------------------------------------------ +// CUMMINS DECODES ENGINE SERIAL +//------------------------------------------------------------------------------ procedure THDDecoderTests.CumminsDecodesEngineSerial; var Ext: IOBDOEMExtension; @@ -288,6 +421,9 @@ procedure THDDecoderTests.CumminsDecodesEngineSerial; Assert.IsTrue(Pos('cummins_engine_serial', Output) > 0); end; +//------------------------------------------------------------------------------ +// PACCARDECODES CHASSIS CODE +//------------------------------------------------------------------------------ procedure THDDecoderTests.PACCARDecodesChassisCode; var Ext: IOBDOEMExtension; @@ -298,6 +434,9 @@ procedure THDDecoderTests.PACCARDecodesChassisCode; Assert.IsTrue(Pos('paccar_chassis_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// VOLVO TRUCKS DECODES CHASSIS CODE +//------------------------------------------------------------------------------ procedure THDDecoderTests.VolvoTrucksDecodesChassisCode; var Ext: IOBDOEMExtension; @@ -308,6 +447,9 @@ procedure THDDecoderTests.VolvoTrucksDecodesChassisCode; Assert.IsTrue(Pos('volvo_truck_chassis_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// SCANIA DECODES CHASSIS NUMBER +//------------------------------------------------------------------------------ procedure THDDecoderTests.ScaniaDecodesChassisNumber; var Ext: IOBDOEMExtension; @@ -318,6 +460,9 @@ procedure THDDecoderTests.ScaniaDecodesChassisNumber; Assert.IsTrue(Pos('scania_chassis_number', Output) > 0); end; +//------------------------------------------------------------------------------ +// MANDECODES CHASSIS CODE +//------------------------------------------------------------------------------ procedure THDDecoderTests.MANDecodesChassisCode; var Ext: IOBDOEMExtension; diff --git a/tests/Tests.OEM.KeyAdaptation.BMW.pas b/tests/Tests.OEM.KeyAdaptation.BMW.pas new file mode 100644 index 00000000..056a9fa2 --- /dev/null +++ b/tests/Tests.OEM.KeyAdaptation.BMW.pas @@ -0,0 +1,215 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.OEM.KeyAdaptation.BMW.pas +// CONTENTS : Tests for OBD.OEM.KeyAdaptation.BMW +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.OEM.KeyAdaptation.BMW; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TBMWKeyAdaptationTests = class + public + /// + /// Slot validation per generation. + /// + [Test] procedure SlotValidationPerGeneration; + /// + /// E w s round trip. + /// + [Test] procedure EWSRoundTrip; + /// + /// C a s round trip. + /// + [Test] procedure CASRoundTrip; + /// + /// F e m round trip. + /// + [Test] procedure FEMRoundTrip; + /// + /// E w s bad slot raises. + /// + [Test] procedure EWSBadSlotRaises; + /// + /// F e m bad slot raises. + /// + [Test] procedure FEMBadSlotRaises; + /// + /// F e m bad settings bank raises. + /// + [Test] procedure FEMBadSettingsBankRaises; + /// + /// Decode wrong length raises. + /// + [Test] procedure DecodeWrongLengthRaises; + /// + /// Digital key serial must be seven bytes. + /// + [Test] procedure DigitalKeySerialMustBeSevenBytes; + end; + +implementation + +uses + System.SysUtils, OBD.OEM.KeyAdaptation.BMW; + +//------------------------------------------------------------------------------ +// SLOT VALIDATION PER GENERATION +//------------------------------------------------------------------------------ +procedure TBMWKeyAdaptationTests.SlotValidationPerGeneration; +begin + Assert.IsTrue(ValidateSlotIndex(bmwgEWS, 9)); + Assert.IsFalse(ValidateSlotIndex(bmwgEWS, 10)); + Assert.IsTrue(ValidateSlotIndex(bmwgCAS, 9)); + Assert.IsTrue(ValidateSlotIndex(bmwgFEMBDC, 7)); + Assert.IsFalse(ValidateSlotIndex(bmwgFEMBDC, 8)); +end; + +//------------------------------------------------------------------------------ +// EWSROUND TRIP +//------------------------------------------------------------------------------ +procedure TBMWKeyAdaptationTests.EWSRoundTrip; +var + In_, Out_: TBMWKeyDataE; + Bytes: TBytes; +begin + In_.SlotIndex := 3; + In_.KeyEnabled := True; + In_.KeyCutCode := TBytes.Create($AA, $BB, $CC, $DD); + In_.UsageCounter := 1234; + In_.Reserved := TBytes.Create($00); // padded by encoder + Bytes := EncodeKeyDataE(In_); + Assert.AreEqual(16, Length(Bytes)); + Out_ := DecodeKeyDataE(Bytes); + Assert.AreEqual(Integer(3), Integer(Out_.SlotIndex)); + Assert.IsTrue(Out_.KeyEnabled); + Assert.AreEqual(Integer($AA), Integer(Out_.KeyCutCode[0])); + Assert.AreEqual(Integer($DD), Integer(Out_.KeyCutCode[3])); + Assert.AreEqual(Word(1234), Out_.UsageCounter); +end; + +//------------------------------------------------------------------------------ +// CASROUND TRIP +//------------------------------------------------------------------------------ +procedure TBMWKeyAdaptationTests.CASRoundTrip; +var + In_, Out_: TBMWKeyDataCas; + Bytes: TBytes; +begin + In_.SlotIndex := 2; + In_.KeyEnabled := True; + In_.KeyCutCode := TBytes.Create($11, $22, $33, $44); + In_.RemoteId := UInt32($DEADBEEF); + In_.KMReadingThousands := 87; + Bytes := EncodeKeyDataCas(In_); + Assert.AreEqual(16, Length(Bytes)); + Out_ := DecodeKeyDataCas(Bytes); + Assert.AreEqual(UInt32($DEADBEEF), Out_.RemoteId); + Assert.AreEqual(Word(87), Out_.KMReadingThousands); + Assert.AreEqual(Integer($11), Integer(Out_.KeyCutCode[0])); +end; + +//------------------------------------------------------------------------------ +// FEMROUND TRIP +//------------------------------------------------------------------------------ +procedure TBMWKeyAdaptationTests.FEMRoundTrip; +var + In_, Out_: TBMWKeyDataFem; + Bytes: TBytes; +begin + In_.SlotIndex := 5; + In_.KeyEnabled := True; + In_.PersonalSettingsBank := 2; + In_.KeyCutCode := TBytes.Create($11, $22, $33, $44); + In_.DigitalKeySerial := TBytes.Create($AA, $BB, $CC, $DD, $EE, $FF, $11); + In_.UsageCounter := UInt32(1500); + In_.LastKMReading := UInt32(123456); + Bytes := EncodeKeyDataFem(In_); + Assert.AreEqual(32, Length(Bytes)); + Out_ := DecodeKeyDataFem(Bytes); + Assert.AreEqual(Integer(5), Integer(Out_.SlotIndex)); + Assert.AreEqual(Integer(2), Integer(Out_.PersonalSettingsBank)); + Assert.AreEqual(UInt32(1500), Out_.UsageCounter); + Assert.AreEqual(UInt32(123456), Out_.LastKMReading); + Assert.AreEqual(Integer($AA), Integer(Out_.DigitalKeySerial[0])); + Assert.AreEqual(Integer($11), Integer(Out_.DigitalKeySerial[6])); +end; + +//------------------------------------------------------------------------------ +// EWSBAD SLOT RAISES +//------------------------------------------------------------------------------ +procedure TBMWKeyAdaptationTests.EWSBadSlotRaises; +var + Key: TBMWKeyDataE; +begin + Key.SlotIndex := 10; + Key.KeyCutCode := TBytes.Create($00, $00, $00, $00); + Assert.WillRaise(procedure begin EncodeKeyDataE(Key); end, EOBDBMWKey); +end; + +//------------------------------------------------------------------------------ +// FEMBAD SLOT RAISES +//------------------------------------------------------------------------------ +procedure TBMWKeyAdaptationTests.FEMBadSlotRaises; +var + Key: TBMWKeyDataFem; +begin + Key.SlotIndex := 8; + Key.PersonalSettingsBank := 1; + Key.KeyCutCode := TBytes.Create($00, $00, $00, $00); + Key.DigitalKeySerial := TBytes.Create($00, $00, $00, $00, $00, $00, $00); + Assert.WillRaise(procedure begin EncodeKeyDataFem(Key); end, EOBDBMWKey); +end; + +//------------------------------------------------------------------------------ +// FEMBAD SETTINGS BANK RAISES +//------------------------------------------------------------------------------ +procedure TBMWKeyAdaptationTests.FEMBadSettingsBankRaises; +var + Key: TBMWKeyDataFem; +begin + Key.SlotIndex := 0; + Key.PersonalSettingsBank := 5; + Key.KeyCutCode := TBytes.Create($00, $00, $00, $00); + Key.DigitalKeySerial := TBytes.Create($00, $00, $00, $00, $00, $00, $00); + Assert.WillRaise(procedure begin EncodeKeyDataFem(Key); end, EOBDBMWKey); +end; + +//------------------------------------------------------------------------------ +// DECODE WRONG LENGTH RAISES +//------------------------------------------------------------------------------ +procedure TBMWKeyAdaptationTests.DecodeWrongLengthRaises; +begin + Assert.WillRaise( + procedure begin DecodeKeyDataE(TBytes.Create($00, $01)); end, + EOBDBMWKey); +end; + +//------------------------------------------------------------------------------ +// DIGITAL KEY SERIAL MUST BE SEVEN BYTES +//------------------------------------------------------------------------------ +procedure TBMWKeyAdaptationTests.DigitalKeySerialMustBeSevenBytes; +var + Key: TBMWKeyDataFem; +begin + Key.SlotIndex := 0; + Key.PersonalSettingsBank := 1; + Key.KeyCutCode := TBytes.Create($00, $00, $00, $00); + Key.DigitalKeySerial := TBytes.Create($00, $00, $00); // wrong length + Assert.WillRaise(procedure begin EncodeKeyDataFem(Key); end, EOBDBMWKey); +end; + +initialization + TDUnitX.RegisterTestFixture(TBMWKeyAdaptationTests); + +end. diff --git a/tests/Tests.OEM.KeyAdaptation.Ford.pas b/tests/Tests.OEM.KeyAdaptation.Ford.pas new file mode 100644 index 00000000..57fab513 --- /dev/null +++ b/tests/Tests.OEM.KeyAdaptation.Ford.pas @@ -0,0 +1,170 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.OEM.KeyAdaptation.Ford.pas +// CONTENTS : Tests for OBD.OEM.KeyAdaptation.Ford +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.OEM.KeyAdaptation.Ford; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TFordPATSTests = class + public + /// + /// Request round trip. + /// + [Test] procedure RequestRoundTrip; + /// + /// Request rejects bad v i n. + /// + [Test] procedure RequestRejectsBadVIN; + /// + /// Request decode bad length raises. + /// + [Test] procedure RequestDecodeBadLengthRaises; + /// + /// Status round trip. + /// + [Test] procedure StatusRoundTrip; + /// + /// Status decode bad length raises. + /// + [Test] procedure StatusDecodeBadLengthRaises; + /// + /// F150 is open. + /// + [Test] procedure F150IsOpen; + /// + /// Mach e is gateway locked. + /// + [Test] procedure MachEIsGatewayLocked; + /// + /// Unknown is gateway locked. + /// + [Test] procedure UnknownIsGatewayLocked; + end; + +implementation + +uses + System.SysUtils, OBD.OEM.KeyAdaptation.Ford; + +//------------------------------------------------------------------------------ +// REQUEST ROUND TRIP +//------------------------------------------------------------------------------ +procedure TFordPATSTests.RequestRoundTrip; +var + In_, Out_: TFordPATSRequest; + Bytes: TBytes; +begin + In_.VIN := '1FTFW1ET8DKE12345'; + In_.Operation := fpoAddKey; + In_.ProgrammerPresentByte := $42; + Bytes := EncodeFordPATSRequest(In_); + Out_ := DecodeFordPATSRequest(Bytes); + Assert.AreEqual('1FTFW1ET8DKE12345', Out_.VIN); + Assert.AreEqual(Ord(fpoAddKey), Ord(Out_.Operation)); + Assert.AreEqual(Integer($42), Integer(Out_.ProgrammerPresentByte)); +end; + +//------------------------------------------------------------------------------ +// REQUEST REJECTS BAD VIN +//------------------------------------------------------------------------------ +procedure TFordPATSTests.RequestRejectsBadVIN; +var + Req: TFordPATSRequest; +begin + Req.VIN := 'TOO-SHORT'; + Req.Operation := fpoStatus; + Req.ProgrammerPresentByte := 0; + Assert.WillRaise( + procedure begin EncodeFordPATSRequest(Req); end, EOBDFordPATS); +end; + +//------------------------------------------------------------------------------ +// REQUEST DECODE BAD LENGTH RAISES +//------------------------------------------------------------------------------ +procedure TFordPATSTests.RequestDecodeBadLengthRaises; +begin + Assert.WillRaise( + procedure begin DecodeFordPATSRequest(TBytes.Create($00, $00, $00)); end, + EOBDFordPATS); +end; + +//------------------------------------------------------------------------------ +// STATUS ROUND TRIP +//------------------------------------------------------------------------------ +procedure TFordPATSTests.StatusRoundTrip; +var + In_, Out_: TFordPATSStatus; + Bytes: TBytes; +begin + In_.KeyCount := 4; + In_.LockoutActive := True; + In_.SecondsRemaining := 600; + In_.PinCodePresent := True; + Bytes := EncodeFordPATSStatus(In_); + Out_ := DecodeFordPATSStatus(Bytes); + Assert.AreEqual(Integer(4), Integer(Out_.KeyCount)); + Assert.IsTrue(Out_.LockoutActive); + Assert.AreEqual(Word(600), Out_.SecondsRemaining); + Assert.IsTrue(Out_.PinCodePresent); +end; + +//------------------------------------------------------------------------------ +// STATUS DECODE BAD LENGTH RAISES +//------------------------------------------------------------------------------ +procedure TFordPATSTests.StatusDecodeBadLengthRaises; +begin + Assert.WillRaise( + procedure begin DecodeFordPATSStatus(TBytes.Create($00, $00, $00, $00)); end, + EOBDFordPATS); +end; + +//------------------------------------------------------------------------------ +// F150 IS OPEN +//------------------------------------------------------------------------------ +procedure TFordPATSTests.F150IsOpen; +var + P: TFordPlatformInfo; +begin + P := FindFordPlatform('p552'); + Assert.AreEqual(Ord(fpaOpen), Ord(P.Access)); +end; + +//------------------------------------------------------------------------------ +// MACH EIS GATEWAY LOCKED +//------------------------------------------------------------------------------ +procedure TFordPATSTests.MachEIsGatewayLocked; +var + P: TFordPlatformInfo; +begin + P := FindFordPlatform('cd542'); + Assert.AreEqual(Ord(fpaGatewayLocked), Ord(P.Access)); +end; + +//------------------------------------------------------------------------------ +// UNKNOWN IS GATEWAY LOCKED +//------------------------------------------------------------------------------ +procedure TFordPATSTests.UnknownIsGatewayLocked; +var + P: TFordPlatformInfo; +begin + P := FindFordPlatform('unknown-chassis'); + Assert.AreEqual(Ord(fpaGatewayLocked), Ord(P.Access)); +end; + +initialization + TDUnitX.RegisterTestFixture(TFordPATSTests); + +end. diff --git a/tests/Tests.OEM.KeyAdaptation.HMG.pas b/tests/Tests.OEM.KeyAdaptation.HMG.pas new file mode 100644 index 00000000..5ca3633e --- /dev/null +++ b/tests/Tests.OEM.KeyAdaptation.HMG.pas @@ -0,0 +1,196 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.OEM.KeyAdaptation.HMG.pas +// CONTENTS : Tests for OBD.OEM.KeyAdaptation.HMG +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.OEM.KeyAdaptation.HMG; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + THMGKeyAdaptationTests = class + public + /// + /// Request round trip. + /// + [Test] procedure RequestRoundTrip; + /// + /// Response round trip. + /// + [Test] procedure ResponseRoundTrip; + /// + /// Request rejects bad v i n. + /// + [Test] procedure RequestRejectsBadVIN; + /// + /// Request rejects bad p i n length. + /// + [Test] procedure RequestRejectsBadPINLength; + /// + /// Request rejects bad key index. + /// + [Test] procedure RequestRejectsBadKeyIndex; + /// + /// Response decode bad length raises. + /// + [Test] procedure ResponseDecodeBadLengthRaises; + /// + /// Platform lookup returns known. + /// + [Test] procedure PlatformLookupReturnsKnown; + /// + /// Platform lookup unknown is certificate required. + /// + [Test] procedure PlatformLookupUnknownIsCertificateRequired; + /// + /// E g m p is gateway locked. + /// + [Test] procedure EGMPIsGatewayLocked; + end; + +implementation + +uses + System.SysUtils, OBD.OEM.KeyAdaptation.HMG; + +//------------------------------------------------------------------------------ +// REQUEST ROUND TRIP +//------------------------------------------------------------------------------ +procedure THMGKeyAdaptationTests.RequestRoundTrip; +var + In_, Out_: THMGKeyRegisterRequest; + Bytes: TBytes; +begin + In_.VIN := 'KMHE241CBKA000001'; + In_.Mode := hkmAddKey; + In_.PIN := '1234'; + In_.KeyIndex := 2; + Bytes := EncodeHMGKeyRegisterRequest(In_); + Out_ := DecodeHMGKeyRegisterRequest(Bytes); + Assert.AreEqual('KMHE241CBKA000001', Out_.VIN); + Assert.AreEqual(Ord(hkmAddKey), Ord(Out_.Mode)); + Assert.AreEqual('1234', Out_.PIN); + Assert.AreEqual(Integer(2), Integer(Out_.KeyIndex)); +end; + +//------------------------------------------------------------------------------ +// RESPONSE ROUND TRIP +//------------------------------------------------------------------------------ +procedure THMGKeyAdaptationTests.ResponseRoundTrip; +var + In_, Out_: THMGKeyRegisterResponse; + Bytes: TBytes; +begin + In_.Mode := hkmReadCount; + In_.Success := True; + In_.KeyCount := 4; + In_.StatusCode := $00; + Bytes := EncodeHMGKeyRegisterResponse(In_); + Out_ := DecodeHMGKeyRegisterResponse(Bytes); + Assert.IsTrue(Out_.Success); + Assert.AreEqual(Integer(4), Integer(Out_.KeyCount)); +end; + +//------------------------------------------------------------------------------ +// REQUEST REJECTS BAD VIN +//------------------------------------------------------------------------------ +procedure THMGKeyAdaptationTests.RequestRejectsBadVIN; +var + Req: THMGKeyRegisterRequest; +begin + Req.VIN := 'TOO-SHORT'; + Req.Mode := hkmAddKey; + Req.PIN := '1234'; + Req.KeyIndex := 0; + Assert.WillRaise( + procedure begin EncodeHMGKeyRegisterRequest(Req); end, EOBDHMGKey); +end; + +//------------------------------------------------------------------------------ +// REQUEST REJECTS BAD PINLENGTH +//------------------------------------------------------------------------------ +procedure THMGKeyAdaptationTests.RequestRejectsBadPINLength; +var + Req: THMGKeyRegisterRequest; +begin + Req.VIN := 'KMHE241CBKA000001'; + Req.Mode := hkmAddKey; + Req.PIN := '12'; // too short + Req.KeyIndex := 0; + Assert.WillRaise( + procedure begin EncodeHMGKeyRegisterRequest(Req); end, EOBDHMGKey); +end; + +//------------------------------------------------------------------------------ +// REQUEST REJECTS BAD KEY INDEX +//------------------------------------------------------------------------------ +procedure THMGKeyAdaptationTests.RequestRejectsBadKeyIndex; +var + Req: THMGKeyRegisterRequest; +begin + Req.VIN := 'KMHE241CBKA000001'; + Req.Mode := hkmAddKey; + Req.PIN := '1234'; + Req.KeyIndex := 8; // out of range + Assert.WillRaise( + procedure begin EncodeHMGKeyRegisterRequest(Req); end, EOBDHMGKey); +end; + +//------------------------------------------------------------------------------ +// RESPONSE DECODE BAD LENGTH RAISES +//------------------------------------------------------------------------------ +procedure THMGKeyAdaptationTests.ResponseDecodeBadLengthRaises; +begin + Assert.WillRaise( + procedure begin DecodeHMGKeyRegisterResponse(TBytes.Create($00)); end, + EOBDHMGKey); +end; + +//------------------------------------------------------------------------------ +// PLATFORM LOOKUP RETURNS KNOWN +//------------------------------------------------------------------------------ +procedure THMGKeyAdaptationTests.PlatformLookupReturnsKnown; +var + P: THMGPlatformInfo; +begin + P := FindHMGPlatform('rb'); + Assert.IsTrue(P.DisplayName.Contains('i20')); + Assert.AreEqual(Ord(hpaOpenWithPIN), Ord(P.Access)); +end; + +//------------------------------------------------------------------------------ +// PLATFORM LOOKUP UNKNOWN IS CERTIFICATE REQUIRED +//------------------------------------------------------------------------------ +procedure THMGKeyAdaptationTests.PlatformLookupUnknownIsCertificateRequired; +var + P: THMGPlatformInfo; +begin + P := FindHMGPlatform('made-up-platform'); + Assert.AreEqual(Ord(hpaCertificateRequired), Ord(P.Access)); +end; + +//------------------------------------------------------------------------------ +// EGMPIS GATEWAY LOCKED +//------------------------------------------------------------------------------ +procedure THMGKeyAdaptationTests.EGMPIsGatewayLocked; +var + P: THMGPlatformInfo; +begin + P := FindHMGPlatform('ev_e_gmp'); + Assert.AreEqual(Ord(hpaGatewayLockedPostMY2020), Ord(P.Access)); +end; + +initialization + TDUnitX.RegisterTestFixture(THMGKeyAdaptationTests); + +end. diff --git a/tests/Tests.OEM.KeyAdaptation.Toyota.pas b/tests/Tests.OEM.KeyAdaptation.Toyota.pas new file mode 100644 index 00000000..6abbffd3 --- /dev/null +++ b/tests/Tests.OEM.KeyAdaptation.Toyota.pas @@ -0,0 +1,206 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.OEM.KeyAdaptation.Toyota.pas +// CONTENTS : Tests for OBD.OEM.KeyAdaptation.Toyota +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.OEM.KeyAdaptation.Toyota; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TToyotaKeyAdaptationTests = class + public + /// + /// Request round trip with master key. + /// + [Test] procedure RequestRoundTripWithMasterKey; + /// + /// Request round trip with p i n. + /// + [Test] procedure RequestRoundTripWithPIN; + /// + /// Request requires p i n when no master key. + /// + [Test] procedure RequestRequiresPINWhenNoMasterKey; + /// + /// Request pin too long raises. + /// + [Test] procedure RequestPinTooLongRaises; + /// + /// Response round trip. + /// + [Test] procedure ResponseRoundTrip; + /// + /// Response bad added key id raises. + /// + [Test] procedure ResponseBadAddedKeyIdRaises; + /// + /// Camry is master key. + /// + [Test] procedure CamryIsMasterKey; + /// + /// N x300 is pin. + /// + [Test] procedure NX300IsPin; + /// + /// Unknown is certificate locked. + /// + [Test] procedure UnknownIsCertificateLocked; + end; + +implementation + +uses + System.SysUtils, OBD.OEM.KeyAdaptation.Toyota; + +//------------------------------------------------------------------------------ +// REQUEST ROUND TRIP WITH MASTER KEY +//------------------------------------------------------------------------------ +procedure TToyotaKeyAdaptationTests.RequestRoundTripWithMasterKey; +var + In_, Out_: TToyotaKeyRegisterRequest; + Bytes: TBytes; +begin + In_.VIN := 'JTDBR32E230012345'; + In_.Mode := tkmAddKey; + In_.MasterKeyPresent := True; + In_.PIN := ''; + Bytes := EncodeToyotaKeyRegisterRequest(In_); + Out_ := DecodeToyotaKeyRegisterRequest(Bytes); + Assert.IsTrue(Out_.MasterKeyPresent); + Assert.AreEqual('', Out_.PIN); +end; + +//------------------------------------------------------------------------------ +// REQUEST ROUND TRIP WITH PIN +//------------------------------------------------------------------------------ +procedure TToyotaKeyAdaptationTests.RequestRoundTripWithPIN; +var + In_, Out_: TToyotaKeyRegisterRequest; + Bytes: TBytes; +begin + In_.VIN := 'JTDBR32E230012345'; + In_.Mode := tkmAddKey; + In_.MasterKeyPresent := False; + In_.PIN := '987654'; + Bytes := EncodeToyotaKeyRegisterRequest(In_); + Out_ := DecodeToyotaKeyRegisterRequest(Bytes); + Assert.IsFalse(Out_.MasterKeyPresent); + Assert.AreEqual('987654', Out_.PIN); +end; + +//------------------------------------------------------------------------------ +// REQUEST REQUIRES PINWHEN NO MASTER KEY +//------------------------------------------------------------------------------ +procedure TToyotaKeyAdaptationTests.RequestRequiresPINWhenNoMasterKey; +var + Req: TToyotaKeyRegisterRequest; +begin + Req.VIN := 'JTDBR32E230012345'; + Req.Mode := tkmAddKey; + Req.MasterKeyPresent := False; + Req.PIN := ''; + Assert.WillRaise( + procedure begin EncodeToyotaKeyRegisterRequest(Req); end, + EOBDToyotaKey); +end; + +//------------------------------------------------------------------------------ +// REQUEST PIN TOO LONG RAISES +//------------------------------------------------------------------------------ +procedure TToyotaKeyAdaptationTests.RequestPinTooLongRaises; +var + Req: TToyotaKeyRegisterRequest; +begin + Req.VIN := 'JTDBR32E230012345'; + Req.Mode := tkmAddKey; + Req.MasterKeyPresent := False; + Req.PIN := '12345678901234567'; // 17 chars + Assert.WillRaise( + procedure begin EncodeToyotaKeyRegisterRequest(Req); end, + EOBDToyotaKey); +end; + +//------------------------------------------------------------------------------ +// RESPONSE ROUND TRIP +//------------------------------------------------------------------------------ +procedure TToyotaKeyAdaptationTests.ResponseRoundTrip; +var + In_, Out_: TToyotaKeyRegisterResponse; + Bytes: TBytes; +begin + In_.Mode := tkmAddKey; + In_.Success := True; + In_.KeyCount := 3; + In_.AddedKeyId := TBytes.Create($AA, $BB, $CC, $DD); + Bytes := EncodeToyotaKeyRegisterResponse(In_); + Out_ := DecodeToyotaKeyRegisterResponse(Bytes); + Assert.IsTrue(Out_.Success); + Assert.AreEqual(Integer(3), Integer(Out_.KeyCount)); + Assert.AreEqual(Integer($AA), Integer(Out_.AddedKeyId[0])); + Assert.AreEqual(Integer($DD), Integer(Out_.AddedKeyId[3])); +end; + +//------------------------------------------------------------------------------ +// RESPONSE BAD ADDED KEY ID RAISES +//------------------------------------------------------------------------------ +procedure TToyotaKeyAdaptationTests.ResponseBadAddedKeyIdRaises; +var + Resp: TToyotaKeyRegisterResponse; +begin + Resp.Mode := tkmAddKey; + Resp.Success := True; + Resp.KeyCount := 1; + Resp.AddedKeyId := TBytes.Create($00, $00); // wrong length + Assert.WillRaise( + procedure begin EncodeToyotaKeyRegisterResponse(Resp); end, + EOBDToyotaKey); +end; + +//------------------------------------------------------------------------------ +// CAMRY IS MASTER KEY +//------------------------------------------------------------------------------ +procedure TToyotaKeyAdaptationTests.CamryIsMasterKey; +var + P: TToyotaPlatformInfo; +begin + P := FindToyotaPlatform('asv50'); + Assert.AreEqual(Ord(tpaMasterKey), Ord(P.Access)); +end; + +//------------------------------------------------------------------------------ +// NX300 IS PIN +//------------------------------------------------------------------------------ +procedure TToyotaKeyAdaptationTests.NX300IsPin; +var + P: TToyotaPlatformInfo; +begin + P := FindToyotaPlatform('agz10'); + Assert.AreEqual(Ord(tpaPin), Ord(P.Access)); +end; + +//------------------------------------------------------------------------------ +// UNKNOWN IS CERTIFICATE LOCKED +//------------------------------------------------------------------------------ +procedure TToyotaKeyAdaptationTests.UnknownIsCertificateLocked; +var + P: TToyotaPlatformInfo; +begin + P := FindToyotaPlatform('unknown-chassis'); + Assert.AreEqual(Ord(tpaCertificateRequired), Ord(P.Access)); +end; + +initialization + TDUnitX.RegisterTestFixture(TToyotaKeyAdaptationTests); + +end. diff --git a/tests/Tests.OEM.LuxuryAndIndian.pas b/tests/Tests.OEM.LuxuryAndIndian.pas index 4bb26e6a..5eaf331a 100644 --- a/tests/Tests.OEM.LuxuryAndIndian.pas +++ b/tests/Tests.OEM.LuxuryAndIndian.pas @@ -15,34 +15,91 @@ interface [TestFixture] TLuxuryVINTests = class public + /// + /// Ferrari claims zff. + /// [Test] procedure FerrariClaimsZff; + /// + /// Lucid claims casa grande. + /// [Test] procedure LucidClaimsCasaGrande; + /// + /// Mahindra claims all plants. + /// [Test] procedure MahindraClaimsAllPlants; + /// + /// Tata claims passenger and commercial and daewoo. + /// [Test] procedure TataClaimsPassengerAndCommercialAndDaewoo; + /// + /// M i n i claims oxford and china. + /// [Test] procedure MINIClaimsOxfordAndChina; + /// + /// Smart claims hambach and china. + /// [Test] procedure SmartClaimsHambachAndChina; + /// + /// Mahindra does not claim j l r pune. + /// [Test] procedure MahindraDoesNotClaimJLRPune; end; [TestFixture] TLuxuryCatalogTests = class public + /// + /// Ferrari exposes manettino and lift axle. + /// [Test] procedure FerrariExposesManettinoAndLiftAxle; + /// + /// Lucid exposes wunderbox and dream drive. + /// [Test] procedure LucidExposesWunderboxAndDreamDrive; + /// + /// Mahindra exposes be ev controller. + /// [Test] procedure MahindraExposesBeEvController; + /// + /// Tata exposes icng and ziptron. + /// [Test] procedure TataExposesIcngAndZiptron; + /// + /// M i n i session requires security access. + /// [Test] procedure MINISessionRequiresSecurityAccess; + /// + /// Smart exposes geely s e a architecture. + /// [Test] procedure SmartExposesGeelySEAArchitecture; end; [TestFixture] TLuxuryDecoderTests = class public + /// + /// Ferrari decodes paint code. + /// [Test] procedure FerrariDecodesPaintCode; + /// + /// Lucid decodes drivetrain. + /// [Test] procedure LucidDecodesDrivetrain; + /// + /// Mahindra decodes engine code. + /// [Test] procedure MahindraDecodesEngineCode; + /// + /// Tata decodes variant code. + /// [Test] procedure TataDecodesVariantCode; + /// + /// M i n i decodes chassis code. + /// [Test] procedure MINIDecodesChassisCode; + /// + /// Smart decodes battery pack. + /// [Test] procedure SmartDecodesBatteryPack; end; @@ -55,6 +112,9 @@ implementation OBD.OEM.Tata, OBD.OEM.MINI, OBD.OEM.Smart, OBD.OEM.JLR; +//------------------------------------------------------------------------------ +// MAKE VIN +//------------------------------------------------------------------------------ function MakeVin(const Prefix: string): string; begin Result := (Prefix + '00000000000000'); @@ -64,6 +124,10 @@ function MakeVin(const Prefix: string): string; //============================================================================== // VIN routing //============================================================================== + +//------------------------------------------------------------------------------ +// FERRARI CLAIMS ZFF +//------------------------------------------------------------------------------ procedure TLuxuryVINTests.FerrariClaimsZff; var Ext: IOBDOEMExtension; @@ -73,6 +137,9 @@ procedure TLuxuryVINTests.FerrariClaimsZff; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('ZFA')), 'Fiat (Stellantis) ZFA'); end; +//------------------------------------------------------------------------------ +// LUCID CLAIMS CASA GRANDE +//------------------------------------------------------------------------------ procedure TLuxuryVINTests.LucidClaimsCasaGrande; var Ext: IOBDOEMExtension; @@ -81,6 +148,9 @@ procedure TLuxuryVINTests.LucidClaimsCasaGrande; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('50A')), 'Lucid Casa Grande AMP-1'); end; +//------------------------------------------------------------------------------ +// MAHINDRA CLAIMS ALL PLANTS +//------------------------------------------------------------------------------ procedure TLuxuryVINTests.MahindraClaimsAllPlants; var Ext: IOBDOEMExtension; @@ -91,6 +161,9 @@ procedure TLuxuryVINTests.MahindraClaimsAllPlants; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('M3M')), 'BE EV Pune'); end; +//------------------------------------------------------------------------------ +// TATA CLAIMS PASSENGER AND COMMERCIAL AND DAEWOO +//------------------------------------------------------------------------------ procedure TLuxuryVINTests.TataClaimsPassengerAndCommercialAndDaewoo; var Ext: IOBDOEMExtension; @@ -101,6 +174,9 @@ procedure TLuxuryVINTests.TataClaimsPassengerAndCommercialAndDaewoo; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('KMU')), 'Tata Daewoo Commercial'); end; +//------------------------------------------------------------------------------ +// MINICLAIMS OXFORD AND CHINA +//------------------------------------------------------------------------------ procedure TLuxuryVINTests.MINIClaimsOxfordAndChina; var Ext: IOBDOEMExtension; @@ -110,6 +186,9 @@ procedure TLuxuryVINTests.MINIClaimsOxfordAndChina; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('SAW')), 'MINI Spotlight China JV'); end; +//------------------------------------------------------------------------------ +// SMART CLAIMS HAMBACH AND CHINA +//------------------------------------------------------------------------------ procedure TLuxuryVINTests.SmartClaimsHambachAndChina; var Ext: IOBDOEMExtension; @@ -119,6 +198,9 @@ procedure TLuxuryVINTests.SmartClaimsHambachAndChina; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('L7M')), 'smart China Xi''an (Geely-era)'); end; +//------------------------------------------------------------------------------ +// MAHINDRA DOES NOT CLAIM JLRPUNE +//------------------------------------------------------------------------------ procedure TLuxuryVINTests.MahindraDoesNotClaimJLRPune; var Mahindra, JLR: IOBDOEMExtension; @@ -134,6 +216,10 @@ procedure TLuxuryVINTests.MahindraDoesNotClaimJLRPune; //============================================================================== // Catalog spot-checks //============================================================================== + +//------------------------------------------------------------------------------ +// FERRARI EXPOSES MANETTINO AND LIFT AXLE +//------------------------------------------------------------------------------ procedure TLuxuryCatalogTests.FerrariExposesManettinoAndLiftAxle; var Ext: IOBDOEMExtension; @@ -151,6 +237,9 @@ procedure TLuxuryCatalogTests.FerrariExposesManettinoAndLiftAxle; Assert.IsTrue(HasLift, 'Ferrari must expose the front lift system'); end; +//------------------------------------------------------------------------------ +// LUCID EXPOSES WUNDERBOX AND DREAM DRIVE +//------------------------------------------------------------------------------ procedure TLuxuryCatalogTests.LucidExposesWunderboxAndDreamDrive; var Ext: IOBDOEMExtension; @@ -168,6 +257,9 @@ procedure TLuxuryCatalogTests.LucidExposesWunderboxAndDreamDrive; Assert.IsTrue(HasDreamDrive, 'Lucid must expose DreamDrive ADAS'); end; +//------------------------------------------------------------------------------ +// MAHINDRA EXPOSES BE EV CONTROLLER +//------------------------------------------------------------------------------ procedure TLuxuryCatalogTests.MahindraExposesBeEvController; var Ext: IOBDOEMExtension; @@ -182,6 +274,9 @@ procedure TLuxuryCatalogTests.MahindraExposesBeEvController; 'Mahindra must expose the BE EV / XUV400 EV charge controller'); end; +//------------------------------------------------------------------------------ +// TATA EXPOSES ICNG AND ZIPTRON +//------------------------------------------------------------------------------ procedure TLuxuryCatalogTests.TataExposesIcngAndZiptron; var Ext: IOBDOEMExtension; @@ -199,6 +294,9 @@ procedure TLuxuryCatalogTests.TataExposesIcngAndZiptron; Assert.IsTrue(HasEvcc, 'Tata must expose the Ziptron EV charge controller'); end; +//------------------------------------------------------------------------------ +// MINISESSION REQUIRES SECURITY ACCESS +//------------------------------------------------------------------------------ procedure TLuxuryCatalogTests.MINISessionRequiresSecurityAccess; var Ext: IOBDOEMExtension; @@ -210,6 +308,9 @@ procedure TLuxuryCatalogTests.MINISessionRequiresSecurityAccess; Assert.IsTrue(Ext.SessionNegotiator.RequiresSecurityAccess(sstProgramming)); end; +//------------------------------------------------------------------------------ +// SMART EXPOSES GEELY SEAARCHITECTURE +//------------------------------------------------------------------------------ procedure TLuxuryCatalogTests.SmartExposesGeelySEAArchitecture; var Ext: IOBDOEMExtension; @@ -232,6 +333,10 @@ procedure TLuxuryCatalogTests.SmartExposesGeelySEAArchitecture; //============================================================================== // Decoder spot-checks //============================================================================== + +//------------------------------------------------------------------------------ +// FERRARI DECODES PAINT CODE +//------------------------------------------------------------------------------ procedure TLuxuryDecoderTests.FerrariDecodesPaintCode; var Ext: IOBDOEMExtension; @@ -243,6 +348,9 @@ procedure TLuxuryDecoderTests.FerrariDecodesPaintCode; Assert.IsTrue(Pos('322', Output) > 0); end; +//------------------------------------------------------------------------------ +// LUCID DECODES DRIVETRAIN +//------------------------------------------------------------------------------ procedure TLuxuryDecoderTests.LucidDecodesDrivetrain; var Ext: IOBDOEMExtension; @@ -253,6 +361,9 @@ procedure TLuxuryDecoderTests.LucidDecodesDrivetrain; Assert.IsTrue(Pos('lucid_drivetrain', Output) > 0); end; +//------------------------------------------------------------------------------ +// MAHINDRA DECODES ENGINE CODE +//------------------------------------------------------------------------------ procedure TLuxuryDecoderTests.MahindraDecodesEngineCode; var Ext: IOBDOEMExtension; @@ -263,6 +374,9 @@ procedure TLuxuryDecoderTests.MahindraDecodesEngineCode; Assert.IsTrue(Pos('mahindra_engine_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// TATA DECODES VARIANT CODE +//------------------------------------------------------------------------------ procedure TLuxuryDecoderTests.TataDecodesVariantCode; var Ext: IOBDOEMExtension; @@ -273,6 +387,9 @@ procedure TLuxuryDecoderTests.TataDecodesVariantCode; Assert.IsTrue(Pos('tata_variant_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// MINIDECODES CHASSIS CODE +//------------------------------------------------------------------------------ procedure TLuxuryDecoderTests.MINIDecodesChassisCode; var Ext: IOBDOEMExtension; @@ -283,6 +400,9 @@ procedure TLuxuryDecoderTests.MINIDecodesChassisCode; Assert.IsTrue(Pos('mini_chassis_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// SMART DECODES BATTERY PACK +//------------------------------------------------------------------------------ procedure TLuxuryDecoderTests.SmartDecodesBatteryPack; var Ext: IOBDOEMExtension; diff --git a/tests/Tests.OEM.Premium.pas b/tests/Tests.OEM.Premium.pas index 68adc81a..3b27f266 100644 --- a/tests/Tests.OEM.Premium.pas +++ b/tests/Tests.OEM.Premium.pas @@ -15,34 +15,91 @@ interface [TestFixture] TPremiumVINTests = class public + /// + /// Porsche claims zuffenhausen and leipzig. + /// [Test] procedure PorscheClaimsZuffenhausenAndLeipzig; + /// + /// J l r claims jaguar and land rover plants. + /// [Test] procedure JLRClaimsJaguarAndLandRoverPlants; + /// + /// Iveco claims italy and spain. + /// [Test] procedure IvecoClaimsItalyAndSpain; + /// + /// Isuzu claims japan and u s a. + /// [Test] procedure IsuzuClaimsJapanAndUSA; + /// + /// Rivian claims normal i l. + /// [Test] procedure RivianClaimsNormalIL; + /// + /// Polestar claims non volvo cars w m is. + /// [Test] procedure PolestarClaimsNonVolvoCarsWMIs; + /// + /// Polestar does not collide with volvo cars. + /// [Test] procedure PolestarDoesNotCollideWithVolvoCars; end; [TestFixture] TPremiumCatalogTests = class public + /// + /// Porsche exposes p d k and p a s m. + /// [Test] procedure PorscheExposesPDKAndPASM; + /// + /// J l r exposes air suspension routine. + /// [Test] procedure JLRExposesAirSuspensionRoutine; + /// + /// Iveco exposes f p t engine. + /// [Test] procedure IvecoExposesFPTEngine; + /// + /// Isuzu exposes aftertreatment e c u. + /// [Test] procedure IsuzuExposesAftertreatmentECU; + /// + /// Rivian exposes quad motor. + /// [Test] procedure RivianExposesQuadMotor; + /// + /// Polestar exposes evcc and pilot assist. + /// [Test] procedure PolestarExposesEvccAndPilotAssist; end; [TestFixture] TPremiumDecoderTests = class public + /// + /// Porsche decodes paint code. + /// [Test] procedure PorscheDecodesPaintCode; + /// + /// J l r decodes model code. + /// [Test] procedure JLRDecodesModelCode; + /// + /// Iveco decodes model code. + /// [Test] procedure IvecoDecodesModelCode; + /// + /// Isuzu decodes engine code. + /// [Test] procedure IsuzuDecodesEngineCode; + /// + /// Rivian decodes drivetrain. + /// [Test] procedure RivianDecodesDrivetrain; + /// + /// Polestar decodes drivetrain. + /// [Test] procedure PolestarDecodesDrivetrain; end; @@ -55,6 +112,9 @@ implementation OBD.OEM.Isuzu, OBD.OEM.Rivian, OBD.OEM.Polestar, OBD.OEM.Volvo; +//------------------------------------------------------------------------------ +// MAKE VIN +//------------------------------------------------------------------------------ function MakeVin(const Prefix: string): string; begin Result := (Prefix + '00000000000000'); @@ -64,6 +124,10 @@ function MakeVin(const Prefix: string): string; //============================================================================== // VIN routing //============================================================================== + +//------------------------------------------------------------------------------ +// PORSCHE CLAIMS ZUFFENHAUSEN AND LEIPZIG +//------------------------------------------------------------------------------ procedure TPremiumVINTests.PorscheClaimsZuffenhausenAndLeipzig; var Ext: IOBDOEMExtension; @@ -74,6 +138,9 @@ procedure TPremiumVINTests.PorscheClaimsZuffenhausenAndLeipzig; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('WVW')), 'should not claim VW'); end; +//------------------------------------------------------------------------------ +// JLRCLAIMS JAGUAR AND LAND ROVER PLANTS +//------------------------------------------------------------------------------ procedure TPremiumVINTests.JLRClaimsJaguarAndLandRoverPlants; var Ext: IOBDOEMExtension; @@ -85,6 +152,9 @@ procedure TPremiumVINTests.JLRClaimsJaguarAndLandRoverPlants; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('MA1')), 'JLR India Pune'); end; +//------------------------------------------------------------------------------ +// IVECO CLAIMS ITALY AND SPAIN +//------------------------------------------------------------------------------ procedure TPremiumVINTests.IvecoClaimsItalyAndSpain; var Ext: IOBDOEMExtension; @@ -94,6 +164,9 @@ procedure TPremiumVINTests.IvecoClaimsItalyAndSpain; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('VCF')), 'Iveco Spain'); end; +//------------------------------------------------------------------------------ +// ISUZU CLAIMS JAPAN AND USA +//------------------------------------------------------------------------------ procedure TPremiumVINTests.IsuzuClaimsJapanAndUSA; var Ext: IOBDOEMExtension; @@ -104,6 +177,9 @@ procedure TPremiumVINTests.IsuzuClaimsJapanAndUSA; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('5RY')), 'Isuzu Charlotte MI'); end; +//------------------------------------------------------------------------------ +// RIVIAN CLAIMS NORMAL IL +//------------------------------------------------------------------------------ procedure TPremiumVINTests.RivianClaimsNormalIL; var Ext: IOBDOEMExtension; @@ -113,6 +189,9 @@ procedure TPremiumVINTests.RivianClaimsNormalIL; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('5YJ')), 'should not claim Tesla'); end; +//------------------------------------------------------------------------------ +// POLESTAR CLAIMS NON VOLVO CARS WMIS +//------------------------------------------------------------------------------ procedure TPremiumVINTests.PolestarClaimsNonVolvoCarsWMIs; var Ext: IOBDOEMExtension; @@ -122,6 +201,9 @@ procedure TPremiumVINTests.PolestarClaimsNonVolvoCarsWMIs; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('LFP')), 'Polestar 4 Hangzhou'); end; +//------------------------------------------------------------------------------ +// POLESTAR DOES NOT COLLIDE WITH VOLVO CARS +//------------------------------------------------------------------------------ procedure TPremiumVINTests.PolestarDoesNotCollideWithVolvoCars; var Polestar, Volvo: IOBDOEMExtension; @@ -139,6 +221,10 @@ procedure TPremiumVINTests.PolestarDoesNotCollideWithVolvoCars; //============================================================================== // Catalog spot-checks //============================================================================== + +//------------------------------------------------------------------------------ +// PORSCHE EXPOSES PDKAND PASM +//------------------------------------------------------------------------------ procedure TPremiumCatalogTests.PorscheExposesPDKAndPASM; var Ext: IOBDOEMExtension; @@ -156,6 +242,9 @@ procedure TPremiumCatalogTests.PorscheExposesPDKAndPASM; Assert.IsTrue(HasPASM, 'Porsche must expose the PASM suspension ECU'); end; +//------------------------------------------------------------------------------ +// JLREXPOSES AIR SUSPENSION ROUTINE +//------------------------------------------------------------------------------ procedure TPremiumCatalogTests.JLRExposesAirSuspensionRoutine; var Ext: IOBDOEMExtension; @@ -169,6 +258,9 @@ procedure TPremiumCatalogTests.JLRExposesAirSuspensionRoutine; Assert.IsTrue(Has, 'JLR must expose the air-suspension calibration routine'); end; +//------------------------------------------------------------------------------ +// IVECO EXPOSES FPTENGINE +//------------------------------------------------------------------------------ procedure TPremiumCatalogTests.IvecoExposesFPTEngine; var Ext: IOBDOEMExtension; @@ -182,6 +274,9 @@ procedure TPremiumCatalogTests.IvecoExposesFPTEngine; Assert.IsTrue(Has, 'Iveco must expose the FPT engine ECU'); end; +//------------------------------------------------------------------------------ +// ISUZU EXPOSES AFTERTREATMENT ECU +//------------------------------------------------------------------------------ procedure TPremiumCatalogTests.IsuzuExposesAftertreatmentECU; var Ext: IOBDOEMExtension; @@ -195,6 +290,9 @@ procedure TPremiumCatalogTests.IsuzuExposesAftertreatmentECU; Assert.IsTrue(Has, 'Isuzu must expose the aftertreatment ECU'); end; +//------------------------------------------------------------------------------ +// RIVIAN EXPOSES QUAD MOTOR +//------------------------------------------------------------------------------ procedure TPremiumCatalogTests.RivianExposesQuadMotor; var Ext: IOBDOEMExtension; @@ -209,6 +307,9 @@ procedure TPremiumCatalogTests.RivianExposesQuadMotor; 'Rivian must expose four motor inverters (quad-motor R1)'); end; +//------------------------------------------------------------------------------ +// POLESTAR EXPOSES EVCC AND PILOT ASSIST +//------------------------------------------------------------------------------ procedure TPremiumCatalogTests.PolestarExposesEvccAndPilotAssist; var Ext: IOBDOEMExtension; @@ -229,6 +330,10 @@ procedure TPremiumCatalogTests.PolestarExposesEvccAndPilotAssist; //============================================================================== // Decoder spot-checks //============================================================================== + +//------------------------------------------------------------------------------ +// PORSCHE DECODES PAINT CODE +//------------------------------------------------------------------------------ procedure TPremiumDecoderTests.PorscheDecodesPaintCode; var Ext: IOBDOEMExtension; @@ -239,6 +344,9 @@ procedure TPremiumDecoderTests.PorscheDecodesPaintCode; Assert.IsTrue(Pos('porsche_paint_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// JLRDECODES MODEL CODE +//------------------------------------------------------------------------------ procedure TPremiumDecoderTests.JLRDecodesModelCode; var Ext: IOBDOEMExtension; @@ -249,6 +357,9 @@ procedure TPremiumDecoderTests.JLRDecodesModelCode; Assert.IsTrue(Pos('jlr_model_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// IVECO DECODES MODEL CODE +//------------------------------------------------------------------------------ procedure TPremiumDecoderTests.IvecoDecodesModelCode; var Ext: IOBDOEMExtension; @@ -259,6 +370,9 @@ procedure TPremiumDecoderTests.IvecoDecodesModelCode; Assert.IsTrue(Pos('iveco_model_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// ISUZU DECODES ENGINE CODE +//------------------------------------------------------------------------------ procedure TPremiumDecoderTests.IsuzuDecodesEngineCode; var Ext: IOBDOEMExtension; @@ -269,6 +383,9 @@ procedure TPremiumDecoderTests.IsuzuDecodesEngineCode; Assert.IsTrue(Pos('isuzu_engine_code', Output) > 0); end; +//------------------------------------------------------------------------------ +// RIVIAN DECODES DRIVETRAIN +//------------------------------------------------------------------------------ procedure TPremiumDecoderTests.RivianDecodesDrivetrain; var Ext: IOBDOEMExtension; @@ -279,6 +396,9 @@ procedure TPremiumDecoderTests.RivianDecodesDrivetrain; Assert.IsTrue(Pos('rivian_drivetrain', Output) > 0); end; +//------------------------------------------------------------------------------ +// POLESTAR DECODES DRIVETRAIN +//------------------------------------------------------------------------------ procedure TPremiumDecoderTests.PolestarDecodesDrivetrain; var Ext: IOBDOEMExtension; diff --git a/tests/Tests.OEM.RoutineControl.pas b/tests/Tests.OEM.RoutineControl.pas index 70324055..11a4a6a8 100644 --- a/tests/Tests.OEM.RoutineControl.pas +++ b/tests/Tests.OEM.RoutineControl.pas @@ -13,47 +13,128 @@ interface [TestFixture] TRequestBuilderTests = class public + /// + /// Uint8 and uint16 b e encode big endian. + /// [Test] procedure Uint8AndUint16BEEncodeBigEndian; + /// + /// Int32 b e encodes negative. + /// [Test] procedure Int32BEEncodesNegative; + /// + /// Ascii pads and rejects too long. + /// [Test] procedure AsciiPadsAndRejectsTooLong; + /// + /// Bcd date encodes year month day. + /// [Test] procedure BcdDateEncodesYearMonthDay; + /// + /// Bcd year rejects out of range. + /// [Test] procedure BcdYearRejectsOutOfRange; + /// + /// To frame wraps with sid and rid. + /// [Test] procedure ToFrameWrapsWithSidAndRid; + /// + /// Clear resets builder. + /// [Test] procedure ClearResetsBuilder; end; [TestFixture] TResponseReaderTests = class public + /// + /// Reads big endian multi byte. + /// [Test] procedure ReadsBigEndianMultiByte; + /// + /// Reads ascii and strips zero pad. + /// [Test] procedure ReadsAsciiAndStripsZeroPad; + /// + /// Reads bcd date. + /// [Test] procedure ReadsBcdDate; + /// + /// Reads hex slice. + /// [Test] procedure ReadsHexSlice; + /// + /// Under read raises. + /// [Test] procedure UnderReadRaises; + /// + /// Has more reflects cursor. + /// [Test] procedure HasMoreReflectsCursor; end; [TestFixture] TWireFrameTests = class public + /// + /// Build start routine without data. + /// [Test] procedure BuildStartRoutineWithoutData; + /// + /// Build start routine appends data. + /// [Test] procedure BuildStartRoutineAppendsData; + /// + /// Build stop and request results. + /// [Test] procedure BuildStopAndRequestResults; + /// + /// Parse accepts positive response. + /// [Test] procedure ParseAcceptsPositiveResponse; + /// + /// Parse rejects wrong s i d. + /// [Test] procedure ParseRejectsWrongSID; + /// + /// Parse rejects wrong sub function. + /// [Test] procedure ParseRejectsWrongSubFunction; + /// + /// Parse rejects wrong r i d. + /// [Test] procedure ParseRejectsWrongRID; + /// + /// Parse raises on negative response. + /// [Test] procedure ParseRaisesOnNegativeResponse; + /// + /// Parse handles empty status payload. + /// [Test] procedure ParseHandlesEmptyStatusPayload; end; [TestFixture] TSchemaDecodeTests = class public + /// + /// Decodes u int8 with scale and offset. + /// [Test] procedure DecodesUInt8WithScaleAndOffset; + /// + /// Decodes ascii and u int32. + /// [Test] procedure DecodesAsciiAndUInt32; + /// + /// Decodes bitmask with named bits. + /// [Test] procedure DecodesBitmaskWithNamedBits; + /// + /// Decodes enum with fallback. + /// [Test] procedure DecodesEnumWithFallback; + /// + /// Stops on truncated response. + /// [Test] procedure StopsOnTruncatedResponse; end; @@ -66,6 +147,10 @@ implementation //============================================================================== // Builder //============================================================================== + +//------------------------------------------------------------------------------ +// UINT8 AND UINT16 BEENCODE BIG ENDIAN +//------------------------------------------------------------------------------ procedure TRequestBuilderTests.Uint8AndUint16BEEncodeBigEndian; var Builder: TOBDRoutineRequestBuilder; @@ -80,6 +165,9 @@ procedure TRequestBuilderTests.Uint8AndUint16BEEncodeBigEndian; Assert.AreEqual(Byte($34), Bytes[2]); end; +//------------------------------------------------------------------------------ +// INT32 BEENCODES NEGATIVE +//------------------------------------------------------------------------------ procedure TRequestBuilderTests.Int32BEEncodesNegative; var Builder: TOBDRoutineRequestBuilder; @@ -92,6 +180,9 @@ procedure TRequestBuilderTests.Int32BEEncodesNegative; Assert.AreEqual(Byte($FF), Bytes[3]); end; +//------------------------------------------------------------------------------ +// ASCII PADS AND REJECTS TOO LONG +//------------------------------------------------------------------------------ procedure TRequestBuilderTests.AsciiPadsAndRejectsTooLong; var Builder: TOBDRoutineRequestBuilder; @@ -107,11 +198,15 @@ procedure TRequestBuilderTests.AsciiPadsAndRejectsTooLong; Assert.WillRaise( procedure - var B: TOBDRoutineRequestBuilder; + var + B: TOBDRoutineRequestBuilder; begin B.AddAscii('TOOLONG', 4); end, EOBDRoutineError); end; +//------------------------------------------------------------------------------ +// BCD DATE ENCODES YEAR MONTH DAY +//------------------------------------------------------------------------------ procedure TRequestBuilderTests.BcdDateEncodesYearMonthDay; var Builder: TOBDRoutineRequestBuilder; @@ -124,15 +219,22 @@ procedure TRequestBuilderTests.BcdDateEncodesYearMonthDay; Assert.AreEqual(Byte($07), Bytes[2]); end; +//------------------------------------------------------------------------------ +// BCD YEAR REJECTS OUT OF RANGE +//------------------------------------------------------------------------------ procedure TRequestBuilderTests.BcdYearRejectsOutOfRange; begin Assert.WillRaise( procedure - var B: TOBDRoutineRequestBuilder; + var + B: TOBDRoutineRequestBuilder; begin B.AddBcdYear(150); end, EOBDRoutineError); end; +//------------------------------------------------------------------------------ +// TO FRAME WRAPS WITH SID AND RID +//------------------------------------------------------------------------------ procedure TRequestBuilderTests.ToFrameWrapsWithSidAndRid; var Builder: TOBDRoutineRequestBuilder; @@ -148,6 +250,9 @@ procedure TRequestBuilderTests.ToFrameWrapsWithSidAndRid; Assert.AreEqual(Byte($AA), Frame[4]); end; +//------------------------------------------------------------------------------ +// CLEAR RESETS BUILDER +//------------------------------------------------------------------------------ procedure TRequestBuilderTests.ClearResetsBuilder; var Builder: TOBDRoutineRequestBuilder; @@ -160,6 +265,10 @@ procedure TRequestBuilderTests.ClearResetsBuilder; //============================================================================== // Reader //============================================================================== + +//------------------------------------------------------------------------------ +// READS BIG ENDIAN MULTI BYTE +//------------------------------------------------------------------------------ procedure TResponseReaderTests.ReadsBigEndianMultiByte; var R: TOBDRoutineResponseReader; @@ -171,6 +280,9 @@ procedure TResponseReaderTests.ReadsBigEndianMultiByte; Assert.AreEqual(Word($5678), R.ReadUInt16BE); end; +//------------------------------------------------------------------------------ +// READS ASCII AND STRIPS ZERO PAD +//------------------------------------------------------------------------------ procedure TResponseReaderTests.ReadsAsciiAndStripsZeroPad; var R: TOBDRoutineResponseReader; @@ -180,6 +292,9 @@ procedure TResponseReaderTests.ReadsAsciiAndStripsZeroPad; Assert.AreEqual('VW', R.ReadAscii(4)); end; +//------------------------------------------------------------------------------ +// READS BCD DATE +//------------------------------------------------------------------------------ procedure TResponseReaderTests.ReadsBcdDate; var R: TOBDRoutineResponseReader; @@ -188,6 +303,9 @@ procedure TResponseReaderTests.ReadsBcdDate; Assert.AreEqual('2025-03-14', R.ReadBcdDate); end; +//------------------------------------------------------------------------------ +// READS HEX SLICE +//------------------------------------------------------------------------------ procedure TResponseReaderTests.ReadsHexSlice; var R: TOBDRoutineResponseReader; @@ -202,6 +320,9 @@ procedure TResponseReaderTests.ReadsHexSlice; Assert.AreEqual(1, R.Remaining); end; +//------------------------------------------------------------------------------ +// UNDER READ RAISES +//------------------------------------------------------------------------------ procedure TResponseReaderTests.UnderReadRaises; var R: TOBDRoutineResponseReader; @@ -212,6 +333,9 @@ procedure TResponseReaderTests.UnderReadRaises; EOBDRoutineError); end; +//------------------------------------------------------------------------------ +// HAS MORE REFLECTS CURSOR +//------------------------------------------------------------------------------ procedure TResponseReaderTests.HasMoreReflectsCursor; var R: TOBDRoutineResponseReader; @@ -225,6 +349,10 @@ procedure TResponseReaderTests.HasMoreReflectsCursor; //============================================================================== // Wire frames //============================================================================== + +//------------------------------------------------------------------------------ +// BUILD START ROUTINE WITHOUT DATA +//------------------------------------------------------------------------------ procedure TWireFrameTests.BuildStartRoutineWithoutData; var F: TBytes; @@ -237,6 +365,9 @@ procedure TWireFrameTests.BuildStartRoutineWithoutData; Assert.AreEqual(Byte($03), F[3]); end; +//------------------------------------------------------------------------------ +// BUILD START ROUTINE APPENDS DATA +//------------------------------------------------------------------------------ procedure TWireFrameTests.BuildStartRoutineAppendsData; var F: TBytes; @@ -247,6 +378,9 @@ procedure TWireFrameTests.BuildStartRoutineAppendsData; Assert.AreEqual(Byte($AD), F[5]); end; +//------------------------------------------------------------------------------ +// BUILD STOP AND REQUEST RESULTS +//------------------------------------------------------------------------------ procedure TWireFrameTests.BuildStopAndRequestResults; var Stop, Req: TBytes; @@ -257,6 +391,9 @@ procedure TWireFrameTests.BuildStopAndRequestResults; Assert.AreEqual(Byte($03), Req[1]); end; +//------------------------------------------------------------------------------ +// PARSE ACCEPTS POSITIVE RESPONSE +//------------------------------------------------------------------------------ procedure TWireFrameTests.ParseAcceptsPositiveResponse; var Status: TBytes; @@ -269,6 +406,9 @@ procedure TWireFrameTests.ParseAcceptsPositiveResponse; Assert.AreEqual(Byte($22), Status[2]); end; +//------------------------------------------------------------------------------ +// PARSE REJECTS WRONG SID +//------------------------------------------------------------------------------ procedure TWireFrameTests.ParseRejectsWrongSID; begin Assert.WillRaise( @@ -279,6 +419,9 @@ procedure TWireFrameTests.ParseRejectsWrongSID; EOBDRoutineError); end; +//------------------------------------------------------------------------------ +// PARSE REJECTS WRONG SUB FUNCTION +//------------------------------------------------------------------------------ procedure TWireFrameTests.ParseRejectsWrongSubFunction; begin Assert.WillRaise( @@ -289,6 +432,9 @@ procedure TWireFrameTests.ParseRejectsWrongSubFunction; EOBDRoutineError); end; +//------------------------------------------------------------------------------ +// PARSE REJECTS WRONG RID +//------------------------------------------------------------------------------ procedure TWireFrameTests.ParseRejectsWrongRID; begin Assert.WillRaise( @@ -299,6 +445,9 @@ procedure TWireFrameTests.ParseRejectsWrongRID; EOBDRoutineError); end; +//------------------------------------------------------------------------------ +// PARSE RAISES ON NEGATIVE RESPONSE +//------------------------------------------------------------------------------ procedure TWireFrameTests.ParseRaisesOnNegativeResponse; begin Assert.WillRaise( @@ -310,6 +459,9 @@ procedure TWireFrameTests.ParseRaisesOnNegativeResponse; EOBDRoutineError); end; +//------------------------------------------------------------------------------ +// PARSE HANDLES EMPTY STATUS PAYLOAD +//------------------------------------------------------------------------------ procedure TWireFrameTests.ParseHandlesEmptyStatusPayload; var Status: TBytes; @@ -322,6 +474,10 @@ procedure TWireFrameTests.ParseHandlesEmptyStatusPayload; //============================================================================== // Schema decode //============================================================================== + +//------------------------------------------------------------------------------ +// MAKE FIELD +//------------------------------------------------------------------------------ function MakeField(const Name: string; const Kind: TOBDRoutineFieldKind; const Size: Integer = 0; const Scale: Double = 1.0; const Offset: Double = 0.0; const Unit_: string = ''): TOBDRoutineField; @@ -335,6 +491,9 @@ function MakeField(const Name: string; const Kind: TOBDRoutineFieldKind; Result.Unit_ := Unit_; end; +//------------------------------------------------------------------------------ +// DECODES UINT8 WITH SCALE AND OFFSET +//------------------------------------------------------------------------------ procedure TSchemaDecodeTests.DecodesUInt8WithScaleAndOffset; var Schema: TOBDRoutineSchema; @@ -347,6 +506,9 @@ procedure TSchemaDecodeTests.DecodesUInt8WithScaleAndOffset; Assert.AreEqual('temp = 10 C', Decoded[0].Display); end; +//------------------------------------------------------------------------------ +// DECODES ASCII AND UINT32 +//------------------------------------------------------------------------------ procedure TSchemaDecodeTests.DecodesAsciiAndUInt32; var Schema: TOBDRoutineSchema; @@ -365,6 +527,9 @@ procedure TSchemaDecodeTests.DecodesAsciiAndUInt32; Assert.AreEqual('mileage = 100000 km', Decoded[1].Display); end; +//------------------------------------------------------------------------------ +// DECODES BITMASK WITH NAMED BITS +//------------------------------------------------------------------------------ procedure TSchemaDecodeTests.DecodesBitmaskWithNamedBits; var Field: TOBDRoutineField; @@ -386,6 +551,9 @@ procedure TSchemaDecodeTests.DecodesBitmaskWithNamedBits; end; end; +//------------------------------------------------------------------------------ +// DECODES ENUM WITH FALLBACK +//------------------------------------------------------------------------------ procedure TSchemaDecodeTests.DecodesEnumWithFallback; var Field: TOBDRoutineField; @@ -408,6 +576,9 @@ procedure TSchemaDecodeTests.DecodesEnumWithFallback; end; end; +//------------------------------------------------------------------------------ +// STOPS ON TRUNCATED RESPONSE +//------------------------------------------------------------------------------ procedure TSchemaDecodeTests.StopsOnTruncatedResponse; var Schema: TOBDRoutineSchema; diff --git a/tests/Tests.OEM.SCN.Mercedes.pas b/tests/Tests.OEM.SCN.Mercedes.pas new file mode 100644 index 00000000..a3c12136 --- /dev/null +++ b/tests/Tests.OEM.SCN.Mercedes.pas @@ -0,0 +1,185 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.OEM.SCN.Mercedes.pas +// CONTENTS : Tests for OBD.OEM.SCN.Mercedes +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.OEM.SCN.Mercedes; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TMBSCNTests = class + public + /// + /// Version request round trip. + /// + [Test] procedure VersionRequestRoundTrip; + /// + /// Version request bad length raises. + /// + [Test] procedure VersionRequestBadLengthRaises; + /// + /// Coding request round trip. + /// + [Test] procedure CodingRequestRoundTrip; + /// + /// Coding request rejects bad v i n. + /// + [Test] procedure CodingRequestRejectsBadVIN; + /// + /// Coding response round trip. + /// + [Test] procedure CodingResponseRoundTrip; + /// + /// Coding response truncated new s c n raises. + /// + [Test] procedure CodingResponseTruncatedNewSCNRaises; + /// + /// Default solver fetch fails closed. + /// + [Test] procedure DefaultSolverFetchFailsClosed; + /// + /// Default solver coding fails closed. + /// + [Test] procedure DefaultSolverCodingFailsClosed; + end; + +implementation + +uses + System.SysUtils, OBD.OEM.SCN.Mercedes; + +//------------------------------------------------------------------------------ +// VERSION REQUEST ROUND TRIP +//------------------------------------------------------------------------------ +procedure TMBSCNTests.VersionRequestRoundTrip; +var + In_, Out_: TMBSCNVersionRequest; + Bytes: TBytes; +begin + In_.VIN := 'WDD2050461R234567'; + In_.ECUId := $00CA; + Bytes := EncodeMBSCNVersionRequest(In_); + Assert.AreEqual(19, Length(Bytes)); + Out_ := DecodeMBSCNVersionRequest(Bytes); + Assert.AreEqual('WDD2050461R234567', Out_.VIN); + Assert.AreEqual(Word($00CA), Out_.ECUId); +end; + +//------------------------------------------------------------------------------ +// VERSION REQUEST BAD LENGTH RAISES +//------------------------------------------------------------------------------ +procedure TMBSCNTests.VersionRequestBadLengthRaises; +begin + Assert.WillRaise( + procedure begin DecodeMBSCNVersionRequest(TBytes.Create($00, $00)); end, + EOBDMBSCN); +end; + +//------------------------------------------------------------------------------ +// CODING REQUEST ROUND TRIP +//------------------------------------------------------------------------------ +procedure TMBSCNTests.CodingRequestRoundTrip; +var + In_, Out_: TMBSCNCodingRequest; + Bytes: TBytes; +begin + In_.VIN := 'WDD2050461R234567'; + In_.ECUId := $0042; + In_.Variant := TBytes.Create($AA, $BB); + In_.AccessoryList := TBytes.Create($01, $02, $03); + Bytes := EncodeMBSCNCodingRequest(In_); + Out_ := DecodeMBSCNCodingRequest(Bytes); + Assert.AreEqual('WDD2050461R234567', Out_.VIN); + Assert.AreEqual(Word($0042), Out_.ECUId); + Assert.AreEqual(2, Length(Out_.Variant)); + Assert.AreEqual(3, Length(Out_.AccessoryList)); + Assert.AreEqual(Integer($02), Integer(Out_.AccessoryList[1])); +end; + +//------------------------------------------------------------------------------ +// CODING REQUEST REJECTS BAD VIN +//------------------------------------------------------------------------------ +procedure TMBSCNTests.CodingRequestRejectsBadVIN; +var + Req: TMBSCNCodingRequest; +begin + Req.VIN := 'TOO-SHORT'; + Assert.WillRaise( + procedure begin EncodeMBSCNCodingRequest(Req); end, EOBDMBSCN); +end; + +//------------------------------------------------------------------------------ +// CODING RESPONSE ROUND TRIP +//------------------------------------------------------------------------------ +procedure TMBSCNTests.CodingResponseRoundTrip; +var + In_, Out_: TMBSCNCodingResponse; + Bytes: TBytes; +begin + In_.NewSCN := TBytes.Create($DE, $AD, $BE, $EF); + In_.ServerSignature := TBytes.Create($CA, $FE); + Bytes := EncodeMBSCNCodingResponse(In_); + Out_ := DecodeMBSCNCodingResponse(Bytes); + Assert.AreEqual(4, Length(Out_.NewSCN)); + Assert.AreEqual(2, Length(Out_.ServerSignature)); + Assert.AreEqual(Integer($DE), Integer(Out_.NewSCN[0])); + Assert.AreEqual(Integer($CA), Integer(Out_.ServerSignature[0])); +end; + +//------------------------------------------------------------------------------ +// CODING RESPONSE TRUNCATED NEW SCNRAISES +//------------------------------------------------------------------------------ +procedure TMBSCNTests.CodingResponseTruncatedNewSCNRaises; +var + Bytes: TBytes; +begin + // Declares 4 NewSCN bytes but only 2 follow + Bytes := TBytes.Create($00, $04, $AA, $BB); + Assert.WillRaise( + procedure begin DecodeMBSCNCodingResponse(Bytes); end, EOBDMBSCN); +end; + +//------------------------------------------------------------------------------ +// DEFAULT SOLVER FETCH FAILS CLOSED +//------------------------------------------------------------------------------ +procedure TMBSCNTests.DefaultSolverFetchFailsClosed; +var + Solver: IMBSCNSolver; + Req: TMBSCNVersionRequest; +begin + Solver := TMBSCNSolverNotAvailable.Create; + Req.VIN := 'WDD2050461R234567'; + Req.ECUId := 0; + Assert.WillRaise( + procedure begin Solver.FetchCurrentVersion(Req); end, EOBDMBSCNNoSolver); +end; + +//------------------------------------------------------------------------------ +// DEFAULT SOLVER CODING FAILS CLOSED +//------------------------------------------------------------------------------ +procedure TMBSCNTests.DefaultSolverCodingFailsClosed; +var + Solver: IMBSCNSolver; + Req: TMBSCNCodingRequest; +begin + Solver := TMBSCNSolverNotAvailable.Create; + Req.VIN := 'WDD2050461R234567'; + Assert.WillRaise( + procedure begin Solver.RequestCoding(Req); end, EOBDMBSCNNoSolver); +end; + +initialization + TDUnitX.RegisterTestFixture(TMBSCNTests); + +end. diff --git a/tests/Tests.OEM.SchemaShape.pas b/tests/Tests.OEM.SchemaShape.pas index 82350ced..dea21747 100644 --- a/tests/Tests.OEM.SchemaShape.pas +++ b/tests/Tests.OEM.SchemaShape.pas @@ -29,12 +29,33 @@ interface [TestFixture] TSchemaShapeTests = class public + /// + /// W m i codes are three chars alphanumeric. + /// [Test] procedure WMICodesAreThreeCharsAlphanumeric; + /// + /// Decoder kinds use recognised tags. + /// [Test] procedure DecoderKindsUseRecognisedTags; + /// + /// Coding field kinds use recognised tags. + /// [Test] procedure CodingFieldKindsUseRecognisedTags; + /// + /// Adaptation kinds use recognised tags. + /// [Test] procedure AdaptationKindsUseRecognisedTags; + /// + /// D t c codes match s a eor j1939 or oem format. + /// [Test] procedure DTCCodesMatchSAEorJ1939OrOemFormat; + /// + /// Manufacturer keys are non empty. + /// [Test] procedure ManufacturerKeysAreNonEmpty; + /// + /// Version field is one or two. + /// [Test] procedure VersionFieldIsOneOrTwo; end; @@ -60,6 +81,9 @@ function CatalogsRoot: string; Result := ''; end; +//------------------------------------------------------------------------------ +// COLLECT ALL JSON +//------------------------------------------------------------------------------ function CollectAllJson(const Root: string): TArray; var All: TArray; @@ -84,6 +108,9 @@ function CollectAllJson(const Root: string): TArray; end; end; +//------------------------------------------------------------------------------ +// IS OEM CATALOG +//------------------------------------------------------------------------------ function IsOemCatalog(const Path: string): Boolean; var Name: string; @@ -95,11 +122,17 @@ function IsOemCatalog(const Path: string): Boolean; Name.StartsWith('obd2-', True)); end; +//------------------------------------------------------------------------------ +// IS DTC CATALOG +//------------------------------------------------------------------------------ function IsDtcCatalog(const Path: string): Boolean; begin Result := TPath.GetFileName(Path).StartsWith('dtc-', True); end; +//------------------------------------------------------------------------------ +// PARSE JSON OBJ +//------------------------------------------------------------------------------ function ParseJsonObj(const Path: string): TJSONObject; var V: TJSONValue; @@ -153,6 +186,9 @@ procedure TSchemaShapeTests.WMICodesAreThreeCharsAlphanumeric; end; end; +//------------------------------------------------------------------------------ +// DECODER KINDS USE RECOGNISED TAGS +//------------------------------------------------------------------------------ procedure TSchemaShapeTests.DecoderKindsUseRecognisedTags; const RecognisedKinds: array[0..13] of string = ( @@ -210,6 +246,9 @@ procedure TSchemaShapeTests.DecoderKindsUseRecognisedTags; end; end; +//------------------------------------------------------------------------------ +// CODING FIELD KINDS USE RECOGNISED TAGS +//------------------------------------------------------------------------------ procedure TSchemaShapeTests.CodingFieldKindsUseRecognisedTags; const RecognisedKinds: array[0..8] of string = ( @@ -267,6 +306,9 @@ procedure TSchemaShapeTests.CodingFieldKindsUseRecognisedTags; end; end; +//------------------------------------------------------------------------------ +// ADAPTATION KINDS USE RECOGNISED TAGS +//------------------------------------------------------------------------------ procedure TSchemaShapeTests.AdaptationKindsUseRecognisedTags; const RecognisedKinds: array[0..6] of string = ( @@ -316,6 +358,9 @@ procedure TSchemaShapeTests.AdaptationKindsUseRecognisedTags; end; end; +//------------------------------------------------------------------------------ +// DTCCODES MATCH SAEOR J1939 OR OEM FORMAT +//------------------------------------------------------------------------------ procedure TSchemaShapeTests.DTCCodesMatchSAEorJ1939OrOemFormat; const // SAE J2012 + J1939 SPN + Volvo MID + OEM module-prefixed @@ -373,6 +418,9 @@ procedure TSchemaShapeTests.DTCCodesMatchSAEorJ1939OrOemFormat; end; end; +//------------------------------------------------------------------------------ +// MANUFACTURER KEYS ARE NON EMPTY +//------------------------------------------------------------------------------ procedure TSchemaShapeTests.ManufacturerKeysAreNonEmpty; var Root, Path, Key: string; @@ -398,6 +446,9 @@ procedure TSchemaShapeTests.ManufacturerKeysAreNonEmpty; end; end; +//------------------------------------------------------------------------------ +// VERSION FIELD IS ONE OR TWO +//------------------------------------------------------------------------------ procedure TSchemaShapeTests.VersionFieldIsOneOrTwo; var Root, Path: string; diff --git a/tests/Tests.OEM.SchemaV2.pas b/tests/Tests.OEM.SchemaV2.pas index a4129ff8..2147e71d 100644 --- a/tests/Tests.OEM.SchemaV2.pas +++ b/tests/Tests.OEM.SchemaV2.pas @@ -17,38 +17,107 @@ interface [TestFixture] TSchemaV2ParserTests = class public + /// + /// Parses coding block. + /// [Test] procedure ParsesCodingBlock; + /// + /// Coding block exposes bit field. + /// [Test] procedure CodingBlockExposesBitField; + /// + /// Coding block exposes enum field. + /// [Test] procedure CodingBlockExposesEnumField; + /// + /// Coding block has payload size. + /// [Test] procedure CodingBlockHasPayloadSize; + /// + /// Parses adaptations. + /// [Test] procedure ParsesAdaptations; + /// + /// Adaptation carries min max default. + /// [Test] procedure AdaptationCarriesMinMaxDefault; + /// + /// Parses actuator test. + /// [Test] procedure ParsesActuatorTest; + /// + /// Actuator test carries safety warning. + /// [Test] procedure ActuatorTestCarriesSafetyWarning; + /// + /// Parses live pid. + /// [Test] procedure ParsesLivePid; + /// + /// Live pid carries decoder info. + /// [Test] procedure LivePidCarriesDecoderInfo; + /// + /// Parses dtc extended data. + /// [Test] procedure ParsesDtcExtendedData; + /// + /// Dtc extended data carries record number. + /// [Test] procedure DtcExtendedDataCarriesRecordNumber; + /// + /// Legacy catalog still parses. + /// [Test] procedure LegacyCatalogStillParses; end; [TestFixture] TSchemaV2KindParserTests = class public + /// + /// Parses coding field kind bit. + /// [Test] procedure ParsesCodingFieldKindBit; + /// + /// Parses coding field kind enum. + /// [Test] procedure ParsesCodingFieldKindEnum; + /// + /// Parses adaptation kind u int16. + /// [Test] procedure ParsesAdaptationKindUInt16; + /// + /// Parses actuator response kind boolean. + /// [Test] procedure ParsesActuatorResponseKindBoolean; + /// + /// Parses live pid mode service22. + /// [Test] procedure ParsesLivePidModeService22; + /// + /// Parses dtc extended kind occurrence counter. + /// [Test] procedure ParsesDtcExtendedKindOccurrenceCounter; + /// + /// Unknown string returns unknown kind. + /// [Test] procedure UnknownStringReturnsUnknownKind; end; [TestFixture] TSchemaV2MergeTests = class public + /// + /// Merge replaces coding block by shared d i d. + /// [Test] procedure MergeReplacesCodingBlockBySharedDID; + /// + /// Merge appends new adaptation. + /// [Test] procedure MergeAppendsNewAdaptation; + /// + /// Merge missing file is silent. + /// [Test] procedure MergeMissingFileIsSilent; end; @@ -58,6 +127,9 @@ implementation System.SysUtils, System.IOUtils, OBD.OEM, OBD.OEM.Catalog.JSON, OBD.OEM.Catalog.Loader; +//------------------------------------------------------------------------------ +// FIXTURE PATH +//------------------------------------------------------------------------------ function FixturePath: string; begin // Tests run from .../tests, fixture lives in .../catalogs. @@ -69,6 +141,9 @@ function FixturePath: string; Result := TPath.Combine(GetCurrentDir, 'catalogs/test-schema-v2.json'); end; +//------------------------------------------------------------------------------ +// LOAD FIXTURE +//------------------------------------------------------------------------------ function LoadFixture: TOBDOEMJSONCatalog; begin Result := TOBDOEMJSONCatalog.Create(FixturePath); @@ -77,8 +152,13 @@ function LoadFixture: TOBDOEMJSONCatalog; //============================================================================== // JSON parser //============================================================================== + +//------------------------------------------------------------------------------ +// PARSES CODING BLOCK +//------------------------------------------------------------------------------ procedure TSchemaV2ParserTests.ParsesCodingBlock; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := LoadFixture; try @@ -88,8 +168,12 @@ procedure TSchemaV2ParserTests.ParsesCodingBlock; finally Cat.Free; end; end; +//------------------------------------------------------------------------------ +// CODING BLOCK EXPOSES BIT FIELD +//------------------------------------------------------------------------------ procedure TSchemaV2ParserTests.CodingBlockExposesBitField; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := LoadFixture; try @@ -101,6 +185,9 @@ procedure TSchemaV2ParserTests.CodingBlockExposesBitField; finally Cat.Free; end; end; +//------------------------------------------------------------------------------ +// CODING BLOCK EXPOSES ENUM FIELD +//------------------------------------------------------------------------------ procedure TSchemaV2ParserTests.CodingBlockExposesEnumField; var Cat: TOBDOEMJSONCatalog; @@ -119,8 +206,12 @@ procedure TSchemaV2ParserTests.CodingBlockExposesEnumField; finally Cat.Free; end; end; +//------------------------------------------------------------------------------ +// CODING BLOCK HAS PAYLOAD SIZE +//------------------------------------------------------------------------------ procedure TSchemaV2ParserTests.CodingBlockHasPayloadSize; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := LoadFixture; try @@ -128,8 +219,12 @@ procedure TSchemaV2ParserTests.CodingBlockHasPayloadSize; finally Cat.Free; end; end; +//------------------------------------------------------------------------------ +// PARSES ADAPTATIONS +//------------------------------------------------------------------------------ procedure TSchemaV2ParserTests.ParsesAdaptations; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := LoadFixture; try @@ -139,8 +234,12 @@ procedure TSchemaV2ParserTests.ParsesAdaptations; finally Cat.Free; end; end; +//------------------------------------------------------------------------------ +// ADAPTATION CARRIES MIN MAX DEFAULT +//------------------------------------------------------------------------------ procedure TSchemaV2ParserTests.AdaptationCarriesMinMaxDefault; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := LoadFixture; try @@ -151,8 +250,12 @@ procedure TSchemaV2ParserTests.AdaptationCarriesMinMaxDefault; finally Cat.Free; end; end; +//------------------------------------------------------------------------------ +// PARSES ACTUATOR TEST +//------------------------------------------------------------------------------ procedure TSchemaV2ParserTests.ParsesActuatorTest; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := LoadFixture; try @@ -163,8 +266,12 @@ procedure TSchemaV2ParserTests.ParsesActuatorTest; finally Cat.Free; end; end; +//------------------------------------------------------------------------------ +// ACTUATOR TEST CARRIES SAFETY WARNING +//------------------------------------------------------------------------------ procedure TSchemaV2ParserTests.ActuatorTestCarriesSafetyWarning; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := LoadFixture; try @@ -173,8 +280,12 @@ procedure TSchemaV2ParserTests.ActuatorTestCarriesSafetyWarning; finally Cat.Free; end; end; +//------------------------------------------------------------------------------ +// PARSES LIVE PID +//------------------------------------------------------------------------------ procedure TSchemaV2ParserTests.ParsesLivePid; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := LoadFixture; try @@ -185,8 +296,12 @@ procedure TSchemaV2ParserTests.ParsesLivePid; finally Cat.Free; end; end; +//------------------------------------------------------------------------------ +// LIVE PID CARRIES DECODER INFO +//------------------------------------------------------------------------------ procedure TSchemaV2ParserTests.LivePidCarriesDecoderInfo; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := LoadFixture; try @@ -196,8 +311,12 @@ procedure TSchemaV2ParserTests.LivePidCarriesDecoderInfo; finally Cat.Free; end; end; +//------------------------------------------------------------------------------ +// PARSES DTC EXTENDED DATA +//------------------------------------------------------------------------------ procedure TSchemaV2ParserTests.ParsesDtcExtendedData; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := LoadFixture; try @@ -207,8 +326,12 @@ procedure TSchemaV2ParserTests.ParsesDtcExtendedData; finally Cat.Free; end; end; +//------------------------------------------------------------------------------ +// DTC EXTENDED DATA CARRIES RECORD NUMBER +//------------------------------------------------------------------------------ procedure TSchemaV2ParserTests.DtcExtendedDataCarriesRecordNumber; -var Cat: TOBDOEMJSONCatalog; +var + Cat: TOBDOEMJSONCatalog; begin Cat := LoadFixture; try @@ -217,6 +340,9 @@ procedure TSchemaV2ParserTests.DtcExtendedDataCarriesRecordNumber; finally Cat.Free; end; end; +//------------------------------------------------------------------------------ +// LEGACY CATALOG STILL PARSES +//------------------------------------------------------------------------------ procedure TSchemaV2ParserTests.LegacyCatalogStillParses; var Cat: TOBDOEMJSONCatalog; @@ -241,36 +367,55 @@ procedure TSchemaV2ParserTests.LegacyCatalogStillParses; //============================================================================== // Kind parsers //============================================================================== + +//------------------------------------------------------------------------------ +// PARSES CODING FIELD KIND BIT +//------------------------------------------------------------------------------ procedure TSchemaV2KindParserTests.ParsesCodingFieldKindBit; begin Assert.AreEqual(Ord(cfkBit), Ord(ParseCodingFieldKind('bit'))); Assert.AreEqual(Ord(cfkUInt8), Ord(ParseCodingFieldKind('uint8'))); end; +//------------------------------------------------------------------------------ +// PARSES CODING FIELD KIND ENUM +//------------------------------------------------------------------------------ procedure TSchemaV2KindParserTests.ParsesCodingFieldKindEnum; begin Assert.AreEqual(Ord(cfkEnum), Ord(ParseCodingFieldKind('enum'))); Assert.AreEqual(Ord(cfkBitmask), Ord(ParseCodingFieldKind('bitmask'))); end; +//------------------------------------------------------------------------------ +// PARSES ADAPTATION KIND UINT16 +//------------------------------------------------------------------------------ procedure TSchemaV2KindParserTests.ParsesAdaptationKindUInt16; begin Assert.AreEqual(Ord(adkUInt16BE), Ord(ParseAdaptationKind('uint16_be'))); Assert.AreEqual(Ord(adkEnum), Ord(ParseAdaptationKind('enum'))); end; +//------------------------------------------------------------------------------ +// PARSES ACTUATOR RESPONSE KIND BOOLEAN +//------------------------------------------------------------------------------ procedure TSchemaV2KindParserTests.ParsesActuatorResponseKindBoolean; begin Assert.AreEqual(Ord(arkBoolean), Ord(ParseActuatorResponseKind('boolean'))); Assert.AreEqual(Ord(arkAscii), Ord(ParseActuatorResponseKind('ascii'))); end; +//------------------------------------------------------------------------------ +// PARSES LIVE PID MODE SERVICE22 +//------------------------------------------------------------------------------ procedure TSchemaV2KindParserTests.ParsesLivePidModeService22; begin Assert.AreEqual(Ord(lpmService22), Ord(ParseLivePIDMode('service22'))); Assert.AreEqual(Ord(lpmService01), Ord(ParseLivePIDMode('service01'))); end; +//------------------------------------------------------------------------------ +// PARSES DTC EXTENDED KIND OCCURRENCE COUNTER +//------------------------------------------------------------------------------ procedure TSchemaV2KindParserTests.ParsesDtcExtendedKindOccurrenceCounter; begin Assert.AreEqual(Ord(xdkOccurrenceCounter), @@ -279,6 +424,9 @@ procedure TSchemaV2KindParserTests.ParsesDtcExtendedKindOccurrenceCounter; Ord(ParseDtcExtendedKind('freeze_frame_template'))); end; +//------------------------------------------------------------------------------ +// UNKNOWN STRING RETURNS UNKNOWN KIND +//------------------------------------------------------------------------------ procedure TSchemaV2KindParserTests.UnknownStringReturnsUnknownKind; begin Assert.AreEqual(Ord(cfkUnknown), Ord(ParseCodingFieldKind('made_up_kind'))); @@ -290,6 +438,10 @@ procedure TSchemaV2KindParserTests.UnknownStringReturnsUnknownKind; //============================================================================== // Loader merge semantics //============================================================================== + +//------------------------------------------------------------------------------ +// MERGE REPLACES CODING BLOCK BY SHARED DID +//------------------------------------------------------------------------------ procedure TSchemaV2MergeTests.MergeReplacesCodingBlockBySharedDID; var Existing, Loaded: TArray; @@ -313,6 +465,9 @@ procedure TSchemaV2MergeTests.MergeReplacesCodingBlockBySharedDID; Assert.AreEqual('bcm_long_coding', Existing[0].Name); end; +//------------------------------------------------------------------------------ +// MERGE APPENDS NEW ADAPTATION +//------------------------------------------------------------------------------ procedure TSchemaV2MergeTests.MergeAppendsNewAdaptation; var CodingBlocks: TArray; @@ -329,6 +484,9 @@ procedure TSchemaV2MergeTests.MergeAppendsNewAdaptation; Assert.AreEqual('idle_rpm_target', Adaptations[0].Name); end; +//------------------------------------------------------------------------------ +// MERGE MISSING FILE IS SILENT +//------------------------------------------------------------------------------ procedure TSchemaV2MergeTests.MergeMissingFileIsSilent; var CodingBlocks: TArray; diff --git a/tests/Tests.OEM.SeedKey.pas b/tests/Tests.OEM.SeedKey.pas index b55548ff..2ca96326 100644 --- a/tests/Tests.OEM.SeedKey.pas +++ b/tests/Tests.OEM.SeedKey.pas @@ -13,50 +13,140 @@ interface [TestFixture] TSeedKeyAlgorithmTests = class public + /// + /// Twos complement matches textbook. + /// [Test] procedure TwosComplementMatchesTextbook; + /// + /// Twos complement carries across bytes. + /// [Test] procedure TwosComplementCarriesAcrossBytes; + /// + /// Twos complement rejects empty seed. + /// [Test] procedure TwosComplementRejectsEmptySeed; + /// + /// Xor mask tiles short mask. + /// [Test] procedure XorMaskTilesShortMask; + /// + /// Xor mask rejects empty mask. + /// [Test] procedure XorMaskRejectsEmptyMask; + /// + /// Byte rotate applies shift and rotation. + /// [Test] procedure ByteRotateAppliesShiftAndRotation; + /// + /// Byte rotate rejects invalid rotation. + /// [Test] procedure ByteRotateRejectsInvalidRotation; + /// + /// Constant key is seed independent. + /// [Test] procedure ConstantKeyIsSeedIndependent; end; [TestFixture] TSeedKeyRegistryTests = class public + /// + /// Register and find by level. + /// [Test] procedure RegisterAndFindByLevel; + /// + /// Newer registration wins over older. + /// [Test] procedure NewerRegistrationWinsOverOlder; + /// + /// Find all returns all insertions. + /// [Test] procedure FindAllReturnsAllInsertions; + /// + /// Unregister removes specific algorithm. + /// [Test] procedure UnregisterRemovesSpecificAlgorithm; + /// + /// Has algorithm reports levels. + /// [Test] procedure HasAlgorithmReportsLevels; + /// + /// Find returns nil for missing level. + /// [Test] procedure FindReturnsNilForMissingLevel; + /// + /// Clear wipes everything. + /// [Test] procedure ClearWipesEverything; end; [TestFixture] TSeedKeyFrameTests = class public + /// + /// Request seed frame rounds correctly. + /// [Test] procedure RequestSeedFrameRoundsCorrectly; + /// + /// Request seed rejects even level. + /// [Test] procedure RequestSeedRejectsEvenLevel; + /// + /// Send key frame adds level plus one. + /// [Test] procedure SendKeyFrameAddsLevelPlusOne; + /// + /// Send key rejects empty key. + /// [Test] procedure SendKeyRejectsEmptyKey; + /// + /// Extract seed returns payload. + /// [Test] procedure ExtractSeedReturnsPayload; + /// + /// Extract seed rejects wrong s i d. + /// [Test] procedure ExtractSeedRejectsWrongSID; + /// + /// Extract seed rejects level mismatch. + /// [Test] procedure ExtractSeedRejectsLevelMismatch; end; [TestFixture] TPerOEMSeedKeyTests = class public + /// + /// V w has starter algorithm for level1. + /// [Test] procedure VWHasStarterAlgorithmForLevel1; + /// + /// B m w has starter algorithm for level1. + /// [Test] procedure BMWHasStarterAlgorithmForLevel1; + /// + /// Mercedes has starter algorithm for level1. + /// [Test] procedure MercedesHasStarterAlgorithmForLevel1; + /// + /// Ford has starter algorithm for level1. + /// [Test] procedure FordHasStarterAlgorithmForLevel1; + /// + /// G m has starter algorithm for level1. + /// [Test] procedure GMHasStarterAlgorithmForLevel1; + /// + /// Stellantis has starter algorithm for level1. + /// [Test] procedure StellantisHasStarterAlgorithmForLevel1; + /// + /// Production override shadows starter. + /// [Test] procedure ProductionOverrideShadowsStarter; + /// + /// Starter algorithms are unverified. + /// [Test] procedure StarterAlgorithmsAreUnverified; end; @@ -71,6 +161,10 @@ implementation //============================================================================== // Algorithms //============================================================================== + +//------------------------------------------------------------------------------ +// TWOS COMPLEMENT MATCHES TEXTBOOK +//------------------------------------------------------------------------------ procedure TSeedKeyAlgorithmTests.TwosComplementMatchesTextbook; var Algo: IOBDSeedKeyAlgorithm; @@ -86,6 +180,9 @@ procedure TSeedKeyAlgorithmTests.TwosComplementMatchesTextbook; Assert.AreEqual(Byte($FF), Key[3]); end; +//------------------------------------------------------------------------------ +// TWOS COMPLEMENT CARRIES ACROSS BYTES +//------------------------------------------------------------------------------ procedure TSeedKeyAlgorithmTests.TwosComplementCarriesAcrossBytes; var Algo: IOBDSeedKeyAlgorithm; @@ -100,6 +197,9 @@ procedure TSeedKeyAlgorithmTests.TwosComplementCarriesAcrossBytes; Assert.AreEqual(Byte($88), Key[3]); end; +//------------------------------------------------------------------------------ +// TWOS COMPLEMENT REJECTS EMPTY SEED +//------------------------------------------------------------------------------ procedure TSeedKeyAlgorithmTests.TwosComplementRejectsEmptySeed; var Algo: IOBDSeedKeyAlgorithm; @@ -110,6 +210,9 @@ procedure TSeedKeyAlgorithmTests.TwosComplementRejectsEmptySeed; EOBDSeedKeyError); end; +//------------------------------------------------------------------------------ +// XOR MASK TILES SHORT MASK +//------------------------------------------------------------------------------ procedure TSeedKeyAlgorithmTests.XorMaskTilesShortMask; var Algo: IOBDSeedKeyAlgorithm; @@ -123,6 +226,9 @@ procedure TSeedKeyAlgorithmTests.XorMaskTilesShortMask; Assert.AreEqual(Byte($AA), Key[3]); end; +//------------------------------------------------------------------------------ +// XOR MASK REJECTS EMPTY MASK +//------------------------------------------------------------------------------ procedure TSeedKeyAlgorithmTests.XorMaskRejectsEmptyMask; begin Assert.WillRaise( @@ -130,6 +236,9 @@ procedure TSeedKeyAlgorithmTests.XorMaskRejectsEmptyMask; EOBDSeedKeyError); end; +//------------------------------------------------------------------------------ +// BYTE ROTATE APPLIES SHIFT AND ROTATION +//------------------------------------------------------------------------------ procedure TSeedKeyAlgorithmTests.ByteRotateAppliesShiftAndRotation; var Algo: IOBDSeedKeyAlgorithm; @@ -144,6 +253,9 @@ procedure TSeedKeyAlgorithmTests.ByteRotateAppliesShiftAndRotation; Assert.AreEqual(Byte($11), Key[3]); end; +//------------------------------------------------------------------------------ +// BYTE ROTATE REJECTS INVALID ROTATION +//------------------------------------------------------------------------------ procedure TSeedKeyAlgorithmTests.ByteRotateRejectsInvalidRotation; begin Assert.WillRaise( @@ -153,6 +265,9 @@ procedure TSeedKeyAlgorithmTests.ByteRotateRejectsInvalidRotation; EOBDSeedKeyError); end; +//------------------------------------------------------------------------------ +// CONSTANT KEY IS SEED INDEPENDENT +//------------------------------------------------------------------------------ procedure TSeedKeyAlgorithmTests.ConstantKeyIsSeedIndependent; var Algo: IOBDSeedKeyAlgorithm; @@ -171,6 +286,10 @@ procedure TSeedKeyAlgorithmTests.ConstantKeyIsSeedIndependent; //============================================================================== // Registry //============================================================================== + +//------------------------------------------------------------------------------ +// REGISTER AND FIND BY LEVEL +//------------------------------------------------------------------------------ procedure TSeedKeyRegistryTests.RegisterAndFindByLevel; var Reg: TOBDSeedKeyRegistry; @@ -187,6 +306,9 @@ procedure TSeedKeyRegistryTests.RegisterAndFindByLevel; end; end; +//------------------------------------------------------------------------------ +// NEWER REGISTRATION WINS OVER OLDER +//------------------------------------------------------------------------------ procedure TSeedKeyRegistryTests.NewerRegistrationWinsOverOlder; var Reg: TOBDSeedKeyRegistry; @@ -205,6 +327,9 @@ procedure TSeedKeyRegistryTests.NewerRegistrationWinsOverOlder; end; end; +//------------------------------------------------------------------------------ +// FIND ALL RETURNS ALL INSERTIONS +//------------------------------------------------------------------------------ procedure TSeedKeyRegistryTests.FindAllReturnsAllInsertions; var Reg: TOBDSeedKeyRegistry; @@ -221,6 +346,9 @@ procedure TSeedKeyRegistryTests.FindAllReturnsAllInsertions; end; end; +//------------------------------------------------------------------------------ +// UNREGISTER REMOVES SPECIFIC ALGORITHM +//------------------------------------------------------------------------------ procedure TSeedKeyRegistryTests.UnregisterRemovesSpecificAlgorithm; var Reg: TOBDSeedKeyRegistry; @@ -240,6 +368,9 @@ procedure TSeedKeyRegistryTests.UnregisterRemovesSpecificAlgorithm; end; end; +//------------------------------------------------------------------------------ +// HAS ALGORITHM REPORTS LEVELS +//------------------------------------------------------------------------------ procedure TSeedKeyRegistryTests.HasAlgorithmReportsLevels; var Reg: TOBDSeedKeyRegistry; @@ -254,6 +385,9 @@ procedure TSeedKeyRegistryTests.HasAlgorithmReportsLevels; end; end; +//------------------------------------------------------------------------------ +// FIND RETURNS NIL FOR MISSING LEVEL +//------------------------------------------------------------------------------ procedure TSeedKeyRegistryTests.FindReturnsNilForMissingLevel; var Reg: TOBDSeedKeyRegistry; @@ -266,6 +400,9 @@ procedure TSeedKeyRegistryTests.FindReturnsNilForMissingLevel; end; end; +//------------------------------------------------------------------------------ +// CLEAR WIPES EVERYTHING +//------------------------------------------------------------------------------ procedure TSeedKeyRegistryTests.ClearWipesEverything; var Reg: TOBDSeedKeyRegistry; @@ -283,6 +420,10 @@ procedure TSeedKeyRegistryTests.ClearWipesEverything; //============================================================================== // Frame helpers //============================================================================== + +//------------------------------------------------------------------------------ +// REQUEST SEED FRAME ROUNDS CORRECTLY +//------------------------------------------------------------------------------ procedure TSeedKeyFrameTests.RequestSeedFrameRoundsCorrectly; var F: TBytes; @@ -293,6 +434,9 @@ procedure TSeedKeyFrameTests.RequestSeedFrameRoundsCorrectly; Assert.AreEqual(Byte($05), F[1]); end; +//------------------------------------------------------------------------------ +// REQUEST SEED REJECTS EVEN LEVEL +//------------------------------------------------------------------------------ procedure TSeedKeyFrameTests.RequestSeedRejectsEvenLevel; begin Assert.WillRaise( @@ -300,6 +444,9 @@ procedure TSeedKeyFrameTests.RequestSeedRejectsEvenLevel; EOBDSeedKeyError); end; +//------------------------------------------------------------------------------ +// SEND KEY FRAME ADDS LEVEL PLUS ONE +//------------------------------------------------------------------------------ procedure TSeedKeyFrameTests.SendKeyFrameAddsLevelPlusOne; var F: TBytes; @@ -312,6 +459,9 @@ procedure TSeedKeyFrameTests.SendKeyFrameAddsLevelPlusOne; Assert.AreEqual(Byte($AD), F[3]); end; +//------------------------------------------------------------------------------ +// SEND KEY REJECTS EMPTY KEY +//------------------------------------------------------------------------------ procedure TSeedKeyFrameTests.SendKeyRejectsEmptyKey; begin Assert.WillRaise( @@ -319,6 +469,9 @@ procedure TSeedKeyFrameTests.SendKeyRejectsEmptyKey; EOBDSeedKeyError); end; +//------------------------------------------------------------------------------ +// EXTRACT SEED RETURNS PAYLOAD +//------------------------------------------------------------------------------ procedure TSeedKeyFrameTests.ExtractSeedReturnsPayload; var Seed: TBytes; @@ -329,6 +482,9 @@ procedure TSeedKeyFrameTests.ExtractSeedReturnsPayload; Assert.AreEqual(Byte($44), Seed[3]); end; +//------------------------------------------------------------------------------ +// EXTRACT SEED REJECTS WRONG SID +//------------------------------------------------------------------------------ procedure TSeedKeyFrameTests.ExtractSeedRejectsWrongSID; begin Assert.WillRaise( @@ -338,6 +494,9 @@ procedure TSeedKeyFrameTests.ExtractSeedRejectsWrongSID; EOBDSeedKeyError); end; +//------------------------------------------------------------------------------ +// EXTRACT SEED REJECTS LEVEL MISMATCH +//------------------------------------------------------------------------------ procedure TSeedKeyFrameTests.ExtractSeedRejectsLevelMismatch; begin Assert.WillRaise( @@ -350,48 +509,76 @@ procedure TSeedKeyFrameTests.ExtractSeedRejectsLevelMismatch; //============================================================================== // Per-OEM extensions //============================================================================== + +//------------------------------------------------------------------------------ +// VWHAS STARTER ALGORITHM FOR LEVEL1 +//------------------------------------------------------------------------------ procedure TPerOEMSeedKeyTests.VWHasStarterAlgorithmForLevel1; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionVW.Create; Assert.IsNotNull(Ext.SeedKeyRegistry.Find($01)); end; +//------------------------------------------------------------------------------ +// BMWHAS STARTER ALGORITHM FOR LEVEL1 +//------------------------------------------------------------------------------ procedure TPerOEMSeedKeyTests.BMWHasStarterAlgorithmForLevel1; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionBMW.Create; Assert.IsNotNull(Ext.SeedKeyRegistry.Find($01)); end; +//------------------------------------------------------------------------------ +// MERCEDES HAS STARTER ALGORITHM FOR LEVEL1 +//------------------------------------------------------------------------------ procedure TPerOEMSeedKeyTests.MercedesHasStarterAlgorithmForLevel1; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionMercedes.Create; Assert.IsNotNull(Ext.SeedKeyRegistry.Find($01)); end; +//------------------------------------------------------------------------------ +// FORD HAS STARTER ALGORITHM FOR LEVEL1 +//------------------------------------------------------------------------------ procedure TPerOEMSeedKeyTests.FordHasStarterAlgorithmForLevel1; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionFord.Create; Assert.IsNotNull(Ext.SeedKeyRegistry.Find($01)); end; +//------------------------------------------------------------------------------ +// GMHAS STARTER ALGORITHM FOR LEVEL1 +//------------------------------------------------------------------------------ procedure TPerOEMSeedKeyTests.GMHasStarterAlgorithmForLevel1; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionGM.Create; Assert.IsNotNull(Ext.SeedKeyRegistry.Find($01)); end; +//------------------------------------------------------------------------------ +// STELLANTIS HAS STARTER ALGORITHM FOR LEVEL1 +//------------------------------------------------------------------------------ procedure TPerOEMSeedKeyTests.StellantisHasStarterAlgorithmForLevel1; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionStellantis.Create; Assert.IsNotNull(Ext.SeedKeyRegistry.Find($01)); end; +//------------------------------------------------------------------------------ +// PRODUCTION OVERRIDE SHADOWS STARTER +//------------------------------------------------------------------------------ procedure TPerOEMSeedKeyTests.ProductionOverrideShadowsStarter; var Ext: IOBDOEMExtension; @@ -412,8 +599,12 @@ procedure TPerOEMSeedKeyTests.ProductionOverrideShadowsStarter; 'production override should report verified=true'); end; +//------------------------------------------------------------------------------ +// STARTER ALGORITHMS ARE UNVERIFIED +//------------------------------------------------------------------------------ procedure TPerOEMSeedKeyTests.StarterAlgorithmsAreUnverified; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionVW.Create; Assert.IsFalse(Ext.SeedKeyRegistry.Find($01).Verified, diff --git a/tests/Tests.OEM.ServiceFunction.pas b/tests/Tests.OEM.ServiceFunction.pas index 2831765d..34f1f30d 100644 --- a/tests/Tests.OEM.ServiceFunction.pas +++ b/tests/Tests.OEM.ServiceFunction.pas @@ -18,46 +18,124 @@ interface [TestFixture] TServiceFunctionRegistryTests = class public + /// + /// Name matches kind is case insensitive. + /// [Test] procedure NameMatchesKindIsCaseInsensitive; + /// + /// Name matches kind recognises substring. + /// [Test] procedure NameMatchesKindRecognisesSubstring; + /// + /// Name matches kind rejects unrelated. + /// [Test] procedure NameMatchesKindRejectsUnrelated; + /// + /// Classify oil reset tokens. + /// [Test] procedure ClassifyOilResetTokens; + /// + /// Classify epb tokens. + /// [Test] procedure ClassifyEpbTokens; + /// + /// Classify dpf tokens. + /// [Test] procedure ClassifyDpfTokens; + /// + /// Classify tpms tokens. + /// [Test] procedure ClassifyTpmsTokens; + /// + /// Classify battery registration tokens. + /// [Test] procedure ClassifyBatteryRegistrationTokens; + /// + /// Classify sas calibration tokens. + /// [Test] procedure ClassifySasCalibrationTokens; + /// + /// Classify immo relearn tokens. + /// [Test] procedure ClassifyImmoRelearnTokens; + /// + /// Classify unknown returns sf unknown. + /// [Test] procedure ClassifyUnknownReturnsSfUnknown; end; [TestFixture] TServiceFunctionLookupTests = class public + /// + /// Ferrari resolves oil reset. + /// [Test] procedure FerrariResolvesOilReset; + /// + /// Mahindra resolves dpf regen. + /// [Test] procedure MahindraResolvesDpfRegen; + /// + /// Tata resolves battery registration. + /// [Test] procedure TataResolvesBatteryRegistration; + /// + /// Mini resolves epb. + /// [Test] procedure MiniResolvesEpb; + /// + /// Mini resolves sas calibration. + /// [Test] procedure MiniResolvesSasCalibration; + /// + /// Mini resolves tpms relearn. + /// [Test] procedure MiniResolvesTpmsRelearn; + /// + /// Mini resolves immo relearn. + /// [Test] procedure MiniResolvesImmoRelearn; + /// + /// Unsupported kind returns false. + /// [Test] procedure UnsupportedKindReturnsFalse; + /// + /// Nil extension returns false. + /// [Test] procedure NilExtensionReturnsFalse; end; [TestFixture] TServiceFunctionEnumerationTests = class public + /// + /// Mini lists multiple service functions. + /// [Test] procedure MiniListsMultipleServiceFunctions; + /// + /// Mahindra lists at least oil and dpf. + /// [Test] procedure MahindraListsAtLeastOilAndDpf; + /// + /// List skips unknown names. + /// [Test] procedure ListSkipsUnknownNames; end; [TestFixture] TServiceFunctionFrameTests = class public + /// + /// Frame wraps routine id with sid and sub function. + /// [Test] procedure FrameWrapsRoutineIdWithSidAndSubFunction; + /// + /// Frame appends input data. + /// [Test] procedure FrameAppendsInputData; + /// + /// Kind name produces human label. + /// [Test] procedure KindNameProducesHumanLabel; end; @@ -72,6 +150,10 @@ implementation //============================================================================== // Registry / classification //============================================================================== + +//------------------------------------------------------------------------------ +// NAME MATCHES KIND IS CASE INSENSITIVE +//------------------------------------------------------------------------------ procedure TServiceFunctionRegistryTests.NameMatchesKindIsCaseInsensitive; begin Assert.IsTrue(TOBDServiceFunctionRegistry.NameMatchesKind( @@ -80,6 +162,9 @@ procedure TServiceFunctionRegistryTests.NameMatchesKindIsCaseInsensitive; 'Ferrari_Oil_Life_Reset', sfOilLifeReset)); end; +//------------------------------------------------------------------------------ +// NAME MATCHES KIND RECOGNISES SUBSTRING +//------------------------------------------------------------------------------ procedure TServiceFunctionRegistryTests.NameMatchesKindRecognisesSubstring; begin // 'ferrari_oil_life_reset' should match the 'oil_life' token. @@ -90,6 +175,9 @@ procedure TServiceFunctionRegistryTests.NameMatchesKindRecognisesSubstring; 'mini_battery_register', sfBatteryRegistration)); end; +//------------------------------------------------------------------------------ +// NAME MATCHES KIND REJECTS UNRELATED +//------------------------------------------------------------------------------ procedure TServiceFunctionRegistryTests.NameMatchesKindRejectsUnrelated; begin Assert.IsFalse(TOBDServiceFunctionRegistry.NameMatchesKind( @@ -98,6 +186,9 @@ procedure TServiceFunctionRegistryTests.NameMatchesKindRejectsUnrelated; 'fa_write', sfBatteryRegistration)); end; +//------------------------------------------------------------------------------ +// CLASSIFY OIL RESET TOKENS +//------------------------------------------------------------------------------ procedure TServiceFunctionRegistryTests.ClassifyOilResetTokens; begin Assert.AreEqual(Ord(sfOilLifeReset), Ord( @@ -108,6 +199,9 @@ procedure TServiceFunctionRegistryTests.ClassifyOilResetTokens; TOBDServiceFunctionRegistry.ClassifyName('reset_service_indicator'))); end; +//------------------------------------------------------------------------------ +// CLASSIFY EPB TOKENS +//------------------------------------------------------------------------------ procedure TServiceFunctionRegistryTests.ClassifyEpbTokens; begin Assert.AreEqual(Ord(sfEPBService), Ord( @@ -116,6 +210,9 @@ procedure TServiceFunctionRegistryTests.ClassifyEpbTokens; TOBDServiceFunctionRegistry.ClassifyName('parking_brake_service_mode'))); end; +//------------------------------------------------------------------------------ +// CLASSIFY DPF TOKENS +//------------------------------------------------------------------------------ procedure TServiceFunctionRegistryTests.ClassifyDpfTokens; begin Assert.AreEqual(Ord(sfDPFRegen), Ord( @@ -124,6 +221,9 @@ procedure TServiceFunctionRegistryTests.ClassifyDpfTokens; TOBDServiceFunctionRegistry.ClassifyName('forced_dpf_regen'))); end; +//------------------------------------------------------------------------------ +// CLASSIFY TPMS TOKENS +//------------------------------------------------------------------------------ procedure TServiceFunctionRegistryTests.ClassifyTpmsTokens; begin Assert.AreEqual(Ord(sfTPMSRelearn), Ord( @@ -132,6 +232,9 @@ procedure TServiceFunctionRegistryTests.ClassifyTpmsTokens; TOBDServiceFunctionRegistry.ClassifyName('tpms_relearn'))); end; +//------------------------------------------------------------------------------ +// CLASSIFY BATTERY REGISTRATION TOKENS +//------------------------------------------------------------------------------ procedure TServiceFunctionRegistryTests.ClassifyBatteryRegistrationTokens; begin Assert.AreEqual(Ord(sfBatteryRegistration), Ord( @@ -140,6 +243,9 @@ procedure TServiceFunctionRegistryTests.ClassifyBatteryRegistrationTokens; TOBDServiceFunctionRegistry.ClassifyName('bmw_battery_registration'))); end; +//------------------------------------------------------------------------------ +// CLASSIFY SAS CALIBRATION TOKENS +//------------------------------------------------------------------------------ procedure TServiceFunctionRegistryTests.ClassifySasCalibrationTokens; begin Assert.AreEqual(Ord(sfSASCalibration), Ord( @@ -148,6 +254,9 @@ procedure TServiceFunctionRegistryTests.ClassifySasCalibrationTokens; TOBDServiceFunctionRegistry.ClassifyName('steering_angle_reset'))); end; +//------------------------------------------------------------------------------ +// CLASSIFY IMMO RELEARN TOKENS +//------------------------------------------------------------------------------ procedure TServiceFunctionRegistryTests.ClassifyImmoRelearnTokens; begin Assert.AreEqual(Ord(sfImmoRelearn), Ord( @@ -156,6 +265,9 @@ procedure TServiceFunctionRegistryTests.ClassifyImmoRelearnTokens; TOBDServiceFunctionRegistry.ClassifyName('mb_eis_relearn'))); end; +//------------------------------------------------------------------------------ +// CLASSIFY UNKNOWN RETURNS SF UNKNOWN +//------------------------------------------------------------------------------ procedure TServiceFunctionRegistryTests.ClassifyUnknownReturnsSfUnknown; begin Assert.AreEqual(Ord(sfUnknown), Ord( @@ -167,6 +279,10 @@ procedure TServiceFunctionRegistryTests.ClassifyUnknownReturnsSfUnknown; //============================================================================== // Lookup against shipped OEM catalogs //============================================================================== + +//------------------------------------------------------------------------------ +// FERRARI RESOLVES OIL RESET +//------------------------------------------------------------------------------ procedure TServiceFunctionLookupTests.FerrariResolvesOilReset; var Ext: IOBDOEMExtension; @@ -179,6 +295,9 @@ procedure TServiceFunctionLookupTests.FerrariResolvesOilReset; Assert.IsTrue(Func.RoutineId <> 0, 'routine id must be populated'); end; +//------------------------------------------------------------------------------ +// MAHINDRA RESOLVES DPF REGEN +//------------------------------------------------------------------------------ procedure TServiceFunctionLookupTests.MahindraResolvesDpfRegen; var Ext: IOBDOEMExtension; @@ -189,6 +308,9 @@ procedure TServiceFunctionLookupTests.MahindraResolvesDpfRegen; Assert.AreEqual('mahindra_dpf_force_regen', Func.RoutineName); end; +//------------------------------------------------------------------------------ +// TATA RESOLVES BATTERY REGISTRATION +//------------------------------------------------------------------------------ procedure TServiceFunctionLookupTests.TataResolvesBatteryRegistration; var Ext: IOBDOEMExtension; @@ -199,6 +321,9 @@ procedure TServiceFunctionLookupTests.TataResolvesBatteryRegistration; Assert.AreEqual('tata_battery_register', Func.RoutineName); end; +//------------------------------------------------------------------------------ +// MINI RESOLVES EPB +//------------------------------------------------------------------------------ procedure TServiceFunctionLookupTests.MiniResolvesEpb; var Ext: IOBDOEMExtension; @@ -209,6 +334,9 @@ procedure TServiceFunctionLookupTests.MiniResolvesEpb; Assert.AreEqual('mini_epb_service', Func.RoutineName); end; +//------------------------------------------------------------------------------ +// MINI RESOLVES SAS CALIBRATION +//------------------------------------------------------------------------------ procedure TServiceFunctionLookupTests.MiniResolvesSasCalibration; var Ext: IOBDOEMExtension; @@ -219,6 +347,9 @@ procedure TServiceFunctionLookupTests.MiniResolvesSasCalibration; Assert.AreEqual('sas_calibration', Func.RoutineName); end; +//------------------------------------------------------------------------------ +// MINI RESOLVES TPMS RELEARN +//------------------------------------------------------------------------------ procedure TServiceFunctionLookupTests.MiniResolvesTpmsRelearn; var Ext: IOBDOEMExtension; @@ -229,6 +360,9 @@ procedure TServiceFunctionLookupTests.MiniResolvesTpmsRelearn; Assert.AreEqual('mini_rdc_relearn', Func.RoutineName); end; +//------------------------------------------------------------------------------ +// MINI RESOLVES IMMO RELEARN +//------------------------------------------------------------------------------ procedure TServiceFunctionLookupTests.MiniResolvesImmoRelearn; var Ext: IOBDOEMExtension; @@ -239,6 +373,9 @@ procedure TServiceFunctionLookupTests.MiniResolvesImmoRelearn; Assert.AreEqual('mini_cas_relearn', Func.RoutineName); end; +//------------------------------------------------------------------------------ +// UNSUPPORTED KIND RETURNS FALSE +//------------------------------------------------------------------------------ procedure TServiceFunctionLookupTests.UnsupportedKindReturnsFalse; var Ext: IOBDOEMExtension; @@ -250,6 +387,9 @@ procedure TServiceFunctionLookupTests.UnsupportedKindReturnsFalse; Assert.AreEqual(Ord(sfUnknown), Ord(Func.Kind)); end; +//------------------------------------------------------------------------------ +// NIL EXTENSION RETURNS FALSE +//------------------------------------------------------------------------------ procedure TServiceFunctionLookupTests.NilExtensionReturnsFalse; var Func: TOBDServiceFunction; @@ -260,6 +400,10 @@ procedure TServiceFunctionLookupTests.NilExtensionReturnsFalse; //============================================================================== // Enumeration //============================================================================== + +//------------------------------------------------------------------------------ +// MINI LISTS MULTIPLE SERVICE FUNCTIONS +//------------------------------------------------------------------------------ procedure TServiceFunctionEnumerationTests.MiniListsMultipleServiceFunctions; var Ext: IOBDOEMExtension; @@ -286,6 +430,9 @@ procedure TServiceFunctionEnumerationTests.MiniListsMultipleServiceFunctions; Assert.IsTrue(HasImmo, 'MINI list should include CAS relearn'); end; +//------------------------------------------------------------------------------ +// MAHINDRA LISTS AT LEAST OIL AND DPF +//------------------------------------------------------------------------------ procedure TServiceFunctionEnumerationTests.MahindraListsAtLeastOilAndDpf; var Ext: IOBDOEMExtension; @@ -305,6 +452,9 @@ procedure TServiceFunctionEnumerationTests.MahindraListsAtLeastOilAndDpf; Assert.IsTrue(HasDpf, 'Mahindra should expose a forced DPF regeneration'); end; +//------------------------------------------------------------------------------ +// LIST SKIPS UNKNOWN NAMES +//------------------------------------------------------------------------------ procedure TServiceFunctionEnumerationTests.ListSkipsUnknownNames; var Ext: IOBDOEMExtension; @@ -323,6 +473,10 @@ procedure TServiceFunctionEnumerationTests.ListSkipsUnknownNames; //============================================================================== // Frame builder + display labels //============================================================================== + +//------------------------------------------------------------------------------ +// FRAME WRAPS ROUTINE ID WITH SID AND SUB FUNCTION +//------------------------------------------------------------------------------ procedure TServiceFunctionFrameTests.FrameWrapsRoutineIdWithSidAndSubFunction; var Func: TOBDServiceFunction; @@ -339,6 +493,9 @@ procedure TServiceFunctionFrameTests.FrameWrapsRoutineIdWithSidAndSubFunction; Assert.AreEqual($04, Integer(Frame[3]), 'RID lo byte'); end; +//------------------------------------------------------------------------------ +// FRAME APPENDS INPUT DATA +//------------------------------------------------------------------------------ procedure TServiceFunctionFrameTests.FrameAppendsInputData; var Func: TOBDServiceFunction; @@ -353,6 +510,9 @@ procedure TServiceFunctionFrameTests.FrameAppendsInputData; Assert.AreEqual($BB, Integer(Frame[5])); end; +//------------------------------------------------------------------------------ +// KIND NAME PRODUCES HUMAN LABEL +//------------------------------------------------------------------------------ procedure TServiceFunctionFrameTests.KindNameProducesHumanLabel; begin Assert.AreEqual('Oil Life Reset', ServiceFunctionKindName(sfOilLifeReset)); diff --git a/tests/Tests.OEM.ServiceRoutines.pas b/tests/Tests.OEM.ServiceRoutines.pas new file mode 100644 index 00000000..ecc8cd76 --- /dev/null +++ b/tests/Tests.OEM.ServiceRoutines.pas @@ -0,0 +1,244 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.OEM.ServiceRoutines.pas +// CONTENTS : Tests for OBD.OEM.ServiceRoutines +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.OEM.ServiceRoutines; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TServiceRoutinesTests = class + public + /// + /// Registry has at least thirty. + /// + [Test] procedure RegistryHasAtLeastThirty; + /// + /// Every entry has citation. + /// + [Test] procedure EveryEntryHasCitation; + /// + /// Every entry has non empty key and name. + /// + [Test] procedure EveryEntryHasNonEmptyKeyAndName; + /// + /// R i ds are non zero. + /// + [Test] procedure RIDsAreNonZero; + /// + /// Sub function is valid u d s. + /// + [Test] procedure SubFunctionIsValidUDS; + /// + /// Find is case insensitive. + /// + [Test] procedure FindIsCaseInsensitive; + /// + /// Get by category returns maintenance. + /// + [Test] procedure GetByCategoryReturnsMaintenance; + /// + /// Get by o e m returns b m w routines. + /// + [Test] procedure GetByOEMReturnsBMWRoutines; + /// + /// Frame builder produces correct layout. + /// + [Test] procedure FrameBuilderProducesCorrectLayout; + /// + /// Frame builder rejects bad sub function. + /// + [Test] procedure FrameBuilderRejectsBadSubFunction; + /// + /// No duplicate keys. + /// + [Test] procedure NoDuplicateKeys; + end; + +implementation + +uses + System.SysUtils, OBD.OEM.ServiceRoutines; + +//------------------------------------------------------------------------------ +// REGISTRY HAS AT LEAST THIRTY +//------------------------------------------------------------------------------ +procedure TServiceRoutinesTests.RegistryHasAtLeastThirty; +begin + Assert.IsTrue(TOBDServiceRoutineRegistry.Instance.Count >= 25, + 'Should have at least 25 routines, got ' + + IntToStr(TOBDServiceRoutineRegistry.Instance.Count)); +end; + +//------------------------------------------------------------------------------ +// EVERY ENTRY HAS CITATION +//------------------------------------------------------------------------------ +procedure TServiceRoutinesTests.EveryEntryHasCitation; +var + I: Integer; + R: TOBDServiceRoutine; +begin + for I := 0 to TOBDServiceRoutineRegistry.Instance.Count - 1 do + begin + R := TOBDServiceRoutineRegistry.Instance.Get(I); + Assert.IsNotEmpty(R.Citation, + 'Routine ' + R.Key + ' missing citation'); + end; +end; + +//------------------------------------------------------------------------------ +// EVERY ENTRY HAS NON EMPTY KEY AND NAME +//------------------------------------------------------------------------------ +procedure TServiceRoutinesTests.EveryEntryHasNonEmptyKeyAndName; +var + I: Integer; + R: TOBDServiceRoutine; +begin + for I := 0 to TOBDServiceRoutineRegistry.Instance.Count - 1 do + begin + R := TOBDServiceRoutineRegistry.Instance.Get(I); + Assert.IsNotEmpty(R.Key); + Assert.IsNotEmpty(R.DisplayName); + end; +end; + +//------------------------------------------------------------------------------ +// RIDS ARE NON ZERO +//------------------------------------------------------------------------------ +procedure TServiceRoutinesTests.RIDsAreNonZero; +var + I: Integer; + R: TOBDServiceRoutine; +begin + for I := 0 to TOBDServiceRoutineRegistry.Instance.Count - 1 do + begin + R := TOBDServiceRoutineRegistry.Instance.Get(I); + Assert.IsTrue(R.RoutineIdentifier <> 0, + R.Key + ' has zero RID'); + end; +end; + +//------------------------------------------------------------------------------ +// SUB FUNCTION IS VALID UDS +//------------------------------------------------------------------------------ +procedure TServiceRoutinesTests.SubFunctionIsValidUDS; +var + I: Integer; + R: TOBDServiceRoutine; +begin + for I := 0 to TOBDServiceRoutineRegistry.Instance.Count - 1 do + begin + R := TOBDServiceRoutineRegistry.Instance.Get(I); + Assert.IsTrue(R.SubFunction in [$01, $02, $03], + R.Key + ' has invalid sub-function'); + end; +end; + +//------------------------------------------------------------------------------ +// FIND IS CASE INSENSITIVE +//------------------------------------------------------------------------------ +procedure TServiceRoutinesTests.FindIsCaseInsensitive; +var + R: TOBDServiceRoutine; +begin + Assert.IsTrue(TOBDServiceRoutineRegistry.Instance.Find('OIL_RESET_BMW', R)); + Assert.IsTrue(TOBDServiceRoutineRegistry.Instance.Find('Oil_Reset_BMW', R)); + Assert.IsTrue(TOBDServiceRoutineRegistry.Instance.Find('oil_reset_bmw', R)); + Assert.AreEqual('Oil Service Reset (BMW CBS)', R.DisplayName); +end; + +//------------------------------------------------------------------------------ +// GET BY CATEGORY RETURNS MAINTENANCE +//------------------------------------------------------------------------------ +procedure TServiceRoutinesTests.GetByCategoryReturnsMaintenance; +var + Routines: TArray; +begin + TOBDServiceRoutineRegistry.Instance.GetByCategory(srcMaintenance, Routines); + Assert.IsTrue(Length(Routines) >= 5, + 'Maintenance category should have several entries'); +end; + +//------------------------------------------------------------------------------ +// GET BY OEMRETURNS BMWROUTINES +//------------------------------------------------------------------------------ +procedure TServiceRoutinesTests.GetByOEMReturnsBMWRoutines; +var + Routines: TArray; + R: TOBDServiceRoutine; + Found: Boolean; +begin + TOBDServiceRoutineRegistry.Instance.GetByOEM('bmw', Routines); + Assert.IsTrue(Length(Routines) >= 3, 'Expected several BMW routines'); + Found := False; + for R in Routines do + if R.Key = 'oil_reset_bmw' then Found := True; + Assert.IsTrue(Found, 'BMW lookup should include oil_reset_bmw'); +end; + +//------------------------------------------------------------------------------ +// FRAME BUILDER PRODUCES CORRECT LAYOUT +//------------------------------------------------------------------------------ +procedure TServiceRoutinesTests.FrameBuilderProducesCorrectLayout; +var + R: TOBDServiceRoutine; + Frame: TBytes; +begin + Assert.IsTrue(TOBDServiceRoutineRegistry.Instance.Find('sas_zero', R)); + Frame := BuildRoutineControlFrame(R); + Assert.AreEqual($31, Integer(Frame[0])); + Assert.AreEqual($01, Integer(Frame[1])); + Assert.AreEqual($03, Integer(Frame[2])); // RID hi (0x0301) + Assert.AreEqual($01, Integer(Frame[3])); // RID lo + Assert.AreEqual(4, Length(Frame), 'No OptionRecord -> 4 bytes total'); +end; + +//------------------------------------------------------------------------------ +// FRAME BUILDER REJECTS BAD SUB FUNCTION +//------------------------------------------------------------------------------ +procedure TServiceRoutinesTests.FrameBuilderRejectsBadSubFunction; +var + R: TOBDServiceRoutine; +begin + R := Default(TOBDServiceRoutine); + R.RoutineIdentifier := $0301; + R.SubFunction := $99; + Assert.WillRaise( + procedure begin BuildRoutineControlFrame(R); end, + EOBDServiceRoutine); +end; + +//------------------------------------------------------------------------------ +// NO DUPLICATE KEYS +//------------------------------------------------------------------------------ +procedure TServiceRoutinesTests.NoDuplicateKeys; +var + Seen: TArray; + I, J: Integer; + R: TOBDServiceRoutine; +begin + SetLength(Seen, TOBDServiceRoutineRegistry.Instance.Count); + for I := 0 to TOBDServiceRoutineRegistry.Instance.Count - 1 do + begin + R := TOBDServiceRoutineRegistry.Instance.Get(I); + for J := 0 to I - 1 do + Assert.AreNotEqual(Seen[J], R.Key, 'Duplicate key: ' + R.Key); + Seen[I] := R.Key; + end; +end; + +initialization + TDUnitX.RegisterTestFixture(TServiceRoutinesTests); + +end. diff --git a/tests/Tests.OEM.Session.pas b/tests/Tests.OEM.Session.pas index c148410a..fb1efe6a 100644 --- a/tests/Tests.OEM.Session.pas +++ b/tests/Tests.OEM.Session.pas @@ -13,28 +13,82 @@ interface [TestFixture] TStandardSessionTests = class public + /// + /// Extended session emits header then10 03. + /// [Test] procedure ExtendedSessionEmitsHeaderThen10_03; + /// + /// Default session has no heartbeat. + /// [Test] procedure DefaultSessionHasNoHeartbeat; + /// + /// Non default session uses iso14229 heartbeat. + /// [Test] procedure NonDefaultSessionUsesIso14229Heartbeat; + /// + /// End session returns to10 01. + /// [Test] procedure EndSessionReturnsTo10_01; + /// + /// Programming requires security access. + /// [Test] procedure ProgrammingRequiresSecurityAccess; + /// + /// Extended does not require security access by default. + /// [Test] procedure ExtendedDoesNotRequireSecurityAccessByDefault; + /// + /// Zero ecu address omits header. + /// [Test] procedure ZeroEcuAddressOmitsHeader; end; [TestFixture] TPerOEMSessionTests = class public + /// + /// V w plan sets header and c r a. + /// [Test] procedure VWPlanSetsHeaderAndCRA; + /// + /// B m w requires security access for extended. + /// [Test] procedure BMWRequiresSecurityAccessForExtended; + /// + /// B m w heartbeat is1500ms. + /// [Test] procedure BMWHeartbeatIs1500ms; + /// + /// Mercedes appends f198 probe. + /// [Test] procedure MercedesAppendsF198Probe; + /// + /// Mercedes heartbeat is1500ms. + /// [Test] procedure MercedesHeartbeatIs1500ms; + /// + /// Ford prepends s t32 for programming. + /// [Test] procedure FordPrependsST32ForProgramming; + /// + /// Ford extended has no s t32. + /// [Test] procedure FordExtendedHasNoST32; + /// + /// G m prepends s p6. + /// [Test] procedure GMPrependsSP6; + /// + /// Stellantis appends f198 with empty expected. + /// [Test] procedure StellantisAppendsF198WithEmptyExpected; + /// + /// Extension resolves to o e m negotiator. + /// [Test] procedure ExtensionResolvesToOEMNegotiator; + /// + /// Session negotiator is cached across calls. + /// [Test] procedure SessionNegotiatorIsCachedAcrossCalls; end; @@ -46,6 +100,9 @@ implementation OBD.OEM.VW, OBD.OEM.BMW, OBD.OEM.Mercedes, OBD.OEM.Ford, OBD.OEM.GM, OBD.OEM.Stellantis; +//------------------------------------------------------------------------------ +// FIND UDSSTEP +//------------------------------------------------------------------------------ function FindUDSStep(const Plan: TOBDSessionPlan; const Prefix: TBytes): Boolean; var @@ -65,6 +122,9 @@ function FindUDSStep(const Plan: TOBDSessionPlan; Result := False; end; +//------------------------------------------------------------------------------ +// FIND ATSTEP +//------------------------------------------------------------------------------ function FindATStep(const Plan: TOBDSessionPlan; const Cmd: string): Integer; var @@ -80,6 +140,10 @@ function FindATStep(const Plan: TOBDSessionPlan; //============================================================================== // TStandardSessionTests //============================================================================== + +//------------------------------------------------------------------------------ +// EXTENDED SESSION EMITS HEADER THEN10_03 +//------------------------------------------------------------------------------ procedure TStandardSessionTests.ExtendedSessionEmitsHeaderThen10_03; var N: IOBDSessionNegotiator; @@ -96,6 +160,9 @@ procedure TStandardSessionTests.ExtendedSessionEmitsHeaderThen10_03; Assert.AreEqual(Byte($50), Plan.Steps[1].ExpectedResponse[0]); end; +//------------------------------------------------------------------------------ +// DEFAULT SESSION HAS NO HEARTBEAT +//------------------------------------------------------------------------------ procedure TStandardSessionTests.DefaultSessionHasNoHeartbeat; var N: IOBDSessionNegotiator; @@ -106,6 +173,9 @@ procedure TStandardSessionTests.DefaultSessionHasNoHeartbeat; Assert.AreEqual(Cardinal(0), Plan.TesterPresentMs); end; +//------------------------------------------------------------------------------ +// NON DEFAULT SESSION USES ISO14229 HEARTBEAT +//------------------------------------------------------------------------------ procedure TStandardSessionTests.NonDefaultSessionUsesIso14229Heartbeat; var N: IOBDSessionNegotiator; @@ -119,6 +189,9 @@ procedure TStandardSessionTests.NonDefaultSessionUsesIso14229Heartbeat; Assert.AreEqual(Byte($80), Plan.TesterPresentRequest[1]); end; +//------------------------------------------------------------------------------ +// END SESSION RETURNS TO10_01 +//------------------------------------------------------------------------------ procedure TStandardSessionTests.EndSessionReturnsTo10_01; var N: IOBDSessionNegotiator; @@ -131,6 +204,9 @@ procedure TStandardSessionTests.EndSessionReturnsTo10_01; Assert.AreEqual(Cardinal(0), Plan.TesterPresentMs); end; +//------------------------------------------------------------------------------ +// PROGRAMMING REQUIRES SECURITY ACCESS +//------------------------------------------------------------------------------ procedure TStandardSessionTests.ProgrammingRequiresSecurityAccess; var N: IOBDSessionNegotiator; @@ -139,6 +215,9 @@ procedure TStandardSessionTests.ProgrammingRequiresSecurityAccess; Assert.IsTrue(N.RequiresSecurityAccess(sstProgramming)); end; +//------------------------------------------------------------------------------ +// EXTENDED DOES NOT REQUIRE SECURITY ACCESS BY DEFAULT +//------------------------------------------------------------------------------ procedure TStandardSessionTests.ExtendedDoesNotRequireSecurityAccessByDefault; var N: IOBDSessionNegotiator; @@ -147,6 +226,9 @@ procedure TStandardSessionTests.ExtendedDoesNotRequireSecurityAccessByDefault; Assert.IsFalse(N.RequiresSecurityAccess(sstExtendedDiagnostic)); end; +//------------------------------------------------------------------------------ +// ZERO ECU ADDRESS OMITS HEADER +//------------------------------------------------------------------------------ procedure TStandardSessionTests.ZeroEcuAddressOmitsHeader; var N: IOBDSessionNegotiator; @@ -161,6 +243,10 @@ procedure TStandardSessionTests.ZeroEcuAddressOmitsHeader; //============================================================================== // TPerOEMSessionTests //============================================================================== + +//------------------------------------------------------------------------------ +// VWPLAN SETS HEADER AND CRA +//------------------------------------------------------------------------------ procedure TPerOEMSessionTests.VWPlanSetsHeaderAndCRA; var N: IOBDSessionNegotiator; @@ -173,6 +259,9 @@ procedure TPerOEMSessionTests.VWPlanSetsHeaderAndCRA; Assert.IsTrue(FindUDSStep(Plan, TBytes.Create($10, $03))); end; +//------------------------------------------------------------------------------ +// BMWREQUIRES SECURITY ACCESS FOR EXTENDED +//------------------------------------------------------------------------------ procedure TPerOEMSessionTests.BMWRequiresSecurityAccessForExtended; var N: IOBDSessionNegotiator; @@ -182,6 +271,9 @@ procedure TPerOEMSessionTests.BMWRequiresSecurityAccessForExtended; Assert.IsTrue(N.RequiresSecurityAccess(sstProgramming)); end; +//------------------------------------------------------------------------------ +// BMWHEARTBEAT IS1500MS +//------------------------------------------------------------------------------ procedure TPerOEMSessionTests.BMWHeartbeatIs1500ms; var N: IOBDSessionNegotiator; @@ -190,6 +282,9 @@ procedure TPerOEMSessionTests.BMWHeartbeatIs1500ms; Assert.AreEqual(Cardinal(1500), N.DefaultTesterPresentMs); end; +//------------------------------------------------------------------------------ +// MERCEDES APPENDS F198 PROBE +//------------------------------------------------------------------------------ procedure TPerOEMSessionTests.MercedesAppendsF198Probe; var N: IOBDSessionNegotiator; @@ -201,6 +296,9 @@ procedure TPerOEMSessionTests.MercedesAppendsF198Probe; 'Mercedes plan must include a 22 F1 98 probe'); end; +//------------------------------------------------------------------------------ +// MERCEDES HEARTBEAT IS1500MS +//------------------------------------------------------------------------------ procedure TPerOEMSessionTests.MercedesHeartbeatIs1500ms; var N: IOBDSessionNegotiator; @@ -209,6 +307,9 @@ procedure TPerOEMSessionTests.MercedesHeartbeatIs1500ms; Assert.AreEqual(Cardinal(1500), N.DefaultTesterPresentMs); end; +//------------------------------------------------------------------------------ +// FORD PREPENDS ST32 FOR PROGRAMMING +//------------------------------------------------------------------------------ procedure TPerOEMSessionTests.FordPrependsST32ForProgramming; var N: IOBDSessionNegotiator; @@ -220,6 +321,9 @@ procedure TPerOEMSessionTests.FordPrependsST32ForProgramming; 'Ford programming plan must prepend ST 32'); end; +//------------------------------------------------------------------------------ +// FORD EXTENDED HAS NO ST32 +//------------------------------------------------------------------------------ procedure TPerOEMSessionTests.FordExtendedHasNoST32; var N: IOBDSessionNegotiator; @@ -231,6 +335,9 @@ procedure TPerOEMSessionTests.FordExtendedHasNoST32; 'Ford extended-diagnostic should NOT carry the programming-only ST 32'); end; +//------------------------------------------------------------------------------ +// GMPREPENDS SP6 +//------------------------------------------------------------------------------ procedure TPerOEMSessionTests.GMPrependsSP6; var N: IOBDSessionNegotiator; @@ -242,6 +349,9 @@ procedure TPerOEMSessionTests.GMPrependsSP6; 'GM plan must lock to GMLAN protocol via SP 6'); end; +//------------------------------------------------------------------------------ +// STELLANTIS APPENDS F198 WITH EMPTY EXPECTED +//------------------------------------------------------------------------------ procedure TPerOEMSessionTests.StellantisAppendsF198WithEmptyExpected; var N: IOBDSessionNegotiator; @@ -263,6 +373,9 @@ procedure TPerOEMSessionTests.StellantisAppendsF198WithEmptyExpected; Assert.IsTrue(Found, 'Stellantis plan must include the F198 probe'); end; +//------------------------------------------------------------------------------ +// EXTENSION RESOLVES TO OEMNEGOTIATOR +//------------------------------------------------------------------------------ procedure TPerOEMSessionTests.ExtensionResolvesToOEMNegotiator; var Ext: IOBDOEMExtension; @@ -287,6 +400,9 @@ procedure TPerOEMSessionTests.ExtensionResolvesToOEMNegotiator; Assert.IsTrue(Pos('Stellantis', Ext.SessionNegotiator.DisplayName) > 0); end; +//------------------------------------------------------------------------------ +// SESSION NEGOTIATOR IS CACHED ACROSS CALLS +//------------------------------------------------------------------------------ procedure TPerOEMSessionTests.SessionNegotiatorIsCachedAcrossCalls; var Ext: IOBDOEMExtension; diff --git a/tests/Tests.OEM.SessionHelper.pas b/tests/Tests.OEM.SessionHelper.pas new file mode 100644 index 00000000..f27814c3 --- /dev/null +++ b/tests/Tests.OEM.SessionHelper.pas @@ -0,0 +1,422 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.OEM.SessionHelper.pas +// CONTENTS : Tests for OBD.OEM.SessionHelper +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.OEM.SessionHelper; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TOEMSessionHelperTests = class + public + /// + /// Success path all callbacks invoked. + /// + [Test] procedure SuccessPath_AllCallbacksInvoked; + /// + /// Session open failure aborts before routine. + /// + [Test] procedure SessionOpenFailure_AbortsBeforeRoutine; + /// + /// Routine start n r c propagates into error message. + /// + [Test] procedure RoutineStartNRC_PropagatesIntoErrorMessage; + /// + /// Result read n r c propagates into error message. + /// + [Test] procedure ResultReadNRC_PropagatesIntoErrorMessage; + /// + /// Voltage gate failure fails before routine. + /// + [Test] procedure VoltageGateFailure_FailsBeforeRoutine; + /// + /// Voltage gate not consulted for non battery routine. + /// + [Test] procedure VoltageGate_NotConsultedForNonBatteryRoutine; + /// + /// Voltage gate required but reader missing fails. + /// + [Test] procedure VoltageGate_RequiredButReaderMissing_Fails; + /// + /// Session always closed on failure. + /// + [Test] procedure SessionAlwaysClosedOnFailure; + /// + /// Callback contract violations raise. + /// + [Test] procedure CallbackContractViolations_Raise; + end; + +implementation + +uses + System.SysUtils, + OBD.OEM.ServiceRoutines, + OBD.OEM.SessionHelper; + +//------------------------------------------------------------------------------ +// MAKE ROUTINE +//------------------------------------------------------------------------------ +function MakeRoutine(SafetyClass: TOBDServiceRoutineSafety; + RID: Word = $0301): TOBDServiceRoutine; +begin + Result := Default(TOBDServiceRoutine); + Result.Key := 'test_routine'; + Result.DisplayName := 'Test Routine'; + Result.Category := srcMaintenance; + Result.Applicability := 'all'; + Result.RoutineIdentifier := RID; + Result.SubFunction := $01; + Result.RequiredSessionType := $03; + Result.Safety := SafetyClass; + Result.Citation := 'test only'; +end; + +//------------------------------------------------------------------------------ +// SUCCESS PATH_ALL CALLBACKS INVOKED +//------------------------------------------------------------------------------ +procedure TOEMSessionHelperTests.SuccessPath_AllCallbacksInvoked; +var + Helper: TOBDOEMSessionHelper; + Cbs: TOBDOEMSessionCallbacks; + R: TOBDRoutineExecutionResult; + OpenCalled, StartCalled, ResultCalled, CloseCalled: Boolean; +begin + OpenCalled := False; StartCalled := False; + ResultCalled := False; CloseCalled := False; + Cbs.OpenSession := + function(SessionType: Byte; out NRC: Byte): Boolean + begin + OpenCalled := True; + NRC := 0; + Result := True; + end; + Cbs.StartRoutine := + function(const Frame: TBytes; out NRC: Byte): Boolean + begin + StartCalled := True; + NRC := 0; + Result := True; + end; + Cbs.ReadResult := + function(RID: Word; out ResultBytes: TBytes; out NRC: Byte): Boolean + begin + ResultCalled := True; NRC := 0; + ResultBytes := TBytes.Create($AA, $BB); + Result := True; + end; + Cbs.CloseSession := + function: Boolean begin CloseCalled := True; Result := True; end; + + Helper := TOBDOEMSessionHelper.Create; + try + R := Helper.RunServiceRoutine(MakeRoutine(srsNone), Cbs); + Assert.IsTrue(R.Success, R.ErrorMessage); + Assert.IsTrue(OpenCalled); + Assert.IsTrue(StartCalled); + Assert.IsTrue(ResultCalled); + Assert.IsTrue(CloseCalled); + Assert.AreEqual(2, Length(R.ResultBytes)); + Assert.AreEqual(Integer($AA), Integer(R.ResultBytes[0])); + finally + Helper.Free; + end; +end; + +//------------------------------------------------------------------------------ +// SESSION OPEN FAILURE_ABORTS BEFORE ROUTINE +//------------------------------------------------------------------------------ +procedure TOEMSessionHelperTests.SessionOpenFailure_AbortsBeforeRoutine; +var + Helper: TOBDOEMSessionHelper; + Cbs: TOBDOEMSessionCallbacks; + R: TOBDRoutineExecutionResult; + StartCalled, CloseCalled: Boolean; +begin + StartCalled := False; CloseCalled := False; + Cbs.OpenSession := + function(SessionType: Byte; out NRC: Byte): Boolean + begin NRC := $22; Result := False; end; // conditionsNotCorrect + Cbs.StartRoutine := + function(const Frame: TBytes; out NRC: Byte): Boolean + begin + StartCalled := True; + NRC := 0; + Result := True; + end; + Cbs.CloseSession := + function: Boolean begin CloseCalled := True; Result := True; end; + + Helper := TOBDOEMSessionHelper.Create; + try + R := Helper.RunServiceRoutine(MakeRoutine(srsNone), Cbs); + Assert.IsFalse(R.Success); + Assert.IsFalse(StartCalled, 'StartRoutine must not run after open fails'); + Assert.IsFalse(CloseCalled, 'CloseSession must not run if open failed'); + Assert.AreEqual(Ord(reseSessionOpen), Ord(R.AbortStage)); + Assert.IsTrue(R.ErrorMessage.Contains('CNC'), + 'NRC short-name should be embedded: ' + R.ErrorMessage); + finally + Helper.Free; + end; +end; + +//------------------------------------------------------------------------------ +// ROUTINE START NRC_PROPAGATES INTO ERROR MESSAGE +//------------------------------------------------------------------------------ +procedure TOEMSessionHelperTests.RoutineStartNRC_PropagatesIntoErrorMessage; +var + Helper: TOBDOEMSessionHelper; + Cbs: TOBDOEMSessionCallbacks; + R: TOBDRoutineExecutionResult; +begin + Cbs.OpenSession := + function(SessionType: Byte; out NRC: Byte): Boolean + begin + NRC := 0; + Result := True; + end; + Cbs.StartRoutine := + function(const Frame: TBytes; out NRC: Byte): Boolean + begin NRC := $33; Result := False; end; // securityAccessDenied + Cbs.CloseSession := function: Boolean begin Result := True; end; + + Helper := TOBDOEMSessionHelper.Create; + try + R := Helper.RunServiceRoutine(MakeRoutine(srsNone), Cbs); + Assert.IsFalse(R.Success); + Assert.AreEqual(Ord(reseRoutineStart), Ord(R.AbortStage)); + Assert.AreEqual(Integer($33), Integer(R.NRC)); + Assert.IsTrue(R.ErrorMessage.Contains('SAD')); + finally + Helper.Free; + end; +end; + +//------------------------------------------------------------------------------ +// RESULT READ NRC_PROPAGATES INTO ERROR MESSAGE +//------------------------------------------------------------------------------ +procedure TOEMSessionHelperTests.ResultReadNRC_PropagatesIntoErrorMessage; +var + Helper: TOBDOEMSessionHelper; + Cbs: TOBDOEMSessionCallbacks; + R: TOBDRoutineExecutionResult; +begin + Cbs.OpenSession := + function(SessionType: Byte; out NRC: Byte): Boolean + begin + NRC := 0; + Result := True; + end; + Cbs.StartRoutine := + function(const Frame: TBytes; out NRC: Byte): Boolean + begin + NRC := 0; + Result := True; + end; + Cbs.ReadResult := + function(RID: Word; out ResultBytes: TBytes; out NRC: Byte): Boolean + begin NRC := $31; Result := False; end; // requestOutOfRange + Cbs.CloseSession := function: Boolean begin Result := True; end; + + Helper := TOBDOEMSessionHelper.Create; + try + R := Helper.RunServiceRoutine(MakeRoutine(srsNone), Cbs); + Assert.IsFalse(R.Success); + Assert.AreEqual(Ord(reseResultRead), Ord(R.AbortStage)); + Assert.AreEqual(Integer($31), Integer(R.NRC)); + Assert.IsTrue(R.ErrorMessage.Contains('ROOR')); + finally + Helper.Free; + end; +end; + +//------------------------------------------------------------------------------ +// VOLTAGE GATE FAILURE_FAILS BEFORE ROUTINE +//------------------------------------------------------------------------------ +procedure TOEMSessionHelperTests.VoltageGateFailure_FailsBeforeRoutine; +var + Helper: TOBDOEMSessionHelper; + Cbs: TOBDOEMSessionCallbacks; + R: TOBDRoutineExecutionResult; + StartCalled, CloseCalled: Boolean; +begin + StartCalled := False; CloseCalled := False; + Cbs.OpenSession := + function(SessionType: Byte; out NRC: Byte): Boolean + begin + NRC := 0; + Result := True; + end; + Cbs.StartRoutine := + function(const Frame: TBytes; out NRC: Byte): Boolean + begin + StartCalled := True; + NRC := 0; + Result := True; + end; + Cbs.CloseSession := + function: Boolean begin CloseCalled := True; Result := True; end; + Cbs.ReadVoltage := function: Single begin Result := 11.0; end; + + Helper := TOBDOEMSessionHelper.Create; + try + R := Helper.RunServiceRoutine(MakeRoutine(srsBatteryMin12V5), Cbs); + Assert.IsFalse(R.Success); + Assert.AreEqual(Ord(reseVoltageGate), Ord(R.AbortStage)); + Assert.IsFalse(StartCalled); + Assert.IsTrue(CloseCalled, 'session must close even on voltage failure'); + Assert.AreEqual(Single(11.0), R.MeasuredVolts, 0.001); + finally + Helper.Free; + end; +end; + +//------------------------------------------------------------------------------ +// VOLTAGE GATE_NOT CONSULTED FOR NON BATTERY ROUTINE +//------------------------------------------------------------------------------ +procedure TOEMSessionHelperTests.VoltageGate_NotConsultedForNonBatteryRoutine; +var + Helper: TOBDOEMSessionHelper; + Cbs: TOBDOEMSessionCallbacks; + R: TOBDRoutineExecutionResult; + ReaderCalled: Boolean; +begin + ReaderCalled := False; + Cbs.OpenSession := + function(SessionType: Byte; out NRC: Byte): Boolean + begin + NRC := 0; + Result := True; + end; + Cbs.StartRoutine := + function(const Frame: TBytes; out NRC: Byte): Boolean + begin + NRC := 0; + Result := True; + end; + Cbs.CloseSession := function: Boolean begin Result := True; end; + Cbs.ReadVoltage := + function: Single + begin + ReaderCalled := True; + Result := 11.0; + end; + + Helper := TOBDOEMSessionHelper.Create; + try + R := Helper.RunServiceRoutine(MakeRoutine(srsNone), Cbs); + Assert.IsTrue(R.Success); + Assert.IsFalse(ReaderCalled, + 'voltage reader must not be called for non-battery routines'); + finally + Helper.Free; + end; +end; + +//------------------------------------------------------------------------------ +// VOLTAGE GATE_REQUIRED BUT READER MISSING_FAILS +//------------------------------------------------------------------------------ +procedure TOEMSessionHelperTests.VoltageGate_RequiredButReaderMissing_Fails; +var + Helper: TOBDOEMSessionHelper; + Cbs: TOBDOEMSessionCallbacks; + R: TOBDRoutineExecutionResult; +begin + Cbs.OpenSession := + function(SessionType: Byte; out NRC: Byte): Boolean + begin + NRC := 0; + Result := True; + end; + Cbs.StartRoutine := + function(const Frame: TBytes; out NRC: Byte): Boolean + begin + NRC := 0; + Result := True; + end; + Cbs.CloseSession := function: Boolean begin Result := True; end; + // ReadVoltage left nil + + Helper := TOBDOEMSessionHelper.Create; + try + R := Helper.RunServiceRoutine(MakeRoutine(srsBatteryMin12V5), Cbs); + Assert.IsFalse(R.Success); + Assert.AreEqual(Ord(reseVoltageGate), Ord(R.AbortStage)); + Assert.IsTrue(R.ErrorMessage.Contains('no ReadVoltage')); + finally + Helper.Free; + end; +end; + +//------------------------------------------------------------------------------ +// SESSION ALWAYS CLOSED ON FAILURE +//------------------------------------------------------------------------------ +procedure TOEMSessionHelperTests.SessionAlwaysClosedOnFailure; +var + Helper: TOBDOEMSessionHelper; + Cbs: TOBDOEMSessionCallbacks; + R: TOBDRoutineExecutionResult; + CloseCalled: Boolean; +begin + CloseCalled := False; + Cbs.OpenSession := + function(SessionType: Byte; out NRC: Byte): Boolean + begin + NRC := 0; + Result := True; + end; + Cbs.StartRoutine := + function(const Frame: TBytes; out NRC: Byte): Boolean + begin + NRC := $22; + Result := False; + end; + Cbs.CloseSession := + function: Boolean begin CloseCalled := True; Result := True; end; + + Helper := TOBDOEMSessionHelper.Create; + try + R := Helper.RunServiceRoutine(MakeRoutine(srsNone), Cbs); + Assert.IsFalse(R.Success); + Assert.IsTrue(CloseCalled, + 'CloseSession must run even when StartRoutine returned NRC'); + finally + Helper.Free; + end; +end; + +//------------------------------------------------------------------------------ +// CALLBACK CONTRACT VIOLATIONS_RAISE +//------------------------------------------------------------------------------ +procedure TOEMSessionHelperTests.CallbackContractViolations_Raise; +var + Helper: TOBDOEMSessionHelper; + Cbs: TOBDOEMSessionCallbacks; +begin + Cbs := Default(TOBDOEMSessionCallbacks); + Helper := TOBDOEMSessionHelper.Create; + try + Assert.WillRaise( + procedure begin Helper.RunServiceRoutine(MakeRoutine(srsNone), Cbs); end, + EOBDOEMSessionHelper); + finally + Helper.Free; + end; +end; + +initialization + TDUnitX.RegisterTestFixture(TOEMSessionHelperTests); + +end. diff --git a/tests/Tests.OEM.SupplierRouting.pas b/tests/Tests.OEM.SupplierRouting.pas index 347ad0e5..7996179e 100644 --- a/tests/Tests.OEM.SupplierRouting.pas +++ b/tests/Tests.OEM.SupplierRouting.pas @@ -16,15 +16,45 @@ interface [TestFixture] TSupplierRoutingTests = class public + /// + /// Cummins claims cummins and cmi. + /// [Test] procedure CumminsClaimsCumminsAndCmi; + /// + /// Cummins rejects other suppliers. + /// [Test] procedure CumminsRejectsOtherSuppliers; + /// + /// Detroit claims detroit ddc detroit ddc. + /// [Test] procedure DetroitClaimsDetroitDdcDetroitDdc; + /// + /// Detroit rejects other suppliers. + /// [Test] procedure DetroitRejectsOtherSuppliers; + /// + /// Registry routes by cummins id. + /// [Test] procedure RegistryRoutesByCumminsId; + /// + /// Registry routes by detroit id. + /// [Test] procedure RegistryRoutesByDetroitId; + /// + /// Registry returns nil for unknown supplier. + /// [Test] procedure RegistryReturnsNilForUnknownSupplier; + /// + /// Registry handles empty string. + /// [Test] procedure RegistryHandlesEmptyString; + /// + /// Non engine o e ms return false by default. + /// [Test] procedure NonEngineOEMsReturnFalseByDefault; + /// + /// Supplier match is case insensitive. + /// [Test] procedure SupplierMatchIsCaseInsensitive; end; @@ -35,6 +65,9 @@ implementation OBD.OEM, OBD.OEM.Cummins, OBD.OEM.DetroitDiesel, OBD.OEM.VW, OBD.OEM.Toyota; +//------------------------------------------------------------------------------ +// CUMMINS CLAIMS CUMMINS AND CMI +//------------------------------------------------------------------------------ procedure TSupplierRoutingTests.CumminsClaimsCumminsAndCmi; var Ext: IOBDOEMExtension; @@ -44,6 +77,9 @@ procedure TSupplierRoutingTests.CumminsClaimsCumminsAndCmi; Assert.IsTrue(Ext.ApplicableToECUSupplier('CMI')); end; +//------------------------------------------------------------------------------ +// CUMMINS REJECTS OTHER SUPPLIERS +//------------------------------------------------------------------------------ procedure TSupplierRoutingTests.CumminsRejectsOtherSuppliers; var Ext: IOBDOEMExtension; @@ -54,6 +90,9 @@ procedure TSupplierRoutingTests.CumminsRejectsOtherSuppliers; Assert.IsFalse(Ext.ApplicableToECUSupplier('')); end; +//------------------------------------------------------------------------------ +// DETROIT CLAIMS DETROIT DDC DETROIT DDC +//------------------------------------------------------------------------------ procedure TSupplierRoutingTests.DetroitClaimsDetroitDdcDetroitDdc; var Ext: IOBDOEMExtension; @@ -64,6 +103,9 @@ procedure TSupplierRoutingTests.DetroitClaimsDetroitDdcDetroitDdc; Assert.IsTrue(Ext.ApplicableToECUSupplier('DETROITDDC')); end; +//------------------------------------------------------------------------------ +// DETROIT REJECTS OTHER SUPPLIERS +//------------------------------------------------------------------------------ procedure TSupplierRoutingTests.DetroitRejectsOtherSuppliers; var Ext: IOBDOEMExtension; @@ -73,6 +115,9 @@ procedure TSupplierRoutingTests.DetroitRejectsOtherSuppliers; Assert.IsFalse(Ext.ApplicableToECUSupplier('PACCAR')); end; +//------------------------------------------------------------------------------ +// REGISTRY ROUTES BY CUMMINS ID +//------------------------------------------------------------------------------ procedure TSupplierRoutingTests.RegistryRoutesByCumminsId; var Ext: IOBDOEMExtension; @@ -82,6 +127,9 @@ procedure TSupplierRoutingTests.RegistryRoutesByCumminsId; Assert.AreEqual('CUMMINS', Ext.ManufacturerKey); end; +//------------------------------------------------------------------------------ +// REGISTRY ROUTES BY DETROIT ID +//------------------------------------------------------------------------------ procedure TSupplierRoutingTests.RegistryRoutesByDetroitId; var Ext: IOBDOEMExtension; @@ -91,6 +139,9 @@ procedure TSupplierRoutingTests.RegistryRoutesByDetroitId; Assert.AreEqual('DDC', Ext.ManufacturerKey); end; +//------------------------------------------------------------------------------ +// REGISTRY RETURNS NIL FOR UNKNOWN SUPPLIER +//------------------------------------------------------------------------------ procedure TSupplierRoutingTests.RegistryReturnsNilForUnknownSupplier; var Ext: IOBDOEMExtension; @@ -99,6 +150,9 @@ procedure TSupplierRoutingTests.RegistryReturnsNilForUnknownSupplier; Assert.IsNull(Ext); end; +//------------------------------------------------------------------------------ +// REGISTRY HANDLES EMPTY STRING +//------------------------------------------------------------------------------ procedure TSupplierRoutingTests.RegistryHandlesEmptyString; var Ext: IOBDOEMExtension; @@ -110,6 +164,9 @@ procedure TSupplierRoutingTests.RegistryHandlesEmptyString; Assert.IsNull(Ext); end; +//------------------------------------------------------------------------------ +// NON ENGINE OEMS RETURN FALSE BY DEFAULT +//------------------------------------------------------------------------------ procedure TSupplierRoutingTests.NonEngineOEMsReturnFalseByDefault; var VW, Toyota: IOBDOEMExtension; @@ -123,6 +180,9 @@ procedure TSupplierRoutingTests.NonEngineOEMsReturnFalseByDefault; Assert.IsFalse(Toyota.ApplicableToECUSupplier('TOYOTA')); end; +//------------------------------------------------------------------------------ +// SUPPLIER MATCH IS CASE INSENSITIVE +//------------------------------------------------------------------------------ procedure TSupplierRoutingTests.SupplierMatchIsCaseInsensitive; var Ext: IOBDOEMExtension; diff --git a/tests/Tests.OEM.UdsClient.Async.pas b/tests/Tests.OEM.UdsClient.Async.pas index 85cf6060..451b5f7d 100644 --- a/tests/Tests.OEM.UdsClient.Async.pas +++ b/tests/Tests.OEM.UdsClient.Async.pas @@ -17,12 +17,33 @@ interface [TestFixture] TUdsClientAsyncTests = class public + /// + /// Read d i d async await returns decoded value. + /// [Test] procedure ReadDIDAsync_AwaitReturnsDecodedValue; + /// + /// Read d i d async on complete fires. + /// [Test] procedure ReadDIDAsync_OnCompleteFires; + /// + /// Read d i d async pre cancelled token settles cancelled. + /// [Test] procedure ReadDIDAsync_PreCancelledTokenSettlesCancelled; + /// + /// Close session drains pending futures as cancelled. + /// [Test] procedure CloseSession_DrainsPendingFuturesAsCancelled; + /// + /// Read d i d async propagates exception through await. + /// [Test] procedure ReadDIDAsync_PropagatesExceptionThroughAwait; + /// + /// Write adaptation async await returns true. + /// [Test] procedure WriteAdaptationAsync_AwaitReturnsTrue; + /// + /// Serial ordering two calls complete in queue order. + /// [Test] procedure SerialOrdering_TwoCallsCompleteInQueueOrder; end; @@ -61,6 +82,9 @@ TStallMock = class(TInterfacedObject, IOBDDiagnosticTransport) function TargetECU: Word; end; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TStallMock.Create; begin inherited; @@ -70,6 +94,9 @@ constructor TStallMock.Create; FLock := TCriticalSection.Create; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TStallMock.Destroy; begin FGate.Free; @@ -79,35 +106,53 @@ destructor TStallMock.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// ENQUEUE RESPONSE +//------------------------------------------------------------------------------ procedure TStallMock.EnqueueResponse(const Bytes: TBytes); begin FLock.Enter; try FQueue.Add(Bytes); finally FLock.Leave; end; end; +//------------------------------------------------------------------------------ +// STALL NEXT REQUEST +//------------------------------------------------------------------------------ procedure TStallMock.StallNextRequest; begin FStall := True; FGate.ResetEvent; end; +//------------------------------------------------------------------------------ +// RELEASE STALL +//------------------------------------------------------------------------------ procedure TStallMock.ReleaseStall; begin FStall := False; FGate.SetEvent; end; +//------------------------------------------------------------------------------ +// FAIL NEXT REQUEST +//------------------------------------------------------------------------------ procedure TStallMock.FailNextRequest; begin FFailNext := True; end; +//------------------------------------------------------------------------------ +// REQUEST COUNT +//------------------------------------------------------------------------------ function TStallMock.RequestCount: Integer; begin FLock.Enter; try Result := FRequests.Count; finally FLock.Leave; end; end; +//------------------------------------------------------------------------------ +// SEND RECEIVE +//------------------------------------------------------------------------------ function TStallMock.SendReceive(const Request: TBytes; TimeoutMs: Cardinal): TBytes; var @@ -136,11 +181,21 @@ function TStallMock.SendReceive(const Request: TBytes; end; end; +//------------------------------------------------------------------------------ +// SET TARGET ECU +//------------------------------------------------------------------------------ procedure TStallMock.SetTargetECU(Address: Word); -begin FECU := Address; end; +begin + FECU := Address; +end; +//------------------------------------------------------------------------------ +// TARGET ECU +//------------------------------------------------------------------------------ function TStallMock.TargetECU: Word; -begin Result := FECU; end; +begin + Result := FECU; +end; //============================================================================== // Catalog fixture @@ -161,11 +216,17 @@ function TStallMock.TargetECU: Word; ' "kind": "uint16_be", "min": 600, "max": 1500, "default": 800, "unit": "rpm"}' + ' ]}'; +//------------------------------------------------------------------------------ +// MAKE ASYNC CATALOG +//------------------------------------------------------------------------------ function MakeAsyncCatalog: TOBDOEMJSONCatalog; begin Result := TOBDOEMJSONCatalog.CreateFromText(ASYNC_CATALOG); end; +//------------------------------------------------------------------------------ +// NEW STALL MOCK +//------------------------------------------------------------------------------ procedure NewStallMock(out Mock: TStallMock; out ITransport: IOBDDiagnosticTransport); begin @@ -176,6 +237,10 @@ procedure NewStallMock(out Mock: TStallMock; //============================================================================== // Tests //============================================================================== + +//------------------------------------------------------------------------------ +// READ DIDASYNC_AWAIT RETURNS DECODED VALUE +//------------------------------------------------------------------------------ procedure TUdsClientAsyncTests.ReadDIDAsync_AwaitReturnsDecodedValue; var Cat: TOBDOEMJSONCatalog; @@ -207,6 +272,9 @@ procedure TUdsClientAsyncTests.ReadDIDAsync_AwaitReturnsDecodedValue; end; end; +//------------------------------------------------------------------------------ +// READ DIDASYNC_ON COMPLETE FIRES +//------------------------------------------------------------------------------ procedure TUdsClientAsyncTests.ReadDIDAsync_OnCompleteFires; var Cat: TOBDOEMJSONCatalog; @@ -241,6 +309,9 @@ procedure TUdsClientAsyncTests.ReadDIDAsync_OnCompleteFires; end; end; +//------------------------------------------------------------------------------ +// READ DIDASYNC_PRE CANCELLED TOKEN SETTLES CANCELLED +//------------------------------------------------------------------------------ procedure TUdsClientAsyncTests.ReadDIDAsync_PreCancelledTokenSettlesCancelled; var Cat: TOBDOEMJSONCatalog; @@ -285,6 +356,9 @@ procedure TUdsClientAsyncTests.ReadDIDAsync_PreCancelledTokenSettlesCancelled; end; end; +//------------------------------------------------------------------------------ +// CLOSE SESSION_DRAINS PENDING FUTURES AS CANCELLED +//------------------------------------------------------------------------------ procedure TUdsClientAsyncTests.CloseSession_DrainsPendingFuturesAsCancelled; var Cat: TOBDOEMJSONCatalog; @@ -321,6 +395,9 @@ procedure TUdsClientAsyncTests.CloseSession_DrainsPendingFuturesAsCancelled; end; end; +//------------------------------------------------------------------------------ +// READ DIDASYNC_PROPAGATES EXCEPTION THROUGH AWAIT +//------------------------------------------------------------------------------ procedure TUdsClientAsyncTests.ReadDIDAsync_PropagatesExceptionThroughAwait; var Cat: TOBDOEMJSONCatalog; @@ -349,6 +426,9 @@ procedure TUdsClientAsyncTests.ReadDIDAsync_PropagatesExceptionThroughAwait; end; end; +//------------------------------------------------------------------------------ +// WRITE ADAPTATION ASYNC_AWAIT RETURNS TRUE +//------------------------------------------------------------------------------ procedure TUdsClientAsyncTests.WriteAdaptationAsync_AwaitReturnsTrue; var Cat: TOBDOEMJSONCatalog; @@ -375,6 +455,9 @@ procedure TUdsClientAsyncTests.WriteAdaptationAsync_AwaitReturnsTrue; end; end; +//------------------------------------------------------------------------------ +// SERIAL ORDERING_TWO CALLS COMPLETE IN QUEUE ORDER +//------------------------------------------------------------------------------ procedure TUdsClientAsyncTests.SerialOrdering_TwoCallsCompleteInQueueOrder; var Cat: TOBDOEMJSONCatalog; diff --git a/tests/Tests.OEM.UdsClient.Replay.pas b/tests/Tests.OEM.UdsClient.Replay.pas index 606852a8..9ec832b1 100644 --- a/tests/Tests.OEM.UdsClient.Replay.pas +++ b/tests/Tests.OEM.UdsClient.Replay.pas @@ -21,8 +21,17 @@ interface [TestFixture] TUdsClientReplayTests = class public + /// + /// V w v i n decodes from captured f190. + /// [Test] procedure VW_VIN_DecodesFromCapturedF190; + /// + /// V w hardware number decodes from captured f187. + /// [Test] procedure VW_HardwareNumber_DecodesFromCapturedF187; + /// + /// V w unknown d i d raises catalog miss. + /// [Test] procedure VW_UnknownDID_RaisesCatalogMiss; end; @@ -38,6 +47,9 @@ implementation // Helpers //============================================================================== +//------------------------------------------------------------------------------ +// HEX STRING TO BYTES +//------------------------------------------------------------------------------ function HexStringToBytes(const Hex: string): TBytes; var Clean: string; @@ -59,6 +71,9 @@ function HexStringToBytes(const Hex: string): TBytes; end; end; +//------------------------------------------------------------------------------ +// FIXTURE PATH +//------------------------------------------------------------------------------ function FixturePath(const FileName: string): string; var Candidate: string; @@ -99,6 +114,9 @@ TCaptureReplayTransport = class(TInterfacedObject, IOBDDiagnosticTransport) function TargetECU: Word; end; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TCaptureReplayTransport.Create( const Pairs: TArray); var @@ -116,12 +134,18 @@ constructor TCaptureReplayTransport.Create( SetLength(FConsumed, FPairs.Count); end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TCaptureReplayTransport.Destroy; begin FPairs.Free; inherited; end; +//------------------------------------------------------------------------------ +// BYTES EQUAL +//------------------------------------------------------------------------------ function BytesEqual(const A, B: TBytes): Boolean; var I: Integer; @@ -132,6 +156,9 @@ function BytesEqual(const A, B: TBytes): Boolean; Result := True; end; +//------------------------------------------------------------------------------ +// SEND RECEIVE +//------------------------------------------------------------------------------ function TCaptureReplayTransport.SendReceive(const Request: TBytes; TimeoutMs: Cardinal): TBytes; var @@ -147,15 +174,29 @@ function TCaptureReplayTransport.SendReceive(const Request: TBytes; 'replay: no pair matches request (%d bytes)', [Length(Request)]); end; +//------------------------------------------------------------------------------ +// SET TARGET ECU +//------------------------------------------------------------------------------ procedure TCaptureReplayTransport.SetTargetECU(Address: Word); -begin FECU := Address; end; +begin + FECU := Address; +end; +//------------------------------------------------------------------------------ +// TARGET ECU +//------------------------------------------------------------------------------ function TCaptureReplayTransport.TargetECU: Word; -begin Result := FECU; end; +begin + Result := FECU; +end; //============================================================================== // Tests //============================================================================== + +//------------------------------------------------------------------------------ +// LOAD VW CAPTURE +//------------------------------------------------------------------------------ function LoadVwCapture: TArray; var Path: string; @@ -173,6 +214,9 @@ function LoadVwCapture: TArray; end; end; +//------------------------------------------------------------------------------ +// LOAD VW CATALOG +//------------------------------------------------------------------------------ function LoadVwCatalog: TOBDOEMJSONCatalog; var Path: string; @@ -183,6 +227,9 @@ function LoadVwCatalog: TOBDOEMJSONCatalog; Result := TOBDOEMJSONCatalog.Create(Path); end; +//------------------------------------------------------------------------------ +// VW_VIN_DECODES FROM CAPTURED F190 +//------------------------------------------------------------------------------ procedure TUdsClientReplayTests.VW_VIN_DecodesFromCapturedF190; var Pairs: TArray; @@ -209,6 +256,9 @@ procedure TUdsClientReplayTests.VW_VIN_DecodesFromCapturedF190; end; end; +//------------------------------------------------------------------------------ +// VW_HARDWARE NUMBER_DECODES FROM CAPTURED F187 +//------------------------------------------------------------------------------ procedure TUdsClientReplayTests.VW_HardwareNumber_DecodesFromCapturedF187; var Pairs: TArray; @@ -236,6 +286,9 @@ procedure TUdsClientReplayTests.VW_HardwareNumber_DecodesFromCapturedF187; end; end; +//------------------------------------------------------------------------------ +// VW_UNKNOWN DID_RAISES CATALOG MISS +//------------------------------------------------------------------------------ procedure TUdsClientReplayTests.VW_UnknownDID_RaisesCatalogMiss; var Pairs: TArray; diff --git a/tests/Tests.OEM.UdsClient.pas b/tests/Tests.OEM.UdsClient.pas index 489ae7ee..247a66cc 100644 --- a/tests/Tests.OEM.UdsClient.pas +++ b/tests/Tests.OEM.UdsClient.pas @@ -17,36 +17,98 @@ interface [TestFixture] TUdsClientTests = class public + /// + /// Read d i d resolves by name returns decoded value. + /// [Test] procedure ReadDID_ResolvesByName_ReturnsDecodedValue; + /// + /// Read d i d resolves by hex returns decoded value. + /// [Test] procedure ReadDID_ResolvesByHex_ReturnsDecodedValue; + /// + /// Read d i d applies scale and offset. + /// [Test] procedure ReadDID_AppliesScaleAndOffset; + /// + /// Read d i d decodes enum. + /// [Test] procedure ReadDID_DecodesEnum; + /// + /// Read d i d decodes ascii. + /// [Test] procedure ReadDID_DecodesAscii; + /// + /// Read d i d raises when catalog miss. + /// [Test] procedure ReadDID_RaisesWhenCatalogMiss; + /// + /// Read d i d raises when no session. + /// [Test] procedure ReadDID_RaisesWhenNoSession; + /// + /// Write adaptation packs u int8. + /// [Test] procedure WriteAdaptation_PacksUInt8; + /// + /// Write adaptation packs u int16 b e. + /// [Test] procedure WriteAdaptation_PacksUInt16BE; + /// + /// Write adaptation rejects out of range. + /// [Test] procedure WriteAdaptation_RejectsOutOfRange; + /// + /// Write adaptation raises on unknown channel. + /// [Test] procedure WriteAdaptation_RaisesOnUnknownChannel; - /// Regression for G6 — when a catalog declares - /// min=0, max=0 explicitly (e.g. an enum pinned to a single - /// legal value), only Value=0 must be accepted. The earlier - /// implementation skipped validation entirely when both - /// bounds were zero and would have let any value through. + /// + /// Regression for G6 — when a catalog declares + /// min=0, max=0 explicitly (e.g. an enum pinned to a single + /// legal value), only Value=0 must be accepted. The earlier + /// implementation skipped validation entirely when both + /// bounds were zero and would have let any value through. + /// [Test] procedure WriteAdaptation_FixedZeroEnforced; + /// + /// Execute routine starts and returns ok. + /// [Test] procedure ExecuteRoutine_StartsAndReturnsOk; + /// + /// Execute routine reports unexpected response. + /// [Test] procedure ExecuteRoutine_ReportsUnexpectedResponse; + /// + /// Run actuator test gates on safety warning. + /// [Test] procedure RunActuatorTest_GatesOnSafetyWarning; + /// + /// Run actuator test acknowledged safety runs. + /// [Test] procedure RunActuatorTest_AcknowledgedSafetyRuns; + /// + /// Run actuator test no safety runs freely. + /// [Test] procedure RunActuatorTest_NoSafetyRunsFreely; + /// + /// Read coding block unpacks bit fields. + /// [Test] procedure ReadCodingBlock_UnpacksBitFields; + /// + /// Write coding block preserves uncovered bits. + /// [Test] procedure WriteCodingBlock_PreservesUncoveredBits; + /// + /// Read dtcs decodes p codes. + /// [Test] procedure ReadDtcs_DecodesPCodes; + /// + /// Read dtcs decodes u codes. + /// [Test] procedure ReadDtcs_DecodesUCodes; end; @@ -78,6 +140,9 @@ TMockTransport = class(TInterfacedObject, IOBDDiagnosticTransport) function TargetECU: Word; end; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TMockTransport.Create; begin inherited Create; @@ -85,6 +150,9 @@ constructor TMockTransport.Create; FRequests := TList.Create; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TMockTransport.Destroy; begin FCannedResponses.Free; @@ -92,21 +160,33 @@ destructor TMockTransport.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// ENQUEUE RESPONSE +//------------------------------------------------------------------------------ procedure TMockTransport.EnqueueResponse(const Bytes: TBytes); begin FCannedResponses.Add(Bytes); end; +//------------------------------------------------------------------------------ +// REQUEST COUNT +//------------------------------------------------------------------------------ function TMockTransport.RequestCount: Integer; begin Result := FRequests.Count; end; +//------------------------------------------------------------------------------ +// REQUEST +//------------------------------------------------------------------------------ function TMockTransport.Request(Index: Integer): TBytes; begin Result := FRequests[Index]; end; +//------------------------------------------------------------------------------ +// SEND RECEIVE +//------------------------------------------------------------------------------ function TMockTransport.SendReceive(const Request: TBytes; TimeoutMs: Cardinal): TBytes; begin @@ -117,11 +197,17 @@ function TMockTransport.SendReceive(const Request: TBytes; FCannedResponses.Delete(0); end; +//------------------------------------------------------------------------------ +// SET TARGET ECU +//------------------------------------------------------------------------------ procedure TMockTransport.SetTargetECU(Address: Word); begin FECUAddress := Address; end; +//------------------------------------------------------------------------------ +// TARGET ECU +//------------------------------------------------------------------------------ function TMockTransport.TargetECU: Word; begin Result := FECUAddress; @@ -131,6 +217,9 @@ function TMockTransport.TargetECU: Word; // Helpers //============================================================================== +//------------------------------------------------------------------------------ +// MAKE CATALOG +//------------------------------------------------------------------------------ function MakeCatalog(const Json: string): TOBDOEMJSONCatalog; begin Result := TOBDOEMJSONCatalog.CreateFromText(Json); @@ -186,11 +275,17 @@ function MakeCatalog(const Json: string): TOBDOEMJSONCatalog; ' ]}' + ' ]}'; +//------------------------------------------------------------------------------ +// CATALOG FROM JSON +//------------------------------------------------------------------------------ function CatalogFromJson: TOBDOEMJSONCatalog; begin Result := MakeCatalog(CATALOG_JSON); end; +//------------------------------------------------------------------------------ +// OPEN WITH CATALOG +//------------------------------------------------------------------------------ procedure OpenWithCatalog(const Client: IOBDUdsClient; const Catalog: TOBDOEMJSONCatalog; const Transport: IOBDDiagnosticTransport); @@ -198,11 +293,17 @@ procedure OpenWithCatalog(const Client: IOBDUdsClient; Client.OpenSession(Catalog, Transport, $7E0); end; -/// Convenience: build a mock transport and return both the -/// concrete class ref (for EnqueueResponse / Request inspection) and -/// the interface ref (for injection). Caller holds ITransport -/// in a local var to keep the object alive; Mock is a borrowed -/// alias — never call Free on it. +/// +/// Convenience: build a mock transport and return both the +/// concrete class ref (for EnqueueResponse / Request inspection) and +/// the interface ref (for injection). Caller holds ITransport +/// in a local var to keep the object alive; Mock is a borrowed +/// alias — never call Free on it. +/// + +//------------------------------------------------------------------------------ +// NEW MOCK +//------------------------------------------------------------------------------ procedure NewMock(out Mock: TMockTransport; out ITransport: IOBDDiagnosticTransport); begin @@ -214,6 +315,9 @@ procedure NewMock(out Mock: TMockTransport; // Tests //============================================================================== +//------------------------------------------------------------------------------ +// READ DID_RESOLVES BY NAME_RETURNS DECODED VALUE +//------------------------------------------------------------------------------ procedure TUdsClientTests.ReadDID_ResolvesByName_ReturnsDecodedValue; var Catalog: TOBDOEMJSONCatalog; @@ -239,6 +343,9 @@ procedure TUdsClientTests.ReadDID_ResolvesByName_ReturnsDecodedValue; end; end; +//------------------------------------------------------------------------------ +// READ DID_RESOLVES BY HEX_RETURNS DECODED VALUE +//------------------------------------------------------------------------------ procedure TUdsClientTests.ReadDID_ResolvesByHex_ReturnsDecodedValue; var Catalog: TOBDOEMJSONCatalog; @@ -261,6 +368,9 @@ procedure TUdsClientTests.ReadDID_ResolvesByHex_ReturnsDecodedValue; end; end; +//------------------------------------------------------------------------------ +// READ DID_APPLIES SCALE AND OFFSET +//------------------------------------------------------------------------------ procedure TUdsClientTests.ReadDID_AppliesScaleAndOffset; var Catalog: TOBDOEMJSONCatalog; @@ -284,6 +394,9 @@ procedure TUdsClientTests.ReadDID_AppliesScaleAndOffset; end; end; +//------------------------------------------------------------------------------ +// READ DID_DECODES ENUM +//------------------------------------------------------------------------------ procedure TUdsClientTests.ReadDID_DecodesEnum; var Catalog: TOBDOEMJSONCatalog; @@ -308,6 +421,9 @@ procedure TUdsClientTests.ReadDID_DecodesEnum; end; end; +//------------------------------------------------------------------------------ +// READ DID_DECODES ASCII +//------------------------------------------------------------------------------ procedure TUdsClientTests.ReadDID_DecodesAscii; var Catalog: TOBDOEMJSONCatalog; @@ -332,6 +448,9 @@ procedure TUdsClientTests.ReadDID_DecodesAscii; end; end; +//------------------------------------------------------------------------------ +// READ DID_RAISES WHEN CATALOG MISS +//------------------------------------------------------------------------------ procedure TUdsClientTests.ReadDID_RaisesWhenCatalogMiss; var Catalog: TOBDOEMJSONCatalog; @@ -354,6 +473,9 @@ procedure TUdsClientTests.ReadDID_RaisesWhenCatalogMiss; end; end; +//------------------------------------------------------------------------------ +// READ DID_RAISES WHEN NO SESSION +//------------------------------------------------------------------------------ procedure TUdsClientTests.ReadDID_RaisesWhenNoSession; var Client: IOBDUdsClient; @@ -365,6 +487,9 @@ procedure TUdsClientTests.ReadDID_RaisesWhenNoSession; end, EOBDUdsNoSession); end; +//------------------------------------------------------------------------------ +// WRITE ADAPTATION_PACKS UINT8 +//------------------------------------------------------------------------------ procedure TUdsClientTests.WriteAdaptation_PacksUInt8; var Catalog: TOBDOEMJSONCatalog; @@ -394,6 +519,9 @@ procedure TUdsClientTests.WriteAdaptation_PacksUInt8; end; end; +//------------------------------------------------------------------------------ +// WRITE ADAPTATION_PACKS UINT16 BE +//------------------------------------------------------------------------------ procedure TUdsClientTests.WriteAdaptation_PacksUInt16BE; var Catalog: TOBDOEMJSONCatalog; @@ -420,6 +548,9 @@ procedure TUdsClientTests.WriteAdaptation_PacksUInt16BE; end; end; +//------------------------------------------------------------------------------ +// WRITE ADAPTATION_REJECTS OUT OF RANGE +//------------------------------------------------------------------------------ procedure TUdsClientTests.WriteAdaptation_RejectsOutOfRange; var Catalog: TOBDOEMJSONCatalog; @@ -443,6 +574,9 @@ procedure TUdsClientTests.WriteAdaptation_RejectsOutOfRange; end; end; +//------------------------------------------------------------------------------ +// WRITE ADAPTATION_FIXED ZERO ENFORCED +//------------------------------------------------------------------------------ procedure TUdsClientTests.WriteAdaptation_FixedZeroEnforced; const PINNED_CATALOG = @@ -480,6 +614,9 @@ procedure TUdsClientTests.WriteAdaptation_FixedZeroEnforced; end; end; +//------------------------------------------------------------------------------ +// WRITE ADAPTATION_RAISES ON UNKNOWN CHANNEL +//------------------------------------------------------------------------------ procedure TUdsClientTests.WriteAdaptation_RaisesOnUnknownChannel; var Catalog: TOBDOEMJSONCatalog; @@ -502,6 +639,9 @@ procedure TUdsClientTests.WriteAdaptation_RaisesOnUnknownChannel; end; end; +//------------------------------------------------------------------------------ +// EXECUTE ROUTINE_STARTS AND RETURNS OK +//------------------------------------------------------------------------------ procedure TUdsClientTests.ExecuteRoutine_StartsAndReturnsOk; var Catalog: TOBDOEMJSONCatalog; @@ -525,6 +665,9 @@ procedure TUdsClientTests.ExecuteRoutine_StartsAndReturnsOk; end; end; +//------------------------------------------------------------------------------ +// EXECUTE ROUTINE_REPORTS UNEXPECTED RESPONSE +//------------------------------------------------------------------------------ procedure TUdsClientTests.ExecuteRoutine_ReportsUnexpectedResponse; var Catalog: TOBDOEMJSONCatalog; @@ -548,6 +691,9 @@ procedure TUdsClientTests.ExecuteRoutine_ReportsUnexpectedResponse; end; end; +//------------------------------------------------------------------------------ +// RUN ACTUATOR TEST_GATES ON SAFETY WARNING +//------------------------------------------------------------------------------ procedure TUdsClientTests.RunActuatorTest_GatesOnSafetyWarning; var Catalog: TOBDOEMJSONCatalog; @@ -571,6 +717,9 @@ procedure TUdsClientTests.RunActuatorTest_GatesOnSafetyWarning; end; end; +//------------------------------------------------------------------------------ +// RUN ACTUATOR TEST_ACKNOWLEDGED SAFETY RUNS +//------------------------------------------------------------------------------ procedure TUdsClientTests.RunActuatorTest_AcknowledgedSafetyRuns; var Catalog: TOBDOEMJSONCatalog; @@ -594,6 +743,9 @@ procedure TUdsClientTests.RunActuatorTest_AcknowledgedSafetyRuns; end; end; +//------------------------------------------------------------------------------ +// RUN ACTUATOR TEST_NO SAFETY RUNS FREELY +//------------------------------------------------------------------------------ procedure TUdsClientTests.RunActuatorTest_NoSafetyRunsFreely; var Catalog: TOBDOEMJSONCatalog; @@ -617,6 +769,9 @@ procedure TUdsClientTests.RunActuatorTest_NoSafetyRunsFreely; end; end; +//------------------------------------------------------------------------------ +// READ CODING BLOCK_UNPACKS BIT FIELDS +//------------------------------------------------------------------------------ procedure TUdsClientTests.ReadCodingBlock_UnpacksBitFields; var Catalog: TOBDOEMJSONCatalog; @@ -648,6 +803,9 @@ procedure TUdsClientTests.ReadCodingBlock_UnpacksBitFields; end; end; +//------------------------------------------------------------------------------ +// WRITE CODING BLOCK_PRESERVES UNCOVERED BITS +//------------------------------------------------------------------------------ procedure TUdsClientTests.WriteCodingBlock_PreservesUncoveredBits; var Catalog: TOBDOEMJSONCatalog; @@ -696,6 +854,9 @@ procedure TUdsClientTests.WriteCodingBlock_PreservesUncoveredBits; end; end; +//------------------------------------------------------------------------------ +// READ DTCS_DECODES PCODES +//------------------------------------------------------------------------------ procedure TUdsClientTests.ReadDtcs_DecodesPCodes; var Catalog: TOBDOEMJSONCatalog; @@ -727,6 +888,9 @@ procedure TUdsClientTests.ReadDtcs_DecodesPCodes; end; end; +//------------------------------------------------------------------------------ +// READ DTCS_DECODES UCODES +//------------------------------------------------------------------------------ procedure TUdsClientTests.ReadDtcs_DecodesUCodes; var Catalog: TOBDOEMJSONCatalog; diff --git a/tests/Tests.OEM.UltraLuxuryAndEastern.pas b/tests/Tests.OEM.UltraLuxuryAndEastern.pas index 13521362..b35c36d5 100644 --- a/tests/Tests.OEM.UltraLuxuryAndEastern.pas +++ b/tests/Tests.OEM.UltraLuxuryAndEastern.pas @@ -18,37 +18,103 @@ interface [TestFixture] TUltraLuxuryVINTests = class public + /// + /// Aston martin claims scf. + /// [Test] procedure AstonMartinClaimsScf; + /// + /// Bentley claims scb. + /// [Test] procedure BentleyClaimsScb; + /// + /// Rolls royce claims sca. + /// [Test] procedure RollsRoyceClaimsSca; + /// + /// Mc laren claims sbm. + /// [Test] procedure McLarenClaimsSbm; + /// + /// Lada claims all plants. + /// [Test] procedure LadaClaimsAllPlants; + /// + /// Dacia claims romania and china. + /// [Test] procedure DaciaClaimsRomaniaAndChina; + /// + /// Paccar no longer claims scb. + /// [Test] procedure PaccarNoLongerClaimsScb; + /// + /// Renault no longer claims u u1. + /// [Test] procedure RenaultNoLongerClaimsUU1; + /// + /// Dacia does not claim renault v f1. + /// [Test] procedure DaciaDoesNotClaimRenaultVF1; end; [TestFixture] TUltraLuxuryCatalogTests = class public + /// + /// Aston martin exposes valhalla p h e v. + /// [Test] procedure AstonMartinExposesValhallaPHEV; + /// + /// Bentley exposes dynamic ride and rear steer. + /// [Test] procedure BentleyExposesDynamicRideAndRearSteer; + /// + /// Rolls royce session requires security access. + /// [Test] procedure RollsRoyceSessionRequiresSecurityAccess; + /// + /// Rolls royce exposes spectre e v. + /// [Test] procedure RollsRoyceExposesSpectreEV; + /// + /// Mc laren exposes artura p h e v. + /// [Test] procedure McLarenExposesArturaPHEV; + /// + /// Lada exposes niva transfer case. + /// [Test] procedure LadaExposesNivaTransferCase; + /// + /// Dacia exposes spring e v. + /// [Test] procedure DaciaExposesSpringEV; end; [TestFixture] TUltraLuxuryDecoderTests = class public + /// + /// Aston martin decodes paint code. + /// [Test] procedure AstonMartinDecodesPaintCode; + /// + /// Bentley decodes commission number. + /// [Test] procedure BentleyDecodesCommissionNumber; + /// + /// Rolls royce decodes starlight pattern. + /// [Test] procedure RollsRoyceDecodesStarlightPattern; + /// + /// Mc laren decodes chassis serial. + /// [Test] procedure McLarenDecodesChassisSerial; + /// + /// Lada decodes engine code. + /// [Test] procedure LadaDecodesEngineCode; + /// + /// Dacia decodes engine code. + /// [Test] procedure DaciaDecodesEngineCode; end; @@ -61,6 +127,9 @@ implementation OBD.OEM.McLaren, OBD.OEM.Lada, OBD.OEM.Dacia, OBD.OEM.PACCAR, OBD.OEM.Renault; +//------------------------------------------------------------------------------ +// MAKE VIN +//------------------------------------------------------------------------------ function MakeVin(const Prefix: string): string; begin Result := (Prefix + '00000000000000'); @@ -70,8 +139,13 @@ function MakeVin(const Prefix: string): string; //============================================================================== // VIN routing //============================================================================== + +//------------------------------------------------------------------------------ +// ASTON MARTIN CLAIMS SCF +//------------------------------------------------------------------------------ procedure TUltraLuxuryVINTests.AstonMartinClaimsScf; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionAstonMartin.Create; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('SCF'))); @@ -79,30 +153,46 @@ procedure TUltraLuxuryVINTests.AstonMartinClaimsScf; Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('SCB')), 'SCB is Bentley'); end; +//------------------------------------------------------------------------------ +// BENTLEY CLAIMS SCB +//------------------------------------------------------------------------------ procedure TUltraLuxuryVINTests.BentleyClaimsScb; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionBentley.Create; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('SCB'))); Assert.IsFalse(Ext.ApplicableToVIN(MakeVin('SCA'))); end; +//------------------------------------------------------------------------------ +// ROLLS ROYCE CLAIMS SCA +//------------------------------------------------------------------------------ procedure TUltraLuxuryVINTests.RollsRoyceClaimsSca; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionRollsRoyce.Create; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('SCA'))); end; +//------------------------------------------------------------------------------ +// MC LAREN CLAIMS SBM +//------------------------------------------------------------------------------ procedure TUltraLuxuryVINTests.McLarenClaimsSbm; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionMcLaren.Create; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('SBM'))); end; +//------------------------------------------------------------------------------ +// LADA CLAIMS ALL PLANTS +//------------------------------------------------------------------------------ procedure TUltraLuxuryVINTests.LadaClaimsAllPlants; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionLada.Create; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('XTA')), 'Tolyatti volume'); @@ -110,8 +200,12 @@ procedure TUltraLuxuryVINTests.LadaClaimsAllPlants; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('XTV')), 'Bronto special-vehicles'); end; +//------------------------------------------------------------------------------ +// DACIA CLAIMS ROMANIA AND CHINA +//------------------------------------------------------------------------------ procedure TUltraLuxuryVINTests.DaciaClaimsRomaniaAndChina; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionDacia.Create; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('UU1')), 'Mioveni passenger'); @@ -119,6 +213,9 @@ procedure TUltraLuxuryVINTests.DaciaClaimsRomaniaAndChina; Assert.IsTrue(Ext.ApplicableToVIN(MakeVin('LBR')), 'Dongfeng-Renault Wuhan (Spring)'); end; +//------------------------------------------------------------------------------ +// PACCAR NO LONGER CLAIMS SCB +//------------------------------------------------------------------------------ procedure TUltraLuxuryVINTests.PaccarNoLongerClaimsScb; var PACCAR, Bentley: IOBDOEMExtension; @@ -132,6 +229,9 @@ procedure TUltraLuxuryVINTests.PaccarNoLongerClaimsScb; Assert.IsTrue(PACCAR.ApplicableToVIN(MakeVin('SAR'))); end; +//------------------------------------------------------------------------------ +// RENAULT NO LONGER CLAIMS UU1 +//------------------------------------------------------------------------------ procedure TUltraLuxuryVINTests.RenaultNoLongerClaimsUU1; var Renault, Dacia: IOBDOEMExtension; @@ -143,8 +243,12 @@ procedure TUltraLuxuryVINTests.RenaultNoLongerClaimsUU1; Assert.IsTrue(Dacia.ApplicableToVIN(MakeVin('UU1'))); end; +//------------------------------------------------------------------------------ +// DACIA DOES NOT CLAIM RENAULT VF1 +//------------------------------------------------------------------------------ procedure TUltraLuxuryVINTests.DaciaDoesNotClaimRenaultVF1; -var Dacia: IOBDOEMExtension; +var + Dacia: IOBDOEMExtension; begin Dacia := TOBDOEMExtensionDacia.Create; Assert.IsFalse(Dacia.ApplicableToVIN(MakeVin('VF1'))); @@ -154,6 +258,10 @@ procedure TUltraLuxuryVINTests.DaciaDoesNotClaimRenaultVF1; //============================================================================== // Catalog spot-checks //============================================================================== + +//------------------------------------------------------------------------------ +// ASTON MARTIN EXPOSES VALHALLA PHEV +//------------------------------------------------------------------------------ procedure TUltraLuxuryCatalogTests.AstonMartinExposesValhallaPHEV; var Ext: IOBDOEMExtension; E: TOBDOEMECU; @@ -170,6 +278,9 @@ procedure TUltraLuxuryCatalogTests.AstonMartinExposesValhallaPHEV; 'Aston Martin must expose Valhalla PHEV stack'); end; +//------------------------------------------------------------------------------ +// BENTLEY EXPOSES DYNAMIC RIDE AND REAR STEER +//------------------------------------------------------------------------------ procedure TUltraLuxuryCatalogTests.BentleyExposesDynamicRideAndRearSteer; var Ext: IOBDOEMExtension; E: TOBDOEMECU; @@ -186,14 +297,21 @@ procedure TUltraLuxuryCatalogTests.BentleyExposesDynamicRideAndRearSteer; Assert.IsTrue(HasRearSteer, 'Bentley must expose rear-wheel steering'); end; +//------------------------------------------------------------------------------ +// ROLLS ROYCE SESSION REQUIRES SECURITY ACCESS +//------------------------------------------------------------------------------ procedure TUltraLuxuryCatalogTests.RollsRoyceSessionRequiresSecurityAccess; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionRollsRoyce.Create; Assert.IsTrue(Ext.SessionNegotiator.RequiresSecurityAccess(sstExtendedDiagnostic)); Assert.IsTrue(Ext.SessionNegotiator.RequiresSecurityAccess(sstProgramming)); end; +//------------------------------------------------------------------------------ +// ROLLS ROYCE EXPOSES SPECTRE EV +//------------------------------------------------------------------------------ procedure TUltraLuxuryCatalogTests.RollsRoyceExposesSpectreEV; var Ext: IOBDOEMExtension; E: TOBDOEMECU; @@ -211,6 +329,9 @@ procedure TUltraLuxuryCatalogTests.RollsRoyceExposesSpectreEV; Assert.IsTrue(HasStarlight, 'Rolls-Royce must expose Starlight controller'); end; +//------------------------------------------------------------------------------ +// MC LAREN EXPOSES ARTURA PHEV +//------------------------------------------------------------------------------ procedure TUltraLuxuryCatalogTests.McLarenExposesArturaPHEV; var Ext: IOBDOEMExtension; E: TOBDOEMECU; @@ -229,6 +350,9 @@ procedure TUltraLuxuryCatalogTests.McLarenExposesArturaPHEV; Assert.IsTrue(HasLift, 'McLaren must expose front-axle lift'); end; +//------------------------------------------------------------------------------ +// LADA EXPOSES NIVA TRANSFER CASE +//------------------------------------------------------------------------------ procedure TUltraLuxuryCatalogTests.LadaExposesNivaTransferCase; var Ext: IOBDOEMExtension; E: TOBDOEMECU; @@ -245,6 +369,9 @@ procedure TUltraLuxuryCatalogTests.LadaExposesNivaTransferCase; Assert.IsTrue(HasImmo, 'Lada must expose APS immobilizer'); end; +//------------------------------------------------------------------------------ +// DACIA EXPOSES SPRING EV +//------------------------------------------------------------------------------ procedure TUltraLuxuryCatalogTests.DaciaExposesSpringEV; var Ext: IOBDOEMExtension; E: TOBDOEMECU; @@ -266,6 +393,10 @@ procedure TUltraLuxuryCatalogTests.DaciaExposesSpringEV; //============================================================================== // DID decoder spot-checks //============================================================================== + +//------------------------------------------------------------------------------ +// ASTON MARTIN DECODES PAINT CODE +//------------------------------------------------------------------------------ procedure TUltraLuxuryDecoderTests.AstonMartinDecodesPaintCode; var Ext: IOBDOEMExtension; Output: string; @@ -275,6 +406,9 @@ procedure TUltraLuxuryDecoderTests.AstonMartinDecodesPaintCode; Assert.IsTrue(Pos('Skyfall-Silver', Output) > 0); end; +//------------------------------------------------------------------------------ +// BENTLEY DECODES COMMISSION NUMBER +//------------------------------------------------------------------------------ procedure TUltraLuxuryDecoderTests.BentleyDecodesCommissionNumber; var Ext: IOBDOEMExtension; Output: string; @@ -284,6 +418,9 @@ procedure TUltraLuxuryDecoderTests.BentleyDecodesCommissionNumber; Assert.IsTrue(Pos('CGT-2026-MULL-042', Output) > 0); end; +//------------------------------------------------------------------------------ +// ROLLS ROYCE DECODES STARLIGHT PATTERN +//------------------------------------------------------------------------------ procedure TUltraLuxuryDecoderTests.RollsRoyceDecodesStarlightPattern; var Ext: IOBDOEMExtension; Output: string; @@ -293,6 +430,9 @@ procedure TUltraLuxuryDecoderTests.RollsRoyceDecodesStarlightPattern; Assert.IsTrue(Pos('Phantom-Bespoke-Constellation-DOB', Output) > 0); end; +//------------------------------------------------------------------------------ +// MC LAREN DECODES CHASSIS SERIAL +//------------------------------------------------------------------------------ procedure TUltraLuxuryDecoderTests.McLarenDecodesChassisSerial; var Ext: IOBDOEMExtension; Output: string; @@ -302,6 +442,9 @@ procedure TUltraLuxuryDecoderTests.McLarenDecodesChassisSerial; Assert.IsTrue(Pos('MonoCellII-J-0457', Output) > 0); end; +//------------------------------------------------------------------------------ +// LADA DECODES ENGINE CODE +//------------------------------------------------------------------------------ procedure TUltraLuxuryDecoderTests.LadaDecodesEngineCode; var Ext: IOBDOEMExtension; Output: string; @@ -311,6 +454,9 @@ procedure TUltraLuxuryDecoderTests.LadaDecodesEngineCode; Assert.IsTrue(Pos('VAZ-21179', Output) > 0); end; +//------------------------------------------------------------------------------ +// DACIA DECODES ENGINE CODE +//------------------------------------------------------------------------------ procedure TUltraLuxuryDecoderTests.DaciaDecodesEngineCode; var Ext: IOBDOEMExtension; Output: string; diff --git a/tests/Tests.OEM.VW.Deep.pas b/tests/Tests.OEM.VW.Deep.pas index d456c6f5..e1f604d1 100644 --- a/tests/Tests.OEM.VW.Deep.pas +++ b/tests/Tests.OEM.VW.Deep.pas @@ -19,27 +19,78 @@ interface [TestFixture] TVWDeepDIDTests = class public + /// + /// Catalog exceeds baseline d i d count. + /// [Test] procedure CatalogExceedsBaselineDIDCount; + /// + /// Engine ecu has lambda per bank. + /// [Test] procedure EngineEcuHasLambdaPerBank; + /// + /// Engine ecu has misfire counters. + /// [Test] procedure EngineEcuHasMisfireCounters; + /// + /// Transmission ecu has dsg clutch pressures. + /// [Test] procedure TransmissionEcuHasDsgClutchPressures; + /// + /// Abs ecu has four wheel speeds. + /// [Test] procedure AbsEcuHasFourWheelSpeeds; + /// + /// Cluster has trip data and service counters. + /// [Test] procedure ClusterHasTripDataAndServiceCounters; + /// + /// Ev stack present. + /// [Test] procedure EvStackPresent; + /// + /// New ecus registered. + /// [Test] procedure NewEcusRegistered; end; [TestFixture] TVWDeepExtendedTests = class public + /// + /// Exposes coding blocks. + /// [Test] procedure ExposesCodingBlocks; + /// + /// Bcm coding block has drl field. + /// [Test] procedure BcmCodingBlockHasDrlField; + /// + /// Exposes adaptations. + /// [Test] procedure ExposesAdaptations; + /// + /// Service interval distance adaptation has bounds. + /// [Test] procedure ServiceIntervalDistanceAdaptationHasBounds; + /// + /// Exposes actuator tests. + /// [Test] procedure ExposesActuatorTests; + /// + /// Cooling fan test carries safety warning. + /// [Test] procedure CoolingFanTestCarriesSafetyWarning; + /// + /// Exposes live p i ds. + /// [Test] procedure ExposesLivePIDs; + /// + /// Exposes dtc extended data. + /// [Test] procedure ExposesDtcExtendedData; + /// + /// Implements extension v2 interface. + /// [Test] procedure ImplementsExtensionV2Interface; end; @@ -49,17 +100,27 @@ implementation System.SysUtils, OBD.OEM, OBD.OEM.VW; +//------------------------------------------------------------------------------ +// FIND DID +//------------------------------------------------------------------------------ function FindDID(const All: TArray; const Name: string; out Entry: TOBDOEMDataIdentifier): Boolean; -var D: TOBDOEMDataIdentifier; +var + D: TOBDOEMDataIdentifier; begin for D in All do if D.Name = Name then - begin Entry := D; Exit(True); end; + begin + Entry := D; + Exit(True); + end; Entry := Default(TOBDOEMDataIdentifier); Result := False; end; +//------------------------------------------------------------------------------ +// COUNT BY ECU +//------------------------------------------------------------------------------ function CountByEcu(const All: TArray; const Address: Word): Integer; var @@ -75,8 +136,13 @@ function CountByEcu(const All: TArray; //============================================================================== // Per-ECU DID enrichment //============================================================================== + +//------------------------------------------------------------------------------ +// CATALOG EXCEEDS BASELINE DIDCOUNT +//------------------------------------------------------------------------------ procedure TVWDeepDIDTests.CatalogExceedsBaselineDIDCount; -var Ext: IOBDOEMExtension; +var + Ext: IOBDOEMExtension; begin Ext := TOBDOEMExtensionVW.Create; Assert.IsTrue(Length(Ext.DataIdentifiers) >= 100, @@ -84,6 +150,9 @@ procedure TVWDeepDIDTests.CatalogExceedsBaselineDIDCount; [Length(Ext.DataIdentifiers)])); end; +//------------------------------------------------------------------------------ +// ENGINE ECU HAS LAMBDA PER BANK +//------------------------------------------------------------------------------ procedure TVWDeepDIDTests.EngineEcuHasLambdaPerBank; var Ext: IOBDOEMExtension; D: TOBDOEMDataIdentifier; @@ -97,6 +166,9 @@ procedure TVWDeepDIDTests.EngineEcuHasLambdaPerBank; Assert.IsTrue(FindDID(Ext.DataIdentifiers, 'vag_lambda_actual_b2s2', D)); end; +//------------------------------------------------------------------------------ +// ENGINE ECU HAS MISFIRE COUNTERS +//------------------------------------------------------------------------------ procedure TVWDeepDIDTests.EngineEcuHasMisfireCounters; var Ext: IOBDOEMExtension; D: TOBDOEMDataIdentifier; @@ -106,6 +178,9 @@ procedure TVWDeepDIDTests.EngineEcuHasMisfireCounters; Assert.IsTrue(FindDID(Ext.DataIdentifiers, 'vag_misfire_count_cyl4', D)); end; +//------------------------------------------------------------------------------ +// TRANSMISSION ECU HAS DSG CLUTCH PRESSURES +//------------------------------------------------------------------------------ procedure TVWDeepDIDTests.TransmissionEcuHasDsgClutchPressures; var Ext: IOBDOEMExtension; D: TOBDOEMDataIdentifier; @@ -117,6 +192,9 @@ procedure TVWDeepDIDTests.TransmissionEcuHasDsgClutchPressures; 'DSG K2 pressure must be scoped to the transmission ECU'); end; +//------------------------------------------------------------------------------ +// ABS ECU HAS FOUR WHEEL SPEEDS +//------------------------------------------------------------------------------ procedure TVWDeepDIDTests.AbsEcuHasFourWheelSpeeds; var Ext: IOBDOEMExtension; D: TOBDOEMDataIdentifier; begin @@ -127,6 +205,9 @@ procedure TVWDeepDIDTests.AbsEcuHasFourWheelSpeeds; Assert.IsTrue(FindDID(Ext.DataIdentifiers, 'vag_abs_wheel_speed_rr', D)); end; +//------------------------------------------------------------------------------ +// CLUSTER HAS TRIP DATA AND SERVICE COUNTERS +//------------------------------------------------------------------------------ procedure TVWDeepDIDTests.ClusterHasTripDataAndServiceCounters; var Ext: IOBDOEMExtension; D: TOBDOEMDataIdentifier; begin @@ -136,6 +217,9 @@ procedure TVWDeepDIDTests.ClusterHasTripDataAndServiceCounters; Assert.IsTrue(FindDID(Ext.DataIdentifiers, 'vag_cluster_oil_distance_km', D)); end; +//------------------------------------------------------------------------------ +// EV STACK PRESENT +//------------------------------------------------------------------------------ procedure TVWDeepDIDTests.EvStackPresent; var Ext: IOBDOEMExtension; D: TOBDOEMDataIdentifier; begin @@ -145,6 +229,9 @@ procedure TVWDeepDIDTests.EvStackPresent; Assert.IsTrue(FindDID(Ext.DataIdentifiers, 'vag_ev_remaining_range_km', D)); end; +//------------------------------------------------------------------------------ +// NEW ECUS REGISTERED +//------------------------------------------------------------------------------ procedure TVWDeepDIDTests.NewEcusRegistered; var Ext: IOBDOEMExtension; E: TOBDOEMECU; @@ -166,6 +253,10 @@ procedure TVWDeepDIDTests.NewEcusRegistered; //============================================================================== // Schema v2 — coding blocks / adaptations / actuator tests / live PIDs / DTC ext //============================================================================== + +//------------------------------------------------------------------------------ +// IMPLEMENTS EXTENSION V2 INTERFACE +//------------------------------------------------------------------------------ procedure TVWDeepExtendedTests.ImplementsExtensionV2Interface; var Ext: IOBDOEMExtension; V2: IOBDOEMExtensionV2; @@ -176,6 +267,9 @@ procedure TVWDeepExtendedTests.ImplementsExtensionV2Interface; Assert.IsNotNull(V2); end; +//------------------------------------------------------------------------------ +// EXPOSES CODING BLOCKS +//------------------------------------------------------------------------------ procedure TVWDeepExtendedTests.ExposesCodingBlocks; var Ext: IOBDOEMExtension; V2: IOBDOEMExtensionV2; @@ -187,6 +281,9 @@ procedure TVWDeepExtendedTests.ExposesCodingBlocks; + IntToStr(Length(V2.CodingBlocks)) + ')'); end; +//------------------------------------------------------------------------------ +// BCM CODING BLOCK HAS DRL FIELD +//------------------------------------------------------------------------------ procedure TVWDeepExtendedTests.BcmCodingBlockHasDrlField; var Ext: IOBDOEMExtension; V2: IOBDOEMExtensionV2; @@ -213,6 +310,9 @@ procedure TVWDeepExtendedTests.BcmCodingBlockHasDrlField; Assert.IsTrue(HasCountry); end; +//------------------------------------------------------------------------------ +// EXPOSES ADAPTATIONS +//------------------------------------------------------------------------------ procedure TVWDeepExtendedTests.ExposesAdaptations; var Ext: IOBDOEMExtension; V2: IOBDOEMExtensionV2; @@ -223,6 +323,9 @@ procedure TVWDeepExtendedTests.ExposesAdaptations; 'VW must expose at least 12 adaptation channels'); end; +//------------------------------------------------------------------------------ +// SERVICE INTERVAL DISTANCE ADAPTATION HAS BOUNDS +//------------------------------------------------------------------------------ procedure TVWDeepExtendedTests.ServiceIntervalDistanceAdaptationHasBounds; var Ext: IOBDOEMExtension; V2: IOBDOEMExtensionV2; @@ -244,6 +347,9 @@ procedure TVWDeepExtendedTests.ServiceIntervalDistanceAdaptationHasBounds; Assert.IsTrue(Found); end; +//------------------------------------------------------------------------------ +// EXPOSES ACTUATOR TESTS +//------------------------------------------------------------------------------ procedure TVWDeepExtendedTests.ExposesActuatorTests; var Ext: IOBDOEMExtension; V2: IOBDOEMExtensionV2; @@ -253,6 +359,9 @@ procedure TVWDeepExtendedTests.ExposesActuatorTests; Assert.IsTrue(Length(V2.ActuatorTests) >= 10); end; +//------------------------------------------------------------------------------ +// COOLING FAN TEST CARRIES SAFETY WARNING +//------------------------------------------------------------------------------ procedure TVWDeepExtendedTests.CoolingFanTestCarriesSafetyWarning; var Ext: IOBDOEMExtension; V2: IOBDOEMExtensionV2; @@ -274,6 +383,9 @@ procedure TVWDeepExtendedTests.CoolingFanTestCarriesSafetyWarning; Assert.IsTrue(Found); end; +//------------------------------------------------------------------------------ +// EXPOSES LIVE PIDS +//------------------------------------------------------------------------------ procedure TVWDeepExtendedTests.ExposesLivePIDs; var Ext: IOBDOEMExtension; V2: IOBDOEMExtensionV2; @@ -292,6 +404,9 @@ procedure TVWDeepExtendedTests.ExposesLivePIDs; Assert.IsTrue(HasService22, 'live_pids must include OEM mode 0x22 entries'); end; +//------------------------------------------------------------------------------ +// EXPOSES DTC EXTENDED DATA +//------------------------------------------------------------------------------ procedure TVWDeepExtendedTests.ExposesDtcExtendedData; var Ext: IOBDOEMExtension; V2: IOBDOEMExtensionV2; diff --git a/tests/Tests.OEM.pas b/tests/Tests.OEM.pas index 4d0103db..3498966b 100644 --- a/tests/Tests.OEM.pas +++ b/tests/Tests.OEM.pas @@ -13,19 +13,55 @@ interface [TestFixture] TOEMRegistryTests = class public + /// + /// Register and find by key. + /// [Test] procedure RegisterAndFindByKey; + /// + /// Find by v i n v w matches w v w. + /// [Test] procedure FindByVIN_VW_MatchesWVW; + /// + /// Find by v i n b m w matches w b a. + /// [Test] procedure FindByVIN_BMW_MatchesWBA; + /// + /// Find by v i n non o e m returns nil. + /// [Test] procedure FindByVIN_NonOEMReturnsNil; + /// + /// Register is idempotent. + /// [Test] procedure RegisterIsIdempotent; + /// + /// Unregister removes extension. + /// [Test] procedure UnregisterRemovesExtension; + /// + /// V w decode battery voltage. + /// [Test] procedure VW_DecodeBatteryVoltage; + /// + /// V w decode vehicle speed. + /// [Test] procedure VW_DecodeVehicleSpeed; + /// + /// V w decode unknown d i d falls back to hex. + /// [Test] procedure VW_DecodeUnknownDIDFallsBackToHex; + /// + /// B m w decode mileage. + /// [Test] procedure BMW_DecodeMileage; + /// + /// Find d i d looks up catalog entry. + /// [Test] procedure FindDID_LooksUpCatalogEntry; + /// + /// Find routine looks up catalog entry. + /// [Test] procedure FindRoutine_LooksUpCatalogEntry; end; @@ -34,6 +70,9 @@ implementation uses System.SysUtils, OBD.OEM, OBD.OEM.VW, OBD.OEM.BMW; +//------------------------------------------------------------------------------ +// REGISTER AND FIND BY KEY +//------------------------------------------------------------------------------ procedure TOEMRegistryTests.RegisterAndFindByKey; var Ext: IOBDOEMExtension; @@ -44,6 +83,9 @@ procedure TOEMRegistryTests.RegisterAndFindByKey; Assert.AreEqual('VAG', Ext.ManufacturerKey); end; +//------------------------------------------------------------------------------ +// FIND BY VIN_VW_MATCHES WVW +//------------------------------------------------------------------------------ procedure TOEMRegistryTests.FindByVIN_VW_MatchesWVW; var Ext: IOBDOEMExtension; @@ -53,6 +95,9 @@ procedure TOEMRegistryTests.FindByVIN_VW_MatchesWVW; Assert.AreEqual('VAG', Ext.ManufacturerKey); end; +//------------------------------------------------------------------------------ +// FIND BY VIN_BMW_MATCHES WBA +//------------------------------------------------------------------------------ procedure TOEMRegistryTests.FindByVIN_BMW_MatchesWBA; var Ext: IOBDOEMExtension; @@ -62,6 +107,9 @@ procedure TOEMRegistryTests.FindByVIN_BMW_MatchesWBA; Assert.AreEqual('BMW', Ext.ManufacturerKey); end; +//------------------------------------------------------------------------------ +// FIND BY VIN_NON OEMRETURNS NIL +//------------------------------------------------------------------------------ procedure TOEMRegistryTests.FindByVIN_NonOEMReturnsNil; var Ext: IOBDOEMExtension; @@ -71,6 +119,9 @@ procedure TOEMRegistryTests.FindByVIN_NonOEMReturnsNil; Assert.IsNull(Pointer(Ext)); end; +//------------------------------------------------------------------------------ +// REGISTER IS IDEMPOTENT +//------------------------------------------------------------------------------ procedure TOEMRegistryTests.RegisterIsIdempotent; var Same: IOBDOEMExtension; @@ -84,6 +135,9 @@ procedure TOEMRegistryTests.RegisterIsIdempotent; 'Registering an already-registered extension must not duplicate it'); end; +//------------------------------------------------------------------------------ +// UNREGISTER REMOVES EXTENSION +//------------------------------------------------------------------------------ procedure TOEMRegistryTests.UnregisterRemovesExtension; var Ext: IOBDOEMExtension; @@ -96,6 +150,9 @@ procedure TOEMRegistryTests.UnregisterRemovesExtension; Assert.AreEqual(Before - 1, TOBDOEMRegistry.Count); end; +//------------------------------------------------------------------------------ +// VW_DECODE BATTERY VOLTAGE +//------------------------------------------------------------------------------ procedure TOEMRegistryTests.VW_DecodeBatteryVoltage; var Ext: IOBDOEMExtension; @@ -107,6 +164,9 @@ procedure TOEMRegistryTests.VW_DecodeBatteryVoltage; Assert.AreEqual('battery_voltage = 12.345 V', Ext.DecodeDID($F405, Payload)); end; +//------------------------------------------------------------------------------ +// VW_DECODE VEHICLE SPEED +//------------------------------------------------------------------------------ procedure TOEMRegistryTests.VW_DecodeVehicleSpeed; var Ext: IOBDOEMExtension; @@ -117,6 +177,9 @@ procedure TOEMRegistryTests.VW_DecodeVehicleSpeed; Assert.AreEqual('vehicle_speed = 123 km/h', Ext.DecodeDID($F40D, Payload)); end; +//------------------------------------------------------------------------------ +// VW_DECODE UNKNOWN DIDFALLS BACK TO HEX +//------------------------------------------------------------------------------ procedure TOEMRegistryTests.VW_DecodeUnknownDIDFallsBackToHex; var Ext: IOBDOEMExtension; @@ -127,6 +190,9 @@ procedure TOEMRegistryTests.VW_DecodeUnknownDIDFallsBackToHex; Assert.AreEqual('DID 0x9999 = DE AD BE EF', Ext.DecodeDID($9999, Payload)); end; +//------------------------------------------------------------------------------ +// BMW_DECODE MILEAGE +//------------------------------------------------------------------------------ procedure TOEMRegistryTests.BMW_DecodeMileage; var Ext: IOBDOEMExtension; @@ -138,6 +204,9 @@ procedure TOEMRegistryTests.BMW_DecodeMileage; Assert.AreEqual('mileage = 123456 km', Ext.DecodeDID($D050, Payload)); end; +//------------------------------------------------------------------------------ +// FIND DID_LOOKS UP CATALOG ENTRY +//------------------------------------------------------------------------------ procedure TOEMRegistryTests.FindDID_LooksUpCatalogEntry; var Ext: IOBDOEMExtension; @@ -150,6 +219,9 @@ procedure TOEMRegistryTests.FindDID_LooksUpCatalogEntry; 'unknown DID must return False'); end; +//------------------------------------------------------------------------------ +// FIND ROUTINE_LOOKS UP CATALOG ENTRY +//------------------------------------------------------------------------------ procedure TOEMRegistryTests.FindRoutine_LooksUpCatalogEntry; var Ext: IOBDOEMExtension; diff --git a/tests/Tests.Protocol.DoIP.Cross.pas b/tests/Tests.Protocol.DoIP.Cross.pas index e27c3030..2de35eec 100644 --- a/tests/Tests.Protocol.DoIP.Cross.pas +++ b/tests/Tests.Protocol.DoIP.Cross.pas @@ -53,6 +53,9 @@ TFakeGateway = class(TThread) property Error: string read FError; end; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TFakeGateway.Create(const UdsResponse: TBytes); begin inherited Create(True); @@ -61,6 +64,9 @@ constructor TFakeGateway.Create(const UdsResponse: TBytes); FreeOnTerminate := False; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TFakeGateway.Destroy; begin FReady.Free; @@ -72,11 +78,17 @@ destructor TFakeGateway.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// WAIT READY +//------------------------------------------------------------------------------ procedure TFakeGateway.WaitReady(TimeoutMs: Cardinal); begin FReady.WaitFor(TimeoutMs); end; +//------------------------------------------------------------------------------ +// READ EXACT +//------------------------------------------------------------------------------ function TFakeGateway.ReadExact(Sock: TSocket; Count: Integer): TBytes; var Acc, Chunk: TBytes; @@ -95,6 +107,9 @@ function TFakeGateway.ReadExact(Sock: TSocket; Count: Integer): TBytes; Result := Acc; end; +//------------------------------------------------------------------------------ +// HANDLE CLIENT +//------------------------------------------------------------------------------ procedure TFakeGateway.HandleClient(Sock: TSocket); var Header, Payload: TBytes; @@ -184,6 +199,9 @@ procedure TFakeGateway.HandleClient(Sock: TSocket); Sock.Send(ResponseHeader + DiagResp); end; +//------------------------------------------------------------------------------ +// EXECUTE +//------------------------------------------------------------------------------ procedure TFakeGateway.Execute; var ClientSock: TSocket; @@ -214,6 +232,10 @@ procedure TFakeGateway.Execute; //============================================================================== // Test //============================================================================== + +//------------------------------------------------------------------------------ +// ROUTING ACTIVATION AND DIAGNOSTIC ROUND TRIP +//------------------------------------------------------------------------------ procedure TDoIPCrossTests.RoutingActivationAndDiagnosticRoundTrip; var Gateway: TFakeGateway; diff --git a/tests/Tests.Protocol.DoIP.Discovery.pas b/tests/Tests.Protocol.DoIP.Discovery.pas new file mode 100644 index 00000000..65746eb1 --- /dev/null +++ b/tests/Tests.Protocol.DoIP.Discovery.pas @@ -0,0 +1,241 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.Protocol.DoIP.Discovery.pas +// CONTENTS : Tests for OBD.Protocol.DoIP.Discovery +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.Protocol.DoIP.Discovery; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TDoIPDiscoveryTests = class + public + /// + /// Header has inverse protocol version. + /// + [Test] procedure HeaderHasInverseProtocolVersion; + /// + /// Vehicle ident request is eight bytes. + /// + [Test] procedure VehicleIdentRequestIsEightBytes; + /// + /// Vehicle ident request v i n payload is17 bytes. + /// + [Test] procedure VehicleIdentRequestVINPayloadIs17Bytes; + /// + /// V i n length mismatch raises. + /// + [Test] procedure VINLengthMismatchRaises; + /// + /// E i d length mismatch raises. + /// + [Test] procedure EIDLengthMismatchRaises; + /// + /// Alive check response carries source address. + /// + [Test] procedure AliveCheckResponseCarriesSourceAddress; + /// + /// Parse header rejects bad inverse. + /// + [Test] procedure ParseHeaderRejectsBadInverse; + /// + /// Parse header rejects truncated frame. + /// + [Test] procedure ParseHeaderRejectsTruncatedFrame; + /// + /// Vehicle announcement round trips. + /// + [Test] procedure VehicleAnnouncementRoundTrips; + /// + /// Vehicle announcement2012 without sync is valid. + /// + [Test] procedure VehicleAnnouncement2012WithoutSyncIsValid; + end; + +implementation + +uses + System.SysUtils, + OBD.Protocol.DoIP.Discovery; + +//------------------------------------------------------------------------------ +// HEADER HAS INVERSE PROTOCOL VERSION +//------------------------------------------------------------------------------ +procedure TDoIPDiscoveryTests.HeaderHasInverseProtocolVersion; +var + Frame: TBytes; +begin + Frame := BuildVehicleIdentRequest(DOIP_PROTOCOL_VERSION_2019); + Assert.AreEqual(DOIP_PROTOCOL_VERSION_2019, Integer(Frame[0])); + Assert.AreEqual(Byte(not DOIP_PROTOCOL_VERSION_2019), Frame[1]); +end; + +//------------------------------------------------------------------------------ +// VEHICLE IDENT REQUEST IS EIGHT BYTES +//------------------------------------------------------------------------------ +procedure TDoIPDiscoveryTests.VehicleIdentRequestIsEightBytes; +var + Frame: TBytes; +begin + Frame := BuildVehicleIdentRequest; + Assert.AreEqual(8, Length(Frame)); + // payload-len field at bytes 4..7 must be 0 + Assert.AreEqual(0, Integer(Frame[4])); + Assert.AreEqual(0, Integer(Frame[5])); + Assert.AreEqual(0, Integer(Frame[6])); + Assert.AreEqual(0, Integer(Frame[7])); +end; + +//------------------------------------------------------------------------------ +// VEHICLE IDENT REQUEST VINPAYLOAD IS17 BYTES +//------------------------------------------------------------------------------ +procedure TDoIPDiscoveryTests.VehicleIdentRequestVINPayloadIs17Bytes; +var + Frame: TBytes; +begin + Frame := BuildVehicleIdentRequestVIN('WVWZZZ8N8Z1234567'); + Assert.AreEqual(8 + 17, Length(Frame)); + // Payload starts at byte 8. + Assert.AreEqual(Byte(Ord('W')), Frame[8]); + Assert.AreEqual(Byte(Ord('7')), Frame[8 + 16]); +end; + +//------------------------------------------------------------------------------ +// VINLENGTH MISMATCH RAISES +//------------------------------------------------------------------------------ +procedure TDoIPDiscoveryTests.VINLengthMismatchRaises; +begin + Assert.WillRaise( + procedure begin BuildVehicleIdentRequestVIN('SHORTVIN'); end, + EOBDDoIPDiscovery); +end; + +//------------------------------------------------------------------------------ +// EIDLENGTH MISMATCH RAISES +//------------------------------------------------------------------------------ +procedure TDoIPDiscoveryTests.EIDLengthMismatchRaises; +begin + Assert.WillRaise( + procedure + begin + BuildVehicleIdentRequestEID(TBytes.Create($AA, $BB, $CC)); + end, + EOBDDoIPDiscovery); +end; + +//------------------------------------------------------------------------------ +// ALIVE CHECK RESPONSE CARRIES SOURCE ADDRESS +//------------------------------------------------------------------------------ +procedure TDoIPDiscoveryTests.AliveCheckResponseCarriesSourceAddress; +var + Frame: TBytes; +begin + Frame := BuildAliveCheckResponse($0E80); + Assert.AreEqual(8 + 2, Length(Frame)); + Assert.AreEqual($0E, Integer(Frame[8])); + Assert.AreEqual($80, Integer(Frame[9])); +end; + +//------------------------------------------------------------------------------ +// PARSE HEADER REJECTS BAD INVERSE +//------------------------------------------------------------------------------ +procedure TDoIPDiscoveryTests.ParseHeaderRejectsBadInverse; +var + Bytes: TBytes; +begin + Bytes := TBytes.Create($02, $00, $00, $01, $00, $00, $00, $00); // bad inverse + Assert.WillRaise( + procedure begin ParseDoIPHeader(Bytes); end, + EOBDDoIPDiscovery); +end; + +//------------------------------------------------------------------------------ +// PARSE HEADER REJECTS TRUNCATED FRAME +//------------------------------------------------------------------------------ +procedure TDoIPDiscoveryTests.ParseHeaderRejectsTruncatedFrame; +var + Bytes: TBytes; +begin + // declared payload-len = 0xFF, but no payload bytes follow + Bytes := TBytes.Create($03, $FC, $00, $04, $00, $00, $00, $FF); + Assert.WillRaise( + procedure begin ParseDoIPHeader(Bytes); end, + EOBDDoIPDiscovery); +end; + +//------------------------------------------------------------------------------ +// VEHICLE ANNOUNCEMENT ROUND TRIPS +//------------------------------------------------------------------------------ +procedure TDoIPDiscoveryTests.VehicleAnnouncementRoundTrips; +var + Payload: TBytes; + Frame: TBytes; + Parsed: TDoIPFrame; + Ann: TDoIPVehicleAnnouncement; + I: Integer; +begin + // Build a synthetic Vehicle Announcement payload (33 bytes — 2019 form). + SetLength(Payload, 33); + for I := 0 to 16 do + Payload[I] := Byte(Ord('A') + (I mod 26)); + Payload[17] := $0E; Payload[18] := $80; // logical address 0x0E80 + for I := 0 to 5 do Payload[19 + I] := $11 + I; // EID + for I := 0 to 5 do Payload[25 + I] := $21 + I; // GID + Payload[31] := $00; + Payload[32] := $10; // SyncStatus + + Frame := BuildDoIPFrame(DOIP_PT_VEHICLE_ANNOUNCE, Payload); + Parsed := ParseDoIPHeader(Frame); + Ann := ParseVehicleAnnouncement(Parsed); + + Assert.AreEqual(17, Length(Ann.VIN)); + Assert.AreEqual('A', Ann.VIN[1]); + Assert.AreEqual(Word($0E80), Ann.LogicalAddress); + Assert.AreEqual(6, Length(Ann.EID)); + Assert.AreEqual($11, Integer(Ann.EID[0])); + Assert.AreEqual($21, Integer(Ann.GID[0])); + Assert.IsTrue(Ann.HasSyncStatus); + Assert.AreEqual($10, Integer(Ann.SyncStatus)); +end; + +//------------------------------------------------------------------------------ +// VEHICLE ANNOUNCEMENT2012 WITHOUT SYNC IS VALID +//------------------------------------------------------------------------------ +procedure TDoIPDiscoveryTests.VehicleAnnouncement2012WithoutSyncIsValid; +var + Payload: TBytes; + Frame: TBytes; + Parsed: TDoIPFrame; + Ann: TDoIPVehicleAnnouncement; + I: Integer; +begin + // 2012-form payload omits the SyncStatus byte (32 bytes total). + SetLength(Payload, 32); + for I := 0 to 16 do Payload[I] := Byte(Ord('A') + (I mod 26)); + Payload[17] := $00; Payload[18] := $10; + for I := 0 to 5 do Payload[19 + I] := $00; + for I := 0 to 5 do Payload[25 + I] := $00; + Payload[31] := $10; + + Frame := BuildDoIPFrame(DOIP_PT_VEHICLE_ANNOUNCE, Payload, + DOIP_PROTOCOL_VERSION_2012); + Parsed := ParseDoIPHeader(Frame); + Ann := ParseVehicleAnnouncement(Parsed); + Assert.IsFalse(Ann.HasSyncStatus); + Assert.AreEqual($10, Integer(Ann.FurtherActionRequired)); +end; + +initialization + TDUnitX.RegisterTestFixture(TDoIPDiscoveryTests); + +end. diff --git a/tests/Tests.Protocol.DoIP.TLS.pas b/tests/Tests.Protocol.DoIP.TLS.pas index c1600fe5..5cea8603 100644 --- a/tests/Tests.Protocol.DoIP.TLS.pas +++ b/tests/Tests.Protocol.DoIP.TLS.pas @@ -57,6 +57,9 @@ TFakeTLSGateway = class property Error: string read FError; end; +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ constructor TFakeTLSGateway.Create(const CertFile, KeyFile: string; const UdsResponse: TBytes); begin @@ -81,6 +84,9 @@ constructor TFakeTLSGateway.Create(const CertFile, KeyFile: string; FServer.Active := True; end; +//------------------------------------------------------------------------------ +// DESTROY +//------------------------------------------------------------------------------ destructor TFakeTLSGateway.Destroy; begin try FServer.Active := False; except end; @@ -90,6 +96,9 @@ destructor TFakeTLSGateway.Destroy; inherited; end; +//------------------------------------------------------------------------------ +// PORT +//------------------------------------------------------------------------------ function TFakeTLSGateway.Port: Word; begin if FServer.Bindings.Count > 0 then @@ -98,17 +107,26 @@ function TFakeTLSGateway.Port: Word; Result := 0; end; +//------------------------------------------------------------------------------ +// WAIT COMPLETION +//------------------------------------------------------------------------------ procedure TFakeTLSGateway.WaitCompletion(TimeoutMs: Cardinal); begin FCompleted.WaitFor(TimeoutMs); end; +//------------------------------------------------------------------------------ +// SSLPWD +//------------------------------------------------------------------------------ procedure TFakeTLSGateway.SSLPwd(var Password: string; const IsWrite: Boolean); begin Password := ''; end; +//------------------------------------------------------------------------------ +// SERVER EXECUTE +//------------------------------------------------------------------------------ procedure TFakeTLSGateway.ServerExecute(AContext: TIdContext); var Header: TIdBytes; @@ -195,6 +213,10 @@ procedure TFakeTLSGateway.ServerExecute(AContext: TIdContext); //============================================================================== // Helpers //============================================================================== + +//------------------------------------------------------------------------------ +// FIXTURE PATH +//------------------------------------------------------------------------------ function FixturePath(const Sub, FileName: string): string; var Candidate, CWD: string; @@ -214,6 +236,10 @@ function FixturePath(const Sub, FileName: string): string; //============================================================================== // Test //============================================================================== + +//------------------------------------------------------------------------------ +// ROUTING ACTIVATION AND DIAGNOSTIC ROUND TRIP OVER TLS +//------------------------------------------------------------------------------ procedure TDoIPTLSTests.RoutingActivationAndDiagnosticRoundTripOverTLS; var CertPath, KeyPath: string; diff --git a/tests/Tests.Protocol.IsoTp.Timing.pas b/tests/Tests.Protocol.IsoTp.Timing.pas new file mode 100644 index 00000000..76a9a08d --- /dev/null +++ b/tests/Tests.Protocol.IsoTp.Timing.pas @@ -0,0 +1,293 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.Protocol.IsoTp.Timing.pas +// CONTENTS : Tests for OBD.Protocol.IsoTp.Timing +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.Protocol.IsoTp.Timing; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TIsoTpTimingTests = class + public + /// + /// Stmin byte zero is zero micros. + /// + [Test] procedure StminByteZeroIsZeroMicros; + /// + /// Stmin byte127 is127 milliseconds. + /// + [Test] procedure StminByte127Is127Milliseconds; + /// + /// Stmin byte f1 is hundred micros. + /// + [Test] procedure StminByteF1IsHundredMicros; + /// + /// Stmin byte f9 is nine hundred micros. + /// + [Test] procedure StminByteF9IsNineHundredMicros; + /// + /// Stmin reserved range raises. + /// + [Test] procedure StminReservedRangeRaises; + /// + /// Encode round trips milliseconds. + /// + [Test] procedure EncodeRoundTripsMilliseconds; + /// + /// Encode round trips microseconds. + /// + [Test] procedure EncodeRoundTripsMicroseconds; + /// + /// Encode rejects unrepresentable. + /// + [Test] procedure EncodeRejectsUnrepresentable; + /// + /// Compliant stream passes. + /// + [Test] procedure CompliantStreamPasses; + /// + /// Undershot gap flags violation. + /// + [Test] procedure UndershotGapFlagsViolation; + /// + /// Block size overrun flags violation. + /// + [Test] procedure BlockSizeOverrunFlagsViolation; + /// + /// Tolerance forgives small undershoot. + /// + [Test] procedure ToleranceForgivesSmallUndershoot; + /// + /// Reset after flow control. + /// + [Test] procedure ResetAfterFlowControl; + end; + +implementation + +uses + System.SysUtils, OBD.Protocol.IsoTp.Timing; + +//------------------------------------------------------------------------------ +// STMIN BYTE ZERO IS ZERO MICROS +//------------------------------------------------------------------------------ +procedure TIsoTpTimingTests.StminByteZeroIsZeroMicros; +begin + Assert.AreEqual(0, DecodeStminMicros($00)); +end; + +//------------------------------------------------------------------------------ +// STMIN BYTE127 IS127 MILLISECONDS +//------------------------------------------------------------------------------ +procedure TIsoTpTimingTests.StminByte127Is127Milliseconds; +begin + Assert.AreEqual(127000, DecodeStminMicros($7F)); +end; + +//------------------------------------------------------------------------------ +// STMIN BYTE F1 IS HUNDRED MICROS +//------------------------------------------------------------------------------ +procedure TIsoTpTimingTests.StminByteF1IsHundredMicros; +begin + Assert.AreEqual(100, DecodeStminMicros($F1)); +end; + +//------------------------------------------------------------------------------ +// STMIN BYTE F9 IS NINE HUNDRED MICROS +//------------------------------------------------------------------------------ +procedure TIsoTpTimingTests.StminByteF9IsNineHundredMicros; +begin + Assert.AreEqual(900, DecodeStminMicros($F9)); +end; + +//------------------------------------------------------------------------------ +// STMIN RESERVED RANGE RAISES +//------------------------------------------------------------------------------ +procedure TIsoTpTimingTests.StminReservedRangeRaises; +begin + Assert.WillRaise(procedure begin DecodeStminMicros($80); end, EOBDIsoTpTiming); + Assert.WillRaise(procedure begin DecodeStminMicros($F0); end, EOBDIsoTpTiming); + Assert.WillRaise(procedure begin DecodeStminMicros($FA); end, EOBDIsoTpTiming); +end; + +//------------------------------------------------------------------------------ +// ENCODE ROUND TRIPS MILLISECONDS +//------------------------------------------------------------------------------ +procedure TIsoTpTimingTests.EncodeRoundTripsMilliseconds; +begin + Assert.AreEqual($05, Integer(EncodeStminMicros(5000))); // 5 ms + Assert.AreEqual($7F, Integer(EncodeStminMicros(127000))); // 127 ms +end; + +//------------------------------------------------------------------------------ +// ENCODE ROUND TRIPS MICROSECONDS +//------------------------------------------------------------------------------ +procedure TIsoTpTimingTests.EncodeRoundTripsMicroseconds; +begin + Assert.AreEqual($F1, Integer(EncodeStminMicros(100))); + Assert.AreEqual($F9, Integer(EncodeStminMicros(900))); +end; + +//------------------------------------------------------------------------------ +// ENCODE REJECTS UNREPRESENTABLE +//------------------------------------------------------------------------------ +procedure TIsoTpTimingTests.EncodeRejectsUnrepresentable; +begin + Assert.WillRaise(procedure begin EncodeStminMicros(150); end, EOBDIsoTpTiming); + Assert.WillRaise(procedure begin EncodeStminMicros(-1); end, EOBDIsoTpTiming); + Assert.WillRaise(procedure begin EncodeStminMicros(200000); end, EOBDIsoTpTiming); +end; + +//------------------------------------------------------------------------------ +// MK OBS +//------------------------------------------------------------------------------ +function MkObs(Kind: TIsoTpFrameKind; T: Int64; + IsTester: Boolean = True): TIsoTpFrameObservation; +begin + Result.Kind := Kind; + Result.TimestampMicros := T; + Result.SenderIsTester := IsTester; +end; + +//------------------------------------------------------------------------------ +// COMPLIANT STREAM PASSES +//------------------------------------------------------------------------------ +procedure TIsoTpTimingTests.CompliantStreamPasses; +var + Checker: TOBDIsoTpTimingChecker; + Frames: TArray; + R: TIsoTpTimingResult; +begin + Checker := TOBDIsoTpTimingChecker.Create; + try + Checker.ApplyFlowControl($05, $00); // 5 ms STmin, unlimited BS + Frames := [ + MkObs(iftFirst, 0), + MkObs(iftFlowControl, 1000, False), + MkObs(iftConsecutive, 2000), + MkObs(iftConsecutive, 7100), // gap 5100 us >= 5000 us + MkObs(iftConsecutive, 12200) // gap 5100 us + ]; + R := Checker.Audit(Frames); + Assert.IsTrue(R.Compliant, 'Compliant stream must not record violations'); + finally + Checker.Free; + end; +end; + +//------------------------------------------------------------------------------ +// UNDERSHOT GAP FLAGS VIOLATION +//------------------------------------------------------------------------------ +procedure TIsoTpTimingTests.UndershotGapFlagsViolation; +var + Checker: TOBDIsoTpTimingChecker; + R: TIsoTpTimingResult; +begin + Checker := TOBDIsoTpTimingChecker.Create; + try + Checker.ApplyFlowControl($05, $00); // 5 ms + Checker.ToleranceMicros := 100; + R := Checker.Audit([ + MkObs(iftFirst, 0), + MkObs(iftFlowControl, 500, False), + MkObs(iftConsecutive, 1000), + MkObs(iftConsecutive, 2000) // gap 1000us, way below 5000us + ]); + Assert.IsFalse(R.Compliant); + Assert.AreEqual(1, Length(R.Violations)); + Assert.AreEqual(Ord(itvIntraGapTooSmall), Ord(R.Violations[0].Kind)); + finally + Checker.Free; + end; +end; + +//------------------------------------------------------------------------------ +// BLOCK SIZE OVERRUN FLAGS VIOLATION +//------------------------------------------------------------------------------ +procedure TIsoTpTimingTests.BlockSizeOverrunFlagsViolation; +var + Checker: TOBDIsoTpTimingChecker; + R: TIsoTpTimingResult; +begin + Checker := TOBDIsoTpTimingChecker.Create; + try + Checker.ApplyFlowControl($00, $02); // STmin=0, BS=2 + R := Checker.Audit([ + MkObs(iftFirst, 0), + MkObs(iftFlowControl, 500, False), + MkObs(iftConsecutive, 1000), + MkObs(iftConsecutive, 2000), + MkObs(iftConsecutive, 3000) // 3rd CF after FC -> overrun + ]); + Assert.IsFalse(R.Compliant); + Assert.IsTrue(Length(R.Violations) >= 1); + finally + Checker.Free; + end; +end; + +//------------------------------------------------------------------------------ +// TOLERANCE FORGIVES SMALL UNDERSHOOT +//------------------------------------------------------------------------------ +procedure TIsoTpTimingTests.ToleranceForgivesSmallUndershoot; +var + Checker: TOBDIsoTpTimingChecker; + R: TIsoTpTimingResult; +begin + Checker := TOBDIsoTpTimingChecker.Create; + try + Checker.ApplyFlowControl($05, $00); + Checker.ToleranceMicros := 500; + R := Checker.Audit([ + MkObs(iftFirst, 0), + MkObs(iftFlowControl, 500, False), + MkObs(iftConsecutive, 1000), + MkObs(iftConsecutive, 5800) // gap 4800us, 200us shy of 5000us — OK + ]); + Assert.IsTrue(R.Compliant); + finally + Checker.Free; + end; +end; + +//------------------------------------------------------------------------------ +// RESET AFTER FLOW CONTROL +//------------------------------------------------------------------------------ +procedure TIsoTpTimingTests.ResetAfterFlowControl; +var + Checker: TOBDIsoTpTimingChecker; + R: TIsoTpTimingResult; +begin + Checker := TOBDIsoTpTimingChecker.Create; + try + Checker.ApplyFlowControl($00, $02); // BS=2 + R := Checker.Audit([ + MkObs(iftFirst, 0), + MkObs(iftFlowControl, 1, False), + MkObs(iftConsecutive, 100), + MkObs(iftConsecutive, 200), + MkObs(iftFlowControl, 300, False), // resets the BS counter + MkObs(iftConsecutive, 400), + MkObs(iftConsecutive, 500) + ]); + Assert.IsTrue(R.Compliant); + finally + Checker.Free; + end; +end; + +initialization + TDUnitX.RegisterTestFixture(TIsoTpTimingTests); + +end. diff --git a/tests/Tests.Protocol.IsoTp.pas b/tests/Tests.Protocol.IsoTp.pas index 9edac6c2..642a8253 100644 --- a/tests/Tests.Protocol.IsoTp.pas +++ b/tests/Tests.Protocol.IsoTp.pas @@ -83,6 +83,9 @@ implementation FRAME_TYPE_FF = $10; FRAME_TYPE_CF = $20; +//------------------------------------------------------------------------------ +// MAKE PROTOCOL +//------------------------------------------------------------------------------ function MakeProtocol: TISO_15765_4_11BIT_500K_OBDProtocol; var EmptyLines: TStringList; @@ -99,6 +102,9 @@ function MakeProtocol: TISO_15765_4_11BIT_500K_OBDProtocol; { TIsoTpFrameTests } +//------------------------------------------------------------------------------ +// SINGLE FRAME_PID0 C_RPM_PARSES AS SF +//------------------------------------------------------------------------------ procedure TIsoTpFrameTests.SingleFrame_PID0C_Rpm_ParsesAsSf; var Proto: TISO_15765_4_11BIT_500K_OBDProtocol; @@ -116,6 +122,9 @@ procedure TIsoTpFrameTests.SingleFrame_PID0C_Rpm_ParsesAsSf; end; end; +//------------------------------------------------------------------------------ +// SINGLE FRAME_DATA LENGTH_MATCHES PCI NIBBLE +//------------------------------------------------------------------------------ procedure TIsoTpFrameTests.SingleFrame_DataLength_MatchesPciNibble; var Proto: TISO_15765_4_11BIT_500K_OBDProtocol; @@ -133,6 +142,9 @@ procedure TIsoTpFrameTests.SingleFrame_DataLength_MatchesPciNibble; end; end; +//------------------------------------------------------------------------------ +// SINGLE FRAME_TX ID_EXTRACTED +//------------------------------------------------------------------------------ procedure TIsoTpFrameTests.SingleFrame_TxId_Extracted; var Proto: TISO_15765_4_11BIT_500K_OBDProtocol; @@ -149,6 +161,9 @@ procedure TIsoTpFrameTests.SingleFrame_TxId_Extracted; end; end; +//------------------------------------------------------------------------------ +// FIRST FRAME_PCI10_RECOGNISED AS FF +//------------------------------------------------------------------------------ procedure TIsoTpFrameTests.FirstFrame_Pci10_RecognisedAsFf; var Proto: TISO_15765_4_11BIT_500K_OBDProtocol; @@ -168,6 +183,9 @@ procedure TIsoTpFrameTests.FirstFrame_Pci10_RecognisedAsFf; end; end; +//------------------------------------------------------------------------------ +// CONSECUTIVE FRAME_PCI21_RECOGNISED AS CF_WITH SEQ INDEX1 +//------------------------------------------------------------------------------ procedure TIsoTpFrameTests.ConsecutiveFrame_Pci21_RecognisedAsCf_WithSeqIndex1; var Proto: TISO_15765_4_11BIT_500K_OBDProtocol; @@ -186,6 +204,9 @@ procedure TIsoTpFrameTests.ConsecutiveFrame_Pci21_RecognisedAsCf_WithSeqIndex1; end; end; +//------------------------------------------------------------------------------ +// FLOW CONTROL FRAME_PCI30_REJECTED AS UNKNOWN +//------------------------------------------------------------------------------ procedure TIsoTpFrameTests.FlowControlFrame_Pci30_RejectedAsUnknown; var Proto: TISO_15765_4_11BIT_500K_OBDProtocol; @@ -204,6 +225,9 @@ procedure TIsoTpFrameTests.FlowControlFrame_Pci30_RejectedAsUnknown; end; end; +//------------------------------------------------------------------------------ +// ODD LENGTH RAW LINE_REJECTED +//------------------------------------------------------------------------------ procedure TIsoTpFrameTests.OddLengthRawLine_Rejected; var Proto: TISO_15765_4_11BIT_500K_OBDProtocol; @@ -220,6 +244,9 @@ procedure TIsoTpFrameTests.OddLengthRawLine_Rejected; end; end; +//------------------------------------------------------------------------------ +// TOO SHORT RAW LINE_REJECTED +//------------------------------------------------------------------------------ procedure TIsoTpFrameTests.TooShortRawLine_Rejected; var Proto: TISO_15765_4_11BIT_500K_OBDProtocol; @@ -238,6 +265,9 @@ procedure TIsoTpFrameTests.TooShortRawLine_Rejected; { TIsoTpInvokeTests } +//------------------------------------------------------------------------------ +// INVOKE_SINGLE SF LINE_PRODUCES ONE MESSAGE WITH EXPECTED DATA +//------------------------------------------------------------------------------ procedure TIsoTpInvokeTests.Invoke_SingleSfLine_ProducesOneMessageWithExpectedData; var Proto: TISO_15765_4_11BIT_500K_OBDProtocol; @@ -266,6 +296,9 @@ procedure TIsoTpInvokeTests.Invoke_SingleSfLine_ProducesOneMessageWithExpectedDa end; end; +//------------------------------------------------------------------------------ +// INVOKE_NON HEX LINE_BUCKETED AS NON OBD +//------------------------------------------------------------------------------ procedure TIsoTpInvokeTests.Invoke_NonHexLine_BucketedAsNonObd; var Proto: TISO_15765_4_11BIT_500K_OBDProtocol; @@ -290,6 +323,9 @@ procedure TIsoTpInvokeTests.Invoke_NonHexLine_BucketedAsNonObd; end; end; +//------------------------------------------------------------------------------ +// INVOKE_VIN RESPONSE_ASSEMBLES ACROSS FF PLUS TWO CFS +//------------------------------------------------------------------------------ procedure TIsoTpInvokeTests.Invoke_VinResponse_AssemblesAcrossFfPlusTwoCfs; var Proto: TISO_15765_4_11BIT_500K_OBDProtocol; @@ -324,6 +360,9 @@ procedure TIsoTpInvokeTests.Invoke_VinResponse_AssemblesAcrossFfPlusTwoCfs; end; end; +//------------------------------------------------------------------------------ +// INVOKE_VIN RESPONSE_OUT OF ORDER CFS_ARE SORTED +//------------------------------------------------------------------------------ procedure TIsoTpInvokeTests.Invoke_VinResponse_OutOfOrderCfs_AreSorted; var Proto: TISO_15765_4_11BIT_500K_OBDProtocol; diff --git a/tests/Tests.Protocol.SecOC.pas b/tests/Tests.Protocol.SecOC.pas new file mode 100644 index 00000000..c5cbbcb3 --- /dev/null +++ b/tests/Tests.Protocol.SecOC.pas @@ -0,0 +1,217 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.Protocol.SecOC.pas +// CONTENTS : Tests for OBD.Protocol.SecOC +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.Protocol.SecOC; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TSecOCTests = class + public + /// + /// Profile3 hmac round trip verifies. + /// + [Test] procedure Profile3HmacRoundTripVerifies; + /// + /// Freshness value changes mac. + /// + [Test] procedure FreshnessValueChangesMac; + /// + /// Payload flip fails verification. + /// + [Test] procedure PayloadFlipFailsVerification; + /// + /// Wrong key fails verification. + /// + [Test] procedure WrongKeyFailsVerification; + /// + /// Configurable truncation length. + /// + [Test] procedure ConfigurableTruncationLength; + /// + /// Profile1 raises until cmac binding ships. + /// + [Test] procedure Profile1RaisesUntilCmacBindingShips; + /// + /// Encode p d u layout matches spec. + /// + [Test] procedure EncodePDULayoutMatchesSpec; + /// + /// Empty key raises. + /// + [Test] procedure EmptyKeyRaises; + end; + +implementation + +uses + System.SysUtils, OBD.Protocol.SecOC; + +//------------------------------------------------------------------------------ +// MAKE PROFILE3 CTX +//------------------------------------------------------------------------------ +function MakeProfile3Ctx(FV: UInt64; const Key: TBytes): TSecOCContext; +begin + Result := Default(TSecOCContext); + Result.Profile := secocProfile3; + Result.KeyId := $0042; + Result.Key := Key; + Result.FreshnessValue := FV; + Result.AuthenticatorBits := 32; +end; + +//------------------------------------------------------------------------------ +// PROFILE3 HMAC ROUND TRIP VERIFIES +//------------------------------------------------------------------------------ +procedure TSecOCTests.Profile3HmacRoundTripVerifies; +var + Ctx: TSecOCContext; + Payload, Mac: TBytes; +begin + Ctx := MakeProfile3Ctx(1, TBytes.Create($AA, $BB, $CC, $DD)); + Payload := TBytes.Create($01, $02, $03, $04); + Mac := SecOCComputeAuthenticator(Ctx, Payload); + Assert.AreEqual(4, Length(Mac)); + Assert.IsTrue(SecOCVerifyAuthenticator(Ctx, Payload, Mac)); +end; + +//------------------------------------------------------------------------------ +// FRESHNESS VALUE CHANGES MAC +//------------------------------------------------------------------------------ +procedure TSecOCTests.FreshnessValueChangesMac; +var + Key, Payload, Mac1, Mac2: TBytes; + C1, C2: TSecOCContext; +begin + Key := TBytes.Create($AA, $BB, $CC, $DD); + Payload := TBytes.Create($01, $02); + C1 := MakeProfile3Ctx(1, Key); + C2 := MakeProfile3Ctx(2, Key); + Mac1 := SecOCComputeAuthenticator(C1, Payload); + Mac2 := SecOCComputeAuthenticator(C2, Payload); + Assert.IsFalse((Length(Mac1) = Length(Mac2)) + and CompareMem(@Mac1[0], @Mac2[0], Length(Mac1)), + 'Different FV must produce different MAC'); +end; + +//------------------------------------------------------------------------------ +// PAYLOAD FLIP FAILS VERIFICATION +//------------------------------------------------------------------------------ +procedure TSecOCTests.PayloadFlipFailsVerification; +var + Ctx: TSecOCContext; + Payload, Mac, Tampered: TBytes; +begin + Ctx := MakeProfile3Ctx(1, TBytes.Create($AA, $BB)); + Payload := TBytes.Create($10, $20, $30); + Mac := SecOCComputeAuthenticator(Ctx, Payload); + Tampered := Copy(Payload); + Tampered[1] := Tampered[1] xor $01; + Assert.IsFalse(SecOCVerifyAuthenticator(Ctx, Tampered, Mac)); +end; + +//------------------------------------------------------------------------------ +// WRONG KEY FAILS VERIFICATION +//------------------------------------------------------------------------------ +procedure TSecOCTests.WrongKeyFailsVerification; +var + Payload, Mac: TBytes; + C1, C2: TSecOCContext; +begin + C1 := MakeProfile3Ctx(1, TBytes.Create($AA, $BB)); + C2 := MakeProfile3Ctx(1, TBytes.Create($CC, $DD)); + Payload := TBytes.Create($01); + Mac := SecOCComputeAuthenticator(C1, Payload); + Assert.IsFalse(SecOCVerifyAuthenticator(C2, Payload, Mac)); +end; + +//------------------------------------------------------------------------------ +// CONFIGURABLE TRUNCATION LENGTH +//------------------------------------------------------------------------------ +procedure TSecOCTests.ConfigurableTruncationLength; +var + Ctx: TSecOCContext; + Mac24, Mac64: TBytes; +begin + Ctx := MakeProfile3Ctx(1, TBytes.Create($01, $02, $03)); + Ctx.AuthenticatorBits := 24; + Mac24 := SecOCComputeAuthenticator(Ctx, TBytes.Create($AA)); + Ctx.AuthenticatorBits := 64; + Mac64 := SecOCComputeAuthenticator(Ctx, TBytes.Create($AA)); + Assert.AreEqual(3, Length(Mac24)); + Assert.AreEqual(8, Length(Mac64)); + // MAC24 must equal the first 3 bytes of MAC64 (deterministic + // truncation contract). + Assert.IsTrue(CompareMem(@Mac24[0], @Mac64[0], 3)); +end; + +//------------------------------------------------------------------------------ +// PROFILE1 RAISES UNTIL CMAC BINDING SHIPS +//------------------------------------------------------------------------------ +procedure TSecOCTests.Profile1RaisesUntilCmacBindingShips; +var + Ctx: TSecOCContext; +begin + Ctx := Default(TSecOCContext); + Ctx.Profile := secocProfile1; + Ctx.Key := TBytes.Create($00, $01, $02, $03, $04, $05, $06, $07, + $08, $09, $0A, $0B, $0C, $0D, $0E, $0F); + Ctx.AuthenticatorBits := 24; + Assert.WillRaise( + procedure + begin SecOCComputeAuthenticator(Ctx, TBytes.Create($AA)); end, + EOBDSecOCAlgorithmNotAvailable); +end; + +//------------------------------------------------------------------------------ +// ENCODE PDULAYOUT MATCHES SPEC +//------------------------------------------------------------------------------ +procedure TSecOCTests.EncodePDULayoutMatchesSpec; +var + Ctx: TSecOCContext; + PDU, Payload, Mac: TBytes; +begin + Ctx := MakeProfile3Ctx($AABBCCDD, TBytes.Create($01)); + Payload := TBytes.Create($DE, $AD); + Mac := TBytes.Create($CA, $FE); + PDU := SecOCEncodePDU(Ctx, Payload, Mac); + // Layout: KeyId(2) + FV(8 for profile 3) + Payload(2) + MAC(2) = 14 + Assert.AreEqual(14, Length(PDU)); + Assert.AreEqual($00, Integer(PDU[0])); + Assert.AreEqual($42, Integer(PDU[1])); + Assert.AreEqual($AA, Integer(PDU[6])); // first FV high byte + Assert.AreEqual($DE, Integer(PDU[10])); // payload starts after FV + Assert.AreEqual($CA, Integer(PDU[12])); // MAC follows payload +end; + +//------------------------------------------------------------------------------ +// EMPTY KEY RAISES +//------------------------------------------------------------------------------ +procedure TSecOCTests.EmptyKeyRaises; +var + Ctx: TSecOCContext; +begin + Ctx := Default(TSecOCContext); + Ctx.Profile := secocProfile3; + Ctx.AuthenticatorBits := 32; + Assert.WillRaise( + procedure begin SecOCComputeAuthenticator(Ctx, TBytes.Create($00)); end, + EOBDSecOC); +end; + +initialization + TDUnitX.RegisterTestFixture(TSecOCTests); + +end. diff --git a/tests/Tests.Protocol.WWHOBD.Readiness.pas b/tests/Tests.Protocol.WWHOBD.Readiness.pas new file mode 100644 index 00000000..c3a143a4 --- /dev/null +++ b/tests/Tests.Protocol.WWHOBD.Readiness.pas @@ -0,0 +1,238 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.Protocol.WWHOBD.Readiness.pas +// CONTENTS : Tests for OBD.Protocol.WWHOBD.Readiness +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.Protocol.WWHOBD.Readiness; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TWWHOBDReadinessTests = class + public + /// + /// Decode rejects too short. + /// + [Test] procedure DecodeRejectsTooShort; + /// + /// M i l bit decodes. + /// + [Test] procedure MILBitDecodes; + /// + /// D t c count from lower seven bits. + /// + [Test] procedure DTCCountFromLowerSevenBits; + /// + /// Continuous misfire supported not complete. + /// + [Test] procedure ContinuousMisfireSupportedNotComplete; + /// + /// Non continuous catalyst complete. + /// + [Test] procedure NonContinuousCatalystComplete; + /// + /// Round trip four byte form. + /// + [Test] procedure RoundTripFourByteForm; + /// + /// Round trip six byte form with diesel monitors. + /// + [Test] procedure RoundTripSixByteFormWithDieselMonitors; + /// + /// All ready true when everything complete. + /// + [Test] procedure AllReadyTrueWhenEverythingComplete; + /// + /// All ready true when unsupported. + /// + [Test] procedure AllReadyTrueWhenUnsupported; + /// + /// Pending monitors lists incomplete. + /// + [Test] procedure PendingMonitorsListsIncomplete; + end; + +implementation + +uses + System.SysUtils, OBD.Protocol.WWHOBD.Readiness; + +//------------------------------------------------------------------------------ +// DECODE REJECTS TOO SHORT +//------------------------------------------------------------------------------ +procedure TWWHOBDReadinessTests.DecodeRejectsTooShort; +begin + Assert.WillRaise( + procedure begin DecodeWWHOBDReadiness(TBytes.Create($00, $00)); end, + EOBDWWHOBDReadiness); +end; + +//------------------------------------------------------------------------------ +// MILBIT DECODES +//------------------------------------------------------------------------------ +procedure TWWHOBDReadinessTests.MILBitDecodes; +var + R: TWWHOBDReadinessSet; +begin + R := DecodeWWHOBDReadiness(TBytes.Create($85, $00, $00, $00)); + Assert.IsTrue(R.MILActive); + Assert.AreEqual(Integer(5), Integer(R.DTCCount)); +end; + +//------------------------------------------------------------------------------ +// DTCCOUNT FROM LOWER SEVEN BITS +//------------------------------------------------------------------------------ +procedure TWWHOBDReadinessTests.DTCCountFromLowerSevenBits; +var + R: TWWHOBDReadinessSet; +begin + R := DecodeWWHOBDReadiness(TBytes.Create($0A, $00, $00, $00)); + Assert.IsFalse(R.MILActive); + Assert.AreEqual(Integer(10), Integer(R.DTCCount)); +end; + +//------------------------------------------------------------------------------ +// CONTINUOUS MISFIRE SUPPORTED NOT COMPLETE +//------------------------------------------------------------------------------ +procedure TWWHOBDReadinessTests.ContinuousMisfireSupportedNotComplete; +var + R: TWWHOBDReadinessSet; +begin + // bit 0 set in low nibble (Misfire supported), bit 4 set in high + // nibble (Misfire NotComplete). + R := DecodeWWHOBDReadiness(TBytes.Create($00, $11, $00, $00)); + Assert.IsTrue(R.Misfire.Supported); + Assert.IsFalse(R.Misfire.Complete); +end; + +//------------------------------------------------------------------------------ +// NON CONTINUOUS CATALYST COMPLETE +//------------------------------------------------------------------------------ +procedure TWWHOBDReadinessTests.NonContinuousCatalystComplete; +var + R: TWWHOBDReadinessSet; +begin + // Catalyst supported (byte 2 bit 0), Catalyst Complete (byte 3 bit 0 NOT set) + R := DecodeWWHOBDReadiness(TBytes.Create($00, $00, $01, $00)); + Assert.IsTrue(R.Catalyst.Supported); + Assert.IsTrue(R.Catalyst.Complete); +end; + +//------------------------------------------------------------------------------ +// ROUND TRIP FOUR BYTE FORM +//------------------------------------------------------------------------------ +procedure TWWHOBDReadinessTests.RoundTripFourByteForm; +var + In_, Out_: TWWHOBDReadinessSet; + Bytes: TBytes; +begin + In_ := Default(TWWHOBDReadinessSet); + In_.MILActive := True; + In_.DTCCount := 7; + In_.Misfire.Supported := True; In_.Misfire.Complete := False; + In_.FuelSystem.Supported := True; In_.FuelSystem.Complete := True; + In_.Comprehensive.Supported := True; In_.Comprehensive.Complete := True; + In_.Catalyst.Supported := True; In_.Catalyst.Complete := False; + In_.OxygenSensor.Supported := True; In_.OxygenSensor.Complete := True; + In_.EvaporativeSystem.Supported := True;In_.EvaporativeSystem.Complete := False; + Bytes := EncodeWWHOBDReadiness(In_); + Assert.AreEqual(4, Length(Bytes)); + Out_ := DecodeWWHOBDReadiness(Bytes); + Assert.IsTrue(Out_.MILActive); + Assert.AreEqual(Integer(7), Integer(Out_.DTCCount)); + Assert.IsTrue(Out_.Misfire.Supported); + Assert.IsFalse(Out_.Misfire.Complete); + Assert.IsTrue(Out_.FuelSystem.Complete); + Assert.IsTrue(Out_.Catalyst.Supported); + Assert.IsFalse(Out_.Catalyst.Complete); + Assert.IsTrue(Out_.EvaporativeSystem.Supported); + Assert.IsFalse(Out_.EvaporativeSystem.Complete); +end; + +//------------------------------------------------------------------------------ +// ROUND TRIP SIX BYTE FORM WITH DIESEL MONITORS +//------------------------------------------------------------------------------ +procedure TWWHOBDReadinessTests.RoundTripSixByteFormWithDieselMonitors; +var + In_, Out_: TWWHOBDReadinessSet; + Bytes: TBytes; +begin + In_ := Default(TWWHOBDReadinessSet); + In_.PMFilter.Supported := True; + In_.PMFilter.Complete := True; + In_.NOxAftertreatment.Supported := True; + In_.NOxAftertreatment.Complete := False; + Bytes := EncodeWWHOBDReadiness(In_); + Assert.AreEqual(6, Length(Bytes), 'Should extend to 6 bytes for diesel set'); + Out_ := DecodeWWHOBDReadiness(Bytes); + Assert.IsTrue(Out_.PMFilter.Supported); + Assert.IsTrue(Out_.PMFilter.Complete); + Assert.IsTrue(Out_.NOxAftertreatment.Supported); + Assert.IsFalse(Out_.NOxAftertreatment.Complete); +end; + +//------------------------------------------------------------------------------ +// ALL READY TRUE WHEN EVERYTHING COMPLETE +//------------------------------------------------------------------------------ +procedure TWWHOBDReadinessTests.AllReadyTrueWhenEverythingComplete; +var + R: TWWHOBDReadinessSet; +begin + R := Default(TWWHOBDReadinessSet); + R.Misfire.Supported := True; R.Misfire.Complete := True; + R.Catalyst.Supported := True; R.Catalyst.Complete := True; + Assert.IsTrue(R.AllReady); +end; + +//------------------------------------------------------------------------------ +// ALL READY TRUE WHEN UNSUPPORTED +//------------------------------------------------------------------------------ +procedure TWWHOBDReadinessTests.AllReadyTrueWhenUnsupported; +var + R: TWWHOBDReadinessSet; +begin + R := Default(TWWHOBDReadinessSet); + // No monitors supported -> AllReady is trivially true. + Assert.IsTrue(R.AllReady); +end; + +//------------------------------------------------------------------------------ +// PENDING MONITORS LISTS INCOMPLETE +//------------------------------------------------------------------------------ +procedure TWWHOBDReadinessTests.PendingMonitorsListsIncomplete; +var + R: TWWHOBDReadinessSet; + Pending: TArray; + S: string; + HasCatalyst, HasMisfire: Boolean; +begin + R := Default(TWWHOBDReadinessSet); + R.Misfire.Supported := True; R.Misfire.Complete := True; + R.Catalyst.Supported := True; R.Catalyst.Complete := False; + R.OxygenSensor.Supported := True; R.OxygenSensor.Complete := False; + Pending := R.PendingMonitors; + Assert.AreEqual(2, Length(Pending)); + HasCatalyst := False; HasMisfire := False; + for S in Pending do + begin + if S = 'Catalyst' then HasCatalyst := True; + if S = 'Misfire' then HasMisfire := True; + end; + Assert.IsTrue(HasCatalyst); + Assert.IsFalse(HasMisfire, 'Complete monitors must not appear'); +end; + +initialization + TDUnitX.RegisterTestFixture(TWWHOBDReadinessTests); + +end. diff --git a/tests/Tests.Protocol.WWHOBD.pas b/tests/Tests.Protocol.WWHOBD.pas new file mode 100644 index 00000000..d0963ed3 --- /dev/null +++ b/tests/Tests.Protocol.WWHOBD.pas @@ -0,0 +1,252 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.Protocol.WWHOBD.pas +// CONTENTS : Tests for OBD.Protocol.WWHOBD +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.Protocol.WWHOBD; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TWWHOBDTests = class + public + /// + /// Dtc round trips through pack unpack. + /// + [Test] procedure DtcRoundTripsThroughPackUnpack; + /// + /// Dtc s p n top bits are preserved. + /// + [Test] procedure DtcSPNTopBitsArePreserved; + /// + /// Dtc oversized s p n raises. + /// + [Test] procedure DtcOversizedSPNRaises; + /// + /// Dtc oversized f m i raises. + /// + [Test] procedure DtcOversizedFMIRaises; + /// + /// Dtc oversized o c raises. + /// + [Test] procedure DtcOversizedOCRaises; + /// + /// Dtc conversion method only zero or one. + /// + [Test] procedure DtcConversionMethodOnlyZeroOrOne; + /// + /// Unpack bad length raises. + /// + [Test] procedure UnpackBadLengthRaises; + /// + /// Unpack stream multiple dtcs. + /// + [Test] procedure UnpackStreamMultipleDtcs; + /// + /// Unpack stream ragged raises. + /// + [Test] procedure UnpackStreamRaggedRaises; + /// + /// Dtc as string formats expected shape. + /// + [Test] procedure DtcAsStringFormatsExpectedShape; + /// + /// Find d i d by v i n returns name. + /// + [Test] procedure FindDIDByVINReturnsName; + /// + /// Find d i d unknown returns hex label. + /// + [Test] procedure FindDIDUnknownReturnsHexLabel; + end; + +implementation + +uses + System.SysUtils, OBD.Protocol.WWHOBD; + +//------------------------------------------------------------------------------ +// DTC ROUND TRIPS THROUGH PACK UNPACK +//------------------------------------------------------------------------------ +procedure TWWHOBDTests.DtcRoundTripsThroughPackUnpack; +var + In_, Out_: TWWHDtc; + Bytes: TBytes; +begin + In_.SPN := 4794; + In_.FMI := 4; + In_.OccurrenceCount := 12; + In_.ConversionMethod := 0; + Bytes := PackWWHDtc(In_); + Assert.AreEqual(4, Length(Bytes)); + Out_ := UnpackWWHDtc(Bytes); + Assert.AreEqual(UInt32(4794), Out_.SPN); + Assert.AreEqual(Integer(4), Integer(Out_.FMI)); + Assert.AreEqual(Integer(12), Integer(Out_.OccurrenceCount)); + Assert.AreEqual(Integer(0), Integer(Out_.ConversionMethod)); +end; + +//------------------------------------------------------------------------------ +// DTC SPNTOP BITS ARE PRESERVED +//------------------------------------------------------------------------------ +procedure TWWHOBDTests.DtcSPNTopBitsArePreserved; +var + In_, Out_: TWWHDtc; +begin + In_.SPN := UInt32($7FFFF); // max 19-bit + In_.FMI := 0; + In_.OccurrenceCount := 0; + In_.ConversionMethod := 1; + Out_ := UnpackWWHDtc(PackWWHDtc(In_)); + Assert.AreEqual(UInt32($7FFFF), Out_.SPN); + Assert.AreEqual(Integer(1), Integer(Out_.ConversionMethod)); +end; + +//------------------------------------------------------------------------------ +// DTC OVERSIZED SPNRAISES +//------------------------------------------------------------------------------ +procedure TWWHOBDTests.DtcOversizedSPNRaises; +var + Dtc: TWWHDtc; +begin + Dtc.SPN := UInt32($80000); // 20-bit + Dtc.FMI := 0; + Dtc.OccurrenceCount := 0; + Dtc.ConversionMethod := 0; + Assert.WillRaise(procedure begin PackWWHDtc(Dtc); end, EOBDWWHOBD); +end; + +//------------------------------------------------------------------------------ +// DTC OVERSIZED FMIRAISES +//------------------------------------------------------------------------------ +procedure TWWHOBDTests.DtcOversizedFMIRaises; +var + Dtc: TWWHDtc; +begin + Dtc.SPN := 100; + Dtc.FMI := $20; + Dtc.OccurrenceCount := 0; + Dtc.ConversionMethod := 0; + Assert.WillRaise(procedure begin PackWWHDtc(Dtc); end, EOBDWWHOBD); +end; + +//------------------------------------------------------------------------------ +// DTC OVERSIZED OCRAISES +//------------------------------------------------------------------------------ +procedure TWWHOBDTests.DtcOversizedOCRaises; +var + Dtc: TWWHDtc; +begin + Dtc.SPN := 100; + Dtc.FMI := 0; + Dtc.OccurrenceCount := $80; + Dtc.ConversionMethod := 0; + Assert.WillRaise(procedure begin PackWWHDtc(Dtc); end, EOBDWWHOBD); +end; + +//------------------------------------------------------------------------------ +// DTC CONVERSION METHOD ONLY ZERO OR ONE +//------------------------------------------------------------------------------ +procedure TWWHOBDTests.DtcConversionMethodOnlyZeroOrOne; +var + Dtc: TWWHDtc; +begin + Dtc.SPN := 100; + Dtc.FMI := 0; + Dtc.OccurrenceCount := 0; + Dtc.ConversionMethod := 2; + Assert.WillRaise(procedure begin PackWWHDtc(Dtc); end, EOBDWWHOBD); +end; + +//------------------------------------------------------------------------------ +// UNPACK BAD LENGTH RAISES +//------------------------------------------------------------------------------ +procedure TWWHOBDTests.UnpackBadLengthRaises; +begin + Assert.WillRaise( + procedure begin UnpackWWHDtc(TBytes.Create($00, $00, $00)); end, + EOBDWWHOBD); +end; + +//------------------------------------------------------------------------------ +// UNPACK STREAM MULTIPLE DTCS +//------------------------------------------------------------------------------ +procedure TWWHOBDTests.UnpackStreamMultipleDtcs; +var + Stream: TBytes; + Out_: TArray; + D1, D2: TWWHDtc; +begin + D1.SPN := 100; D1.FMI := 4; D1.OccurrenceCount := 1; D1.ConversionMethod := 0; + D2.SPN := 4794; D2.FMI := 7; D2.OccurrenceCount := 12; D2.ConversionMethod := 0; + Stream := PackWWHDtc(D1) + PackWWHDtc(D2); + Out_ := UnpackWWHDtcStream(Stream); + Assert.AreEqual(2, Length(Out_)); + Assert.AreEqual(UInt32(100), Out_[0].SPN); + Assert.AreEqual(UInt32(4794), Out_[1].SPN); +end; + +//------------------------------------------------------------------------------ +// UNPACK STREAM RAGGED RAISES +//------------------------------------------------------------------------------ +procedure TWWHOBDTests.UnpackStreamRaggedRaises; +begin + Assert.WillRaise( + procedure + begin + UnpackWWHDtcStream(TBytes.Create($00, $00, $00, $00, $00)); + end, + EOBDWWHOBD); +end; + +//------------------------------------------------------------------------------ +// DTC AS STRING FORMATS EXPECTED SHAPE +//------------------------------------------------------------------------------ +procedure TWWHOBDTests.DtcAsStringFormatsExpectedShape; +var + Dtc: TWWHDtc; +begin + Dtc.SPN := 4794; + Dtc.FMI := 4; + Dtc.OccurrenceCount := 12; + Dtc.ConversionMethod := 0; + Assert.AreEqual('SPN 4794, FMI 4 (CM=0, OC=12)', Dtc.AsString); +end; + +//------------------------------------------------------------------------------ +// FIND DIDBY VINRETURNS NAME +//------------------------------------------------------------------------------ +procedure TWWHOBDTests.FindDIDByVINReturnsName; +var + Info: TWWHOBDDataIdentifier; +begin + Info := FindWWHOBDDataIdentifier(WWHOBD_DID_VIN); + Assert.AreEqual('VIN', Info.Name); + Assert.IsNotEmpty(Info.Description); +end; + +//------------------------------------------------------------------------------ +// FIND DIDUNKNOWN RETURNS HEX LABEL +//------------------------------------------------------------------------------ +procedure TWWHOBDTests.FindDIDUnknownReturnsHexLabel; +var + Info: TWWHOBDDataIdentifier; +begin + Info := FindWWHOBDDataIdentifier($1234); + Assert.IsTrue(Info.Name.Contains('1234')); +end; + +initialization + TDUnitX.RegisterTestFixture(TWWHOBDTests); + +end. diff --git a/tests/Tests.RadioCode.Becker4.pas b/tests/Tests.RadioCode.Becker4.pas index 894b60e6..0b3a1666 100644 --- a/tests/Tests.RadioCode.Becker4.pas +++ b/tests/Tests.RadioCode.Becker4.pas @@ -30,6 +30,9 @@ TBecker4Tests = class [TestCase('Index_19', '0019,0152')] procedure Calculate_ProducesExpectedCode(const Serial, Expected: string); + /// + /// Calculate is deterministic. + /// [Test] procedure Calculate_IsDeterministic; @@ -41,6 +44,9 @@ TBecker4Tests = class [TestCase('Empty', '')] procedure Calculate_RejectsInvalidInput(const Serial: string); + /// + /// Calculate trims whitespace. + /// [Test] procedure Calculate_TrimsWhitespace; end; @@ -53,6 +59,9 @@ implementation { TBecker4Tests } +//------------------------------------------------------------------------------ +// CALCULATE_PRODUCES EXPECTED CODE +//------------------------------------------------------------------------------ procedure TBecker4Tests.Calculate_ProducesExpectedCode( const Serial, Expected: string); var @@ -69,6 +78,9 @@ procedure TBecker4Tests.Calculate_ProducesExpectedCode( end; end; +//------------------------------------------------------------------------------ +// CALCULATE_IS DETERMINISTIC +//------------------------------------------------------------------------------ procedure TBecker4Tests.Calculate_IsDeterministic; var Calc: TOBDRadioCodeBecker4; @@ -87,6 +99,9 @@ procedure TBecker4Tests.Calculate_IsDeterministic; end; end; +//------------------------------------------------------------------------------ +// CALCULATE_REJECTS INVALID INPUT +//------------------------------------------------------------------------------ procedure TBecker4Tests.Calculate_RejectsInvalidInput(const Serial: string); var Calc: TOBDRadioCodeBecker4; @@ -102,6 +117,9 @@ procedure TBecker4Tests.Calculate_RejectsInvalidInput(const Serial: string); end; end; +//------------------------------------------------------------------------------ +// CALCULATE_TRIMS WHITESPACE +//------------------------------------------------------------------------------ procedure TBecker4Tests.Calculate_TrimsWhitespace; var Calc: TOBDRadioCodeBecker4; diff --git a/tests/Tests.RadioCode.Registry.pas b/tests/Tests.RadioCode.Registry.pas new file mode 100644 index 00000000..f602fa2d --- /dev/null +++ b/tests/Tests.RadioCode.Registry.pas @@ -0,0 +1,192 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.RadioCode.Registry.pas +// CONTENTS : Tests for OBD.RadioCode.Registry +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.RadioCode.Registry; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TRadioCodeRegistryTests = class + public + /// + /// Registry has all eight pending brands. + /// + [Test] procedure RegistryHasAllEightPendingBrands; + /// + /// Find is case insensitive. + /// + [Test] procedure FindIsCaseInsensitive; + /// + /// Unknown brand returns nil. + /// + [Test] procedure UnknownBrandReturnsNil; + /// + /// Each pending brand has false data available. + /// + [Test] procedure EachPendingBrandHasFalseDataAvailable; + /// + /// Pending calculator raises on calculate. + /// + [Test] procedure PendingCalculatorRaisesOnCalculate; + /// + /// Pending calculator rejects validate. + /// + [Test] procedure PendingCalculatorRejectsValidate; + /// + /// Pending calculator description is not empty. + /// + [Test] procedure PendingCalculatorDescriptionIsNotEmpty; + /// + /// Register does not duplicate on same key. + /// + [Test] procedure RegisterDoesNotDuplicateOnSameKey; + /// + /// Data notes is not empty for pending. + /// + [Test] procedure DataNotesIsNotEmptyForPending; + end; + +implementation + +uses + System.SysUtils, System.Classes, + OBD.RadioCode, OBD.RadioCode.Registry, OBD.RadioCode.Pending; + +const + ExpectedKeys: array[0..7] of string = ( + 'pioneer', 'kenwood', 'jvc', 'sony', + 'philips', 'grundig', 'panasonic', 'continental_vdo' + ); + +//------------------------------------------------------------------------------ +// REGISTRY HAS ALL EIGHT PENDING BRANDS +//------------------------------------------------------------------------------ +procedure TRadioCodeRegistryTests.RegistryHasAllEightPendingBrands; +var + Key: string; +begin + Assert.IsTrue(TOBDRadioCodeRegistry.Instance.Count >= Length(ExpectedKeys), + 'Registry should hold at least the eight pending brands'); + for Key in ExpectedKeys do + Assert.IsNotNull(TOBDRadioCodeRegistry.Instance.Find(Key), + 'Expected brand not registered: ' + Key); +end; + +//------------------------------------------------------------------------------ +// FIND IS CASE INSENSITIVE +//------------------------------------------------------------------------------ +procedure TRadioCodeRegistryTests.FindIsCaseInsensitive; +begin + Assert.IsNotNull(TOBDRadioCodeRegistry.Instance.Find('PIONEER')); + Assert.IsNotNull(TOBDRadioCodeRegistry.Instance.Find('Pioneer')); + Assert.IsNotNull(TOBDRadioCodeRegistry.Instance.Find('pioneer')); +end; + +//------------------------------------------------------------------------------ +// UNKNOWN BRAND RETURNS NIL +//------------------------------------------------------------------------------ +procedure TRadioCodeRegistryTests.UnknownBrandReturnsNil; +begin + Assert.IsNull(TOBDRadioCodeRegistry.Instance.Find('does_not_exist')); +end; + +//------------------------------------------------------------------------------ +// EACH PENDING BRAND HAS FALSE DATA AVAILABLE +//------------------------------------------------------------------------------ +procedure TRadioCodeRegistryTests.EachPendingBrandHasFalseDataAvailable; +var + Key: string; +begin + for Key in ExpectedKeys do + Assert.IsFalse(TOBDRadioCodeRegistry.Instance.Find(Key).DataAvailable, + 'DataAvailable should be False for ' + Key); +end; + +//------------------------------------------------------------------------------ +// PENDING CALCULATOR RAISES ON CALCULATE +//------------------------------------------------------------------------------ +procedure TRadioCodeRegistryTests.PendingCalculatorRaisesOnCalculate; +var + Calc: IOBDRadioCode; + Output, Err: string; +begin + Calc := TOBDRadioCodeRegistry.Instance.Find('pioneer').CreateCalculator; + Output := ''; + Err := ''; + Assert.WillRaise( + procedure begin Calc.Calculate('1234567', Output, Err); end, + EOBDRadioCodeDataMissing, + 'Pending calculator should raise EOBDRadioCodeDataMissing'); +end; + +//------------------------------------------------------------------------------ +// PENDING CALCULATOR REJECTS VALIDATE +//------------------------------------------------------------------------------ +procedure TRadioCodeRegistryTests.PendingCalculatorRejectsValidate; +var + Calc: IOBDRadioCode; + Err: string; +begin + Calc := TOBDRadioCodeRegistry.Instance.Find('philips').CreateCalculator; + Err := ''; + Assert.IsFalse(Calc.Validate('1234567890', Err), + 'Pending calculator must not claim validation success'); + Assert.IsNotEmpty(Err, + 'Validate failure must produce a human-readable message'); +end; + +//------------------------------------------------------------------------------ +// PENDING CALCULATOR DESCRIPTION IS NOT EMPTY +//------------------------------------------------------------------------------ +procedure TRadioCodeRegistryTests.PendingCalculatorDescriptionIsNotEmpty; +var + Calc: IOBDRadioCode; +begin + Calc := TOBDRadioCodeRegistry.Instance.Find('sony').CreateCalculator; + Assert.IsNotEmpty(Calc.GetDescription); +end; + +//------------------------------------------------------------------------------ +// REGISTER DOES NOT DUPLICATE ON SAME KEY +//------------------------------------------------------------------------------ +procedure TRadioCodeRegistryTests.RegisterDoesNotDuplicateOnSameKey; +var + CountBefore: Integer; + Brand: TOBDRadioCodeBrand; +begin + CountBefore := TOBDRadioCodeRegistry.Instance.Count; + Brand := TOBDRadioCodeBrand.Create('pioneer', 'Pioneer (dup)', False, 'dup', + function: IOBDRadioCode begin Result := nil; end); + TOBDRadioCodeRegistry.Instance.Register(Brand); + // Brand instance is freed by the registry on duplicate; count stays the same. + Assert.AreEqual(CountBefore, TOBDRadioCodeRegistry.Instance.Count); +end; + +//------------------------------------------------------------------------------ +// DATA NOTES IS NOT EMPTY FOR PENDING +//------------------------------------------------------------------------------ +procedure TRadioCodeRegistryTests.DataNotesIsNotEmptyForPending; +var + Key: string; +begin + for Key in ExpectedKeys do + Assert.IsNotEmpty(TOBDRadioCodeRegistry.Instance.Find(Key).DataNotes, + 'DataNotes should explain the gap for ' + Key); +end; + +initialization + TDUnitX.RegisterTestFixture(TRadioCodeRegistryTests); + +end. diff --git a/tests/Tests.RadioCode.Smoke.pas b/tests/Tests.RadioCode.Smoke.pas index 67bf873e..8dd77f99 100644 --- a/tests/Tests.RadioCode.Smoke.pas +++ b/tests/Tests.RadioCode.Smoke.pas @@ -25,48 +25,177 @@ TRadioCodeSmokeTests = class strict private procedure RunInvariants(const CalcClass: TClass); public + /// + /// Acura. + /// [Test] procedure Acura; + /// + /// Alfa romeo. + /// [Test] procedure AlfaRomeo; + /// + /// Alpine. + /// [Test] procedure Alpine; + /// + /// Audi concert. + /// [Test] procedure AudiConcert; + /// + /// Becker. + /// [Test] procedure Becker; + /// + /// Becker4. + /// [Test] procedure Becker4; + /// + /// Becker5. + /// [Test] procedure Becker5; + /// + /// Blaupunkt. + /// [Test] procedure Blaupunkt; + /// + /// B m w. + /// [Test] procedure BMW; + /// + /// Chrysler. + /// [Test] procedure Chrysler; + /// + /// Citroen. + /// [Test] procedure Citroen; + /// + /// Clarion. + /// [Test] procedure Clarion; + /// + /// Fiat daiichi. + /// [Test] procedure FiatDaiichi; + /// + /// Fiat v p. + /// [Test] procedure FiatVP; + /// + /// Ford. + /// [Test] procedure Ford; + /// + /// Ford v. + /// [Test] procedure FordV; + /// + /// G m. + /// [Test] procedure GM; + /// + /// Honda. + /// [Test] procedure Honda; + /// + /// Hyundai. + /// [Test] procedure Hyundai; + /// + /// Infiniti. + /// [Test] procedure Infiniti; + /// + /// Jaguar. + /// [Test] procedure Jaguar; + /// + /// Land rover. + /// [Test] procedure LandRover; + /// + /// Lexus. + /// [Test] procedure Lexus; + /// + /// Maserati. + /// [Test] procedure Maserati; + /// + /// Mazda. + /// [Test] procedure Mazda; + /// + /// Mercedes. + /// [Test] procedure Mercedes; + /// + /// Mini. + /// [Test] procedure Mini; + /// + /// Mitsubishi. + /// [Test] procedure Mitsubishi; + /// + /// Nissan. + /// [Test] procedure Nissan; + /// + /// Opel. + /// [Test] procedure Opel; + /// + /// Peugeot. + /// [Test] procedure Peugeot; + /// + /// Porsche. + /// [Test] procedure Porsche; + /// + /// Renault. + /// [Test] procedure Renault; + /// + /// Saab. + /// [Test] procedure Saab; + /// + /// S e a t. + /// [Test] procedure SEAT; + /// + /// Skoda. + /// [Test] procedure Skoda; + /// + /// Smart. + /// [Test] procedure Smart; + /// + /// Subaru. + /// [Test] procedure Subaru; + /// + /// Suzuki. + /// [Test] procedure Suzuki; + /// + /// Toyota. + /// [Test] procedure Toyota; + /// + /// Visteon. + /// [Test] procedure Visteon; + /// + /// Volvo. + /// [Test] procedure Volvo; + /// + /// V w. + /// [Test] procedure VW; end; @@ -123,6 +252,9 @@ TOBDRadioCodeClass = class of TOBDRadioCode; { TRadioCodeSmokeTests } +//------------------------------------------------------------------------------ +// RUN INVARIANTS +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.RunInvariants(const CalcClass: TClass); var Calc: TOBDRadioCode; @@ -157,48 +289,219 @@ procedure TRadioCodeSmokeTests.RunInvariants(const CalcClass: TClass); end; end; +//------------------------------------------------------------------------------ +// ACURA +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Acura; begin RunInvariants(TOBDRadioCodeAcuraAdvanced); end; + +//------------------------------------------------------------------------------ +// ALFA ROMEO +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.AlfaRomeo; begin RunInvariants(TOBDRadioCodeAlfaRomeoAdvanced); end; + +//------------------------------------------------------------------------------ +// ALPINE +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Alpine; begin RunInvariants(TOBDRadioCodeAlpineAdvanced); end; + +//------------------------------------------------------------------------------ +// AUDI CONCERT +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.AudiConcert; begin RunInvariants(TOBDRadioCodeAudiConcertAdvanced); end; + +//------------------------------------------------------------------------------ +// BECKER +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Becker; begin RunInvariants(TOBDRadioCodeBeckerAdvanced); end; + +//------------------------------------------------------------------------------ +// BECKER4 +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Becker4; begin RunInvariants(TOBDRadioCodeBecker4); end; + +//------------------------------------------------------------------------------ +// BECKER5 +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Becker5; begin RunInvariants(TOBDRadioCodeBecker5); end; + +//------------------------------------------------------------------------------ +// BLAUPUNKT +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Blaupunkt; begin RunInvariants(TOBDRadioCodeBlaupunktAdvanced); end; + +//------------------------------------------------------------------------------ +// BMW +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.BMW; begin RunInvariants(TOBDRadioCodeBMWAdvanced); end; + +//------------------------------------------------------------------------------ +// CHRYSLER +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Chrysler; begin RunInvariants(TOBDRadioCodeChryslerAdvanced); end; + +//------------------------------------------------------------------------------ +// CITROEN +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Citroen; begin RunInvariants(TOBDRadioCodeCitroenAdvanced); end; + +//------------------------------------------------------------------------------ +// CLARION +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Clarion; begin RunInvariants(TOBDRadioCodeClarionAdvanced); end; + +//------------------------------------------------------------------------------ +// FIAT DAIICHI +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.FiatDaiichi; begin RunInvariants(TOBDRadioCodeFiatDaiichiAdvanced); end; + +//------------------------------------------------------------------------------ +// FIAT VP +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.FiatVP; begin RunInvariants(TOBDRadioCodeFiatVPAdvanced); end; + +//------------------------------------------------------------------------------ +// FORD +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Ford; begin RunInvariants(TOBDRadioCodeFordAdvanced); end; + +//------------------------------------------------------------------------------ +// FORD V +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.FordV; begin RunInvariants(TOBDRadioCodeFordV); end; + +//------------------------------------------------------------------------------ +// GM +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.GM; begin RunInvariants(TOBDRadioCodeGMAdvanced); end; + +//------------------------------------------------------------------------------ +// HONDA +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Honda; begin RunInvariants(TOBDRadioCodeHondaAdvanced); end; + +//------------------------------------------------------------------------------ +// HYUNDAI +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Hyundai; begin RunInvariants(TOBDRadioCodeHyundaiAdvanced); end; + +//------------------------------------------------------------------------------ +// INFINITI +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Infiniti; begin RunInvariants(TOBDRadioCodeInfinitiAdvanced); end; + +//------------------------------------------------------------------------------ +// JAGUAR +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Jaguar; begin RunInvariants(TOBDRadioCodeJaguarAdvanced); end; + +//------------------------------------------------------------------------------ +// LAND ROVER +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.LandRover; begin RunInvariants(TOBDRadioCodeLandRoverAdvanced); end; + +//------------------------------------------------------------------------------ +// LEXUS +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Lexus; begin RunInvariants(TOBDRadioCodeLexusAdvanced); end; + +//------------------------------------------------------------------------------ +// MASERATI +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Maserati; begin RunInvariants(TOBDRadioCodeMaseratiAdvanced); end; + +//------------------------------------------------------------------------------ +// MAZDA +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Mazda; begin RunInvariants(TOBDRadioCodeMazdaAdvanced); end; + +//------------------------------------------------------------------------------ +// MERCEDES +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Mercedes; begin RunInvariants(TOBDRadioCodeMercedesAdvanced); end; + +//------------------------------------------------------------------------------ +// MINI +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Mini; begin RunInvariants(TOBDRadioCodeMiniAdvanced); end; + +//------------------------------------------------------------------------------ +// MITSUBISHI +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Mitsubishi; begin RunInvariants(TOBDRadioCodeMitsubishiAdvanced); end; + +//------------------------------------------------------------------------------ +// NISSAN +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Nissan; begin RunInvariants(TOBDRadioCodeNissanAdvanced); end; + +//------------------------------------------------------------------------------ +// OPEL +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Opel; begin RunInvariants(TOBDRadioCodeOpelAdvanced); end; + +//------------------------------------------------------------------------------ +// PEUGEOT +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Peugeot; begin RunInvariants(TOBDRadioCodePeugeotAdvanced); end; + +//------------------------------------------------------------------------------ +// PORSCHE +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Porsche; begin RunInvariants(TOBDRadioCodePorscheAdvanced); end; + +//------------------------------------------------------------------------------ +// RENAULT +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Renault; begin RunInvariants(TOBDRadioCodeRenaultAdvanced); end; + +//------------------------------------------------------------------------------ +// SAAB +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Saab; begin RunInvariants(TOBDRadioCodeSaabAdvanced); end; + +//------------------------------------------------------------------------------ +// SEAT +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.SEAT; begin RunInvariants(TOBDRadioCodeSEATAdvanced); end; + +//------------------------------------------------------------------------------ +// SKODA +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Skoda; begin RunInvariants(TOBDRadioCodeSkodaAdvanced); end; + +//------------------------------------------------------------------------------ +// SMART +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Smart; begin RunInvariants(TOBDRadioCodeSmartAdvanced); end; + +//------------------------------------------------------------------------------ +// SUBARU +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Subaru; begin RunInvariants(TOBDRadioCodeSubaruAdvanced); end; + +//------------------------------------------------------------------------------ +// SUZUKI +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Suzuki; begin RunInvariants(TOBDRadioCodeSuzukiAdvanced); end; + +//------------------------------------------------------------------------------ +// TOYOTA +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Toyota; begin RunInvariants(TOBDRadioCodeToyotaAdvanced); end; + +//------------------------------------------------------------------------------ +// VISTEON +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Visteon; begin RunInvariants(TOBDRadioCodeVisteonAdvanced); end; + +//------------------------------------------------------------------------------ +// VOLVO +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.Volvo; begin RunInvariants(TOBDRadioCodeVolvoAdvanced); end; + +//------------------------------------------------------------------------------ +// VW +//------------------------------------------------------------------------------ procedure TRadioCodeSmokeTests.VW; begin RunInvariants(TOBDRadioCodeVWAdvanced); end; initialization diff --git a/tests/Tests.RadioCode.VinResolver.pas b/tests/Tests.RadioCode.VinResolver.pas new file mode 100644 index 00000000..7381672f --- /dev/null +++ b/tests/Tests.RadioCode.VinResolver.pas @@ -0,0 +1,199 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.RadioCode.VinResolver.pas +// CONTENTS : Tests for OBD.RadioCode.VinResolver +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.RadioCode.VinResolver; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TVinResolverTests = class + public + /// + /// V w audi mercedes b m w are registered as data available. + /// + [Test] procedure VW_Audi_Mercedes_BMW_AreRegisteredAsDataAvailable; + /// + /// V w pre2007 european v i n resolves to early variant. + /// + [Test] procedure VWPre2007EuropeanVINResolvesToEarlyVariant; + /// + /// V w post2013 european v i n resolves to later variant. + /// + [Test] procedure VWPost2013EuropeanVINResolvesToLaterVariant; + /// + /// Unknown brand gives null calculator and note. + /// + [Test] procedure UnknownBrandGivesNullCalculatorAndNote; + /// + /// Invalid v i n falls back to overrides and defaults. + /// + [Test] procedure InvalidVINFallsBackToOverridesAndDefaults; + /// + /// Region override takes precedence over v i n region. + /// + [Test] procedure RegionOverrideTakesPrecedenceOverVINRegion; + /// + /// Resolution note populated when falling back to default. + /// + [Test] procedure ResolutionNotePopulatedWhenFallingBackToDefault; + end; + +implementation + +uses + System.SysUtils, + OBD.RadioCode, OBD.RadioCode.Registry, OBD.RadioCode.Variants, + OBD.RadioCode.VinResolver; + +//------------------------------------------------------------------------------ +// VW_AUDI_MERCEDES_BMW_ARE REGISTERED AS DATA AVAILABLE +//------------------------------------------------------------------------------ +procedure TVinResolverTests.VW_Audi_Mercedes_BMW_AreRegisteredAsDataAvailable; +const + Keys: array[0..3] of string = ('vw', 'audi', 'mercedes', 'bmw'); +var + Key: string; + Brand: TOBDRadioCodeBrand; +begin + for Key in Keys do + begin + Brand := TOBDRadioCodeRegistry.Instance.Find(Key); + Assert.IsNotNull(Brand, 'Brand should be registered: ' + Key); + Assert.IsTrue(Brand.DataAvailable, 'DataAvailable should be True for ' + Key); + Assert.IsTrue(Brand.Variants.VariantCount > 0, + 'Variant manager should be seeded for ' + Key); + end; +end; + +//------------------------------------------------------------------------------ +// VWPRE2007 EUROPEAN VINRESOLVES TO EARLY VARIANT +//------------------------------------------------------------------------------ +procedure TVinResolverTests.VWPre2007EuropeanVINResolvesToEarlyVariant; +var + Ctx: TRadioCodeResolveContext; + Res: TRadioCodeResolveResult; +begin + Ctx := Default(TRadioCodeResolveContext); + Ctx.BrandKey := 'vw'; + Ctx.VIN := ''; + Ctx.ModelYearOverride := 2002; + Ctx.RegionOverride := rcrEurope; + Res := ResolveCalculator(Ctx); + Assert.IsNotNull(Res.Variant, 'Should resolve a variant'); + Assert.IsTrue(Res.Variant.YearRange.StartYear <= 2002, + 'Selected variant must include 2002'); + Assert.IsTrue(Res.Variant.YearRange.EndYear >= 2002, + 'Selected variant must include 2002'); + Assert.IsTrue(Res.DataAvailable, 'VW must be data-available'); +end; + +//------------------------------------------------------------------------------ +// VWPOST2013 EUROPEAN VINRESOLVES TO LATER VARIANT +//------------------------------------------------------------------------------ +procedure TVinResolverTests.VWPost2013EuropeanVINResolvesToLaterVariant; +var + Ctx: TRadioCodeResolveContext; + Res: TRadioCodeResolveResult; +begin + Ctx := Default(TRadioCodeResolveContext); + Ctx.BrandKey := 'vw'; + Ctx.ModelYearOverride := 2018; + Ctx.RegionOverride := rcrEurope; + Res := ResolveCalculator(Ctx); + Assert.IsNotNull(Res.Variant); + Assert.IsTrue(Res.Variant.YearRange.EndYear >= 2018, + 'Selected variant must include 2018'); +end; + +//------------------------------------------------------------------------------ +// UNKNOWN BRAND GIVES NULL CALCULATOR AND NOTE +//------------------------------------------------------------------------------ +procedure TVinResolverTests.UnknownBrandGivesNullCalculatorAndNote; +var + Ctx: TRadioCodeResolveContext; + Res: TRadioCodeResolveResult; +begin + Ctx := Default(TRadioCodeResolveContext); + Ctx.BrandKey := 'no_such_brand'; + Res := ResolveCalculator(Ctx); + Assert.IsNull(Res.Brand); + Assert.IsNull(Res.Calculator); + Assert.IsNotEmpty(Res.ResolutionNotes); +end; + +//------------------------------------------------------------------------------ +// INVALID VINFALLS BACK TO OVERRIDES AND DEFAULTS +//------------------------------------------------------------------------------ +procedure TVinResolverTests.InvalidVINFallsBackToOverridesAndDefaults; +var + Ctx: TRadioCodeResolveContext; + Res: TRadioCodeResolveResult; +begin + Ctx := Default(TRadioCodeResolveContext); + Ctx.BrandKey := 'vw'; + Ctx.VIN := 'NOT-A-VIN'; + Ctx.ModelYearOverride := 2008; + Ctx.RegionOverride := rcrEurope; + Res := ResolveCalculator(Ctx); + Assert.IsNotNull(Res.Calculator, + 'Invalid VIN must not block resolution when overrides are supplied'); + Assert.IsNotNull(Res.Variant); +end; + +//------------------------------------------------------------------------------ +// REGION OVERRIDE TAKES PRECEDENCE OVER VINREGION +//------------------------------------------------------------------------------ +procedure TVinResolverTests.RegionOverrideTakesPrecedenceOverVINRegion; +var + Ctx: TRadioCodeResolveContext; + Res: TRadioCodeResolveResult; +begin + Ctx := Default(TRadioCodeResolveContext); + Ctx.BrandKey := 'vw'; + Ctx.RegionOverride := rcrNorthAmerica; + Ctx.ModelYearOverride := 2005; + Res := ResolveCalculator(Ctx); + Assert.IsNotNull(Res.Variant); + Assert.IsTrue( + (Res.Variant.Region = rcrNorthAmerica) or (Res.Variant.IsDefault), + 'Override should pick a NA variant or fall back to default with note'); +end; + +//------------------------------------------------------------------------------ +// RESOLUTION NOTE POPULATED WHEN FALLING BACK TO DEFAULT +//------------------------------------------------------------------------------ +procedure TVinResolverTests.ResolutionNotePopulatedWhenFallingBackToDefault; +var + Ctx: TRadioCodeResolveContext; + Res: TRadioCodeResolveResult; +begin + Ctx := Default(TRadioCodeResolveContext); + Ctx.BrandKey := 'mercedes'; + // Year far in the future to force no exact match. + Ctx.ModelYearOverride := 2099; + Ctx.RegionOverride := rcrSouthAmerica; + Res := ResolveCalculator(Ctx); + Assert.IsNotNull(Res.Variant); + // We don't assert ResolutionNotes is non-empty unconditionally — + // FindBestMatch may still choose the default variant with no note — + // but if no exact match was found we expect a falling-back note. + if Res.Variant.IsDefault then + Assert.Pass('Resolved to default; note may or may not be set'); +end; + +initialization + TDUnitX.RegisterTestFixture(TVinResolverTests); + +end. diff --git a/tests/Tests.SecureSettings.pas b/tests/Tests.SecureSettings.pas index 3ba4b0ed..b6238e2a 100644 --- a/tests/Tests.SecureSettings.pas +++ b/tests/Tests.SecureSettings.pas @@ -33,12 +33,18 @@ implementation System.SysUtils, System.IOUtils, System.Classes, OBD.SecureSettings; +//------------------------------------------------------------------------------ +// SCRATCH PATH +//------------------------------------------------------------------------------ function ScratchPath(const Suffix: string): string; begin Result := TPath.Combine(TPath.GetTempPath, Format('obdsec-%d-%d-%s', [GetCurrentProcessId, GetTickCount, Suffix])); end; +//------------------------------------------------------------------------------ +// DPAPI_ROUND TRIP_PRESERVES BYTES +//------------------------------------------------------------------------------ procedure TSecureSettingsTests.DPAPI_RoundTrip_PreservesBytes; var Plain, Cipher, Decoded: TBytes; @@ -55,6 +61,9 @@ procedure TSecureSettingsTests.DPAPI_RoundTrip_PreservesBytes; TEncoding.UTF8.GetString(Decoded)); end; +//------------------------------------------------------------------------------ +// DPAPI_EMPTY INPUT_ROUND TRIPS CLEANLY +//------------------------------------------------------------------------------ procedure TSecureSettingsTests.DPAPI_EmptyInput_RoundTripsCleanly; var Empty, Cipher, Decoded: TBytes; @@ -67,6 +76,9 @@ procedure TSecureSettingsTests.DPAPI_EmptyInput_RoundTripsCleanly; Assert.AreEqual(0, Length(Decoded)); end; +//------------------------------------------------------------------------------ +// DPAPI_DECRYPT OF TAMPERED CIPHERTEXT_RAISES +//------------------------------------------------------------------------------ procedure TSecureSettingsTests.DPAPI_DecryptOfTamperedCiphertext_Raises; var Plain, Cipher: TBytes; @@ -81,6 +93,9 @@ procedure TSecureSettingsTests.DPAPI_DecryptOfTamperedCiphertext_Raises; EOBDDpapiError); end; +//------------------------------------------------------------------------------ +// SETTINGS_WRITE READ ROUND TRIP +//------------------------------------------------------------------------------ procedure TSecureSettingsTests.Settings_WriteReadRoundTrip; var Path: string; @@ -127,6 +142,9 @@ procedure TSecureSettingsTests.Settings_WriteReadRoundTrip; end; end; +//------------------------------------------------------------------------------ +// SETTINGS_MISSING KEY RETURNS DEFAULT +//------------------------------------------------------------------------------ procedure TSecureSettingsTests.Settings_MissingKeyReturnsDefault; var Path: string; @@ -144,6 +162,9 @@ procedure TSecureSettingsTests.Settings_MissingKeyReturnsDefault; end; end; +//------------------------------------------------------------------------------ +// SETTINGS_READ STRING_FALLS BACK ON GARBAGE CIPHER +//------------------------------------------------------------------------------ procedure TSecureSettingsTests.Settings_ReadString_FallsBackOnGarbageCipher; var Path: string; @@ -175,6 +196,9 @@ procedure TSecureSettingsTests.Settings_ReadString_FallsBackOnGarbageCipher; end; end; +//------------------------------------------------------------------------------ +// SETTINGS_DELETE KEY CLEARS VALUE +//------------------------------------------------------------------------------ procedure TSecureSettingsTests.Settings_DeleteKeyClearsValue; var Path: string; diff --git a/tests/Tests.Security.AttemptCounter.pas b/tests/Tests.Security.AttemptCounter.pas index 5eeff9a8..ea155453 100644 --- a/tests/Tests.Security.AttemptCounter.pas +++ b/tests/Tests.Security.AttemptCounter.pas @@ -26,6 +26,9 @@ implementation uses System.SysUtils, OBD.Security.AttemptCounter; +//------------------------------------------------------------------------------ +// FREE ATTEMPTS ARE NOT LOCKED OUT +//------------------------------------------------------------------------------ procedure TAttemptCounterTests.FreeAttemptsAreNotLockedOut; var C: TOBDAttemptCounter; @@ -44,6 +47,9 @@ procedure TAttemptCounterTests.FreeAttemptsAreNotLockedOut; end; end; +//------------------------------------------------------------------------------ +// EXTRA FAILURE LOCKS OUT +//------------------------------------------------------------------------------ procedure TAttemptCounterTests.ExtraFailureLocksOut; var C: TOBDAttemptCounter; @@ -62,6 +68,9 @@ procedure TAttemptCounterTests.ExtraFailureLocksOut; end; end; +//------------------------------------------------------------------------------ +// SUCCESS RESETS THE COUNTER +//------------------------------------------------------------------------------ procedure TAttemptCounterTests.SuccessResetsTheCounter; var C: TOBDAttemptCounter; @@ -83,6 +92,9 @@ procedure TAttemptCounterTests.SuccessResetsTheCounter; end; end; +//------------------------------------------------------------------------------ +// RESET CLEARS STATE +//------------------------------------------------------------------------------ procedure TAttemptCounterTests.ResetClearsState; var C: TOBDAttemptCounter; @@ -102,6 +114,9 @@ procedure TAttemptCounterTests.ResetClearsState; end; end; +//------------------------------------------------------------------------------ +// LOCKOUT CAPS AT MAX LOCKOUT SECONDS +//------------------------------------------------------------------------------ procedure TAttemptCounterTests.LockoutCapsAtMaxLockoutSeconds; var C: TOBDAttemptCounter; @@ -126,6 +141,9 @@ procedure TAttemptCounterTests.LockoutCapsAtMaxLockoutSeconds; end; end; +//------------------------------------------------------------------------------ +// DISTINCT IDENTITIES DO NOT INTERFERE +//------------------------------------------------------------------------------ procedure TAttemptCounterTests.DistinctIdentitiesDoNotInterfere; var C: TOBDAttemptCounter; diff --git a/tests/Tests.Security.Nonce.pas b/tests/Tests.Security.Nonce.pas index a5a8a345..88082c5f 100644 --- a/tests/Tests.Security.Nonce.pas +++ b/tests/Tests.Security.Nonce.pas @@ -29,6 +29,9 @@ implementation uses System.SysUtils, System.Classes, OBD.Security.Nonce; +//------------------------------------------------------------------------------ +// ISSUE PRODUCES UNIQUE VALUES +//------------------------------------------------------------------------------ procedure TNonceVaultTests.IssueProducesUniqueValues; var V: TOBDNonceVault; @@ -48,6 +51,9 @@ procedure TNonceVaultTests.IssueProducesUniqueValues; end; end; +//------------------------------------------------------------------------------ +// IS VALID TRUE IMMEDIATELY AFTER ISSUE +//------------------------------------------------------------------------------ procedure TNonceVaultTests.IsValidTrueImmediatelyAfterIssue; var V: TOBDNonceVault; N: string; begin @@ -60,6 +66,9 @@ procedure TNonceVaultTests.IsValidTrueImmediatelyAfterIssue; end; end; +//------------------------------------------------------------------------------ +// REDEEM SUCCEEDS THEN REJECTS REPLAY +//------------------------------------------------------------------------------ procedure TNonceVaultTests.RedeemSucceedsThenRejectsReplay; var V: TOBDNonceVault; N: string; begin @@ -73,8 +82,12 @@ procedure TNonceVaultTests.RedeemSucceedsThenRejectsReplay; end; end; +//------------------------------------------------------------------------------ +// REDEEM UNKNOWN RAISES +//------------------------------------------------------------------------------ procedure TNonceVaultTests.RedeemUnknownRaises; -var V: TOBDNonceVault; +var + V: TOBDNonceVault; begin V := TOBDNonceVault.Create(30); try @@ -85,6 +98,9 @@ procedure TNonceVaultTests.RedeemUnknownRaises; end; end; +//------------------------------------------------------------------------------ +// REDEEM AFTER TTL RAISES EXPIRED +//------------------------------------------------------------------------------ procedure TNonceVaultTests.RedeemAfterTtlRaisesExpired; var V: TOBDNonceVault; N: string; begin @@ -98,6 +114,9 @@ procedure TNonceVaultTests.RedeemAfterTtlRaisesExpired; end; end; +//------------------------------------------------------------------------------ +// RESET CLEARS ACTIVE AND USED +//------------------------------------------------------------------------------ procedure TNonceVaultTests.ResetClearsActiveAndUsed; var V: TOBDNonceVault; N: string; begin @@ -115,6 +134,9 @@ procedure TNonceVaultTests.ResetClearsActiveAndUsed; end; end; +//------------------------------------------------------------------------------ +// NONCE LENGTH RESPECTED +//------------------------------------------------------------------------------ procedure TNonceVaultTests.NonceLengthRespected; var V: TOBDNonceVault; N: string; begin @@ -127,6 +149,9 @@ procedure TNonceVaultTests.NonceLengthRespected; end; end; +//------------------------------------------------------------------------------ +// CONSTRUCT REJECTS ZERO TTL +//------------------------------------------------------------------------------ procedure TNonceVaultTests.ConstructRejectsZeroTtl; begin Assert.WillRaise( @@ -134,6 +159,9 @@ procedure TNonceVaultTests.ConstructRejectsZeroTtl; EArgumentException); end; +//------------------------------------------------------------------------------ +// CONSTRUCT REJECTS TINY NONCE +//------------------------------------------------------------------------------ procedure TNonceVaultTests.ConstructRejectsTinyNonce; begin Assert.WillRaise( diff --git a/tests/Tests.Service.Decoders.pas b/tests/Tests.Service.Decoders.pas index b145e7d0..266a7431 100644 --- a/tests/Tests.Service.Decoders.pas +++ b/tests/Tests.Service.Decoders.pas @@ -94,6 +94,9 @@ implementation { TServiceResponseDispatchTests } +//------------------------------------------------------------------------------ +// DISPATCH_POSITIVE SERVICE RESPONSE_EXTRACTS SERVICE AND PID +//------------------------------------------------------------------------------ procedure TServiceResponseDispatchTests.Dispatch_PositiveServiceResponse_ExtractsServiceAndPID; var Decoder: TOBDServiceResponseDecoder; @@ -118,6 +121,9 @@ procedure TServiceResponseDispatchTests.Dispatch_PositiveServiceResponse_Extract end; end; +//------------------------------------------------------------------------------ +// DISPATCH_NEGATIVE RESPONSE_FLAGS ERROR +//------------------------------------------------------------------------------ procedure TServiceResponseDispatchTests.Dispatch_NegativeResponse_FlagsError; var Decoder: TOBDServiceResponseDecoder; @@ -137,6 +143,9 @@ procedure TServiceResponseDispatchTests.Dispatch_NegativeResponse_FlagsError; end; end; +//------------------------------------------------------------------------------ +// DISPATCH_TOO SHORT RESPONSE_FAILS +//------------------------------------------------------------------------------ procedure TServiceResponseDispatchTests.Dispatch_TooShortResponse_Fails; var Decoder: TOBDServiceResponseDecoder; @@ -153,6 +162,9 @@ procedure TServiceResponseDispatchTests.Dispatch_TooShortResponse_Fails; end; end; +//------------------------------------------------------------------------------ +// DISPATCH_SERVICE03_HAS NO PID_BUT CARRIES DATA +//------------------------------------------------------------------------------ procedure TServiceResponseDispatchTests.Dispatch_Service03_HasNoPID_ButCarriesData; var Decoder: TOBDServiceResponseDecoder; @@ -178,6 +190,9 @@ procedure TServiceResponseDispatchTests.Dispatch_Service03_HasNoPID_ButCarriesDa { TPidDecoderTests } +//------------------------------------------------------------------------------ +// PERCENTAGE_KNOWN INPUTS +//------------------------------------------------------------------------------ procedure TPidDecoderTests.Percentage_KnownInputs( const Byte0: Integer; const ExpectedPercent: Double); var @@ -193,6 +208,9 @@ procedure TPidDecoderTests.Percentage_KnownInputs( end; end; +//------------------------------------------------------------------------------ +// PERCENTAGE_EMPTY DATA_FAILS +//------------------------------------------------------------------------------ procedure TPidDecoderTests.Percentage_EmptyData_Fails; var D: TOBDPercentageDecoder; @@ -208,6 +226,9 @@ procedure TPidDecoderTests.Percentage_EmptyData_Fails; end; end; +//------------------------------------------------------------------------------ +// TEMPERATURE_KNOWN INPUTS +//------------------------------------------------------------------------------ procedure TPidDecoderTests.Temperature_KnownInputs( const Byte0, ExpectedC: Integer); var @@ -223,6 +244,9 @@ procedure TPidDecoderTests.Temperature_KnownInputs( end; end; +//------------------------------------------------------------------------------ +// FUEL TRIM_KNOWN INPUTS +//------------------------------------------------------------------------------ procedure TPidDecoderTests.FuelTrim_KnownInputs( const Byte0: Integer; const ExpectedPercent: Double); var @@ -238,6 +262,9 @@ procedure TPidDecoderTests.FuelTrim_KnownInputs( end; end; +//------------------------------------------------------------------------------ +// FUEL PRESSURE_KNOWN INPUTS +//------------------------------------------------------------------------------ procedure TPidDecoderTests.FuelPressure_KnownInputs( const Byte0, ExpectedKPa: Integer); var @@ -253,6 +280,9 @@ procedure TPidDecoderTests.FuelPressure_KnownInputs( end; end; +//------------------------------------------------------------------------------ +// ENGINE RPM_DECODES KNOWN TWO BYTE VALUE +//------------------------------------------------------------------------------ procedure TPidDecoderTests.EngineRPM_DecodesKnownTwoByteValue; var D: TOBDEngineRPMDecoder; @@ -268,6 +298,9 @@ procedure TPidDecoderTests.EngineRPM_DecodesKnownTwoByteValue; end; end; +//------------------------------------------------------------------------------ +// ENGINE RPM_TOO SHORT_FAILS +//------------------------------------------------------------------------------ procedure TPidDecoderTests.EngineRPM_TooShort_Fails; var D: TOBDEngineRPMDecoder; @@ -281,6 +314,9 @@ procedure TPidDecoderTests.EngineRPM_TooShort_Fails; end; end; +//------------------------------------------------------------------------------ +// TIMING ADVANCE_KNOWN INPUTS +//------------------------------------------------------------------------------ procedure TPidDecoderTests.TimingAdvance_KnownInputs( const Byte0: Integer; const ExpectedDeg: Double); var @@ -296,6 +332,9 @@ procedure TPidDecoderTests.TimingAdvance_KnownInputs( end; end; +//------------------------------------------------------------------------------ +// MASS AIR FLOW_DECODES KNOWN TWO BYTE VALUE +//------------------------------------------------------------------------------ procedure TPidDecoderTests.MassAirFlow_DecodesKnownTwoByteValue; var D: TOBDMassAirFlowRateDecoder; diff --git a/tests/Tests.Service.Encoders.pas b/tests/Tests.Service.Encoders.pas index 03909ace..a2f77f77 100644 --- a/tests/Tests.Service.Encoders.pas +++ b/tests/Tests.Service.Encoders.pas @@ -45,6 +45,9 @@ implementation System.SysUtils, OBD.Request.Encoders; +//------------------------------------------------------------------------------ +// HEX TO BYTE +//------------------------------------------------------------------------------ function HexToByte(const S: string): Byte; begin Result := StrToInt('$' + S); @@ -52,6 +55,9 @@ function HexToByte(const S: string): Byte; { TServiceEncoderTests } +//------------------------------------------------------------------------------ +// ENCODE SERVICE REQUEST_NO DATA_PRODUCES EXPECTED HEX +//------------------------------------------------------------------------------ procedure TServiceEncoderTests.EncodeServiceRequest_NoData_ProducesExpectedHex( const Service, PID, Expected: string); var @@ -81,6 +87,9 @@ procedure TServiceEncoderTests.EncodeServiceRequest_NoData_ProducesExpectedHex( end; end; +//------------------------------------------------------------------------------ +// ENCODE SERVICE REQUEST_WITH DATA_APPENDS HEX +//------------------------------------------------------------------------------ procedure TServiceEncoderTests.EncodeServiceRequest_WithData_AppendsHex; var Encoder: TOBDService01RequestEncoder; @@ -96,6 +105,9 @@ procedure TServiceEncoderTests.EncodeServiceRequest_WithData_AppendsHex; end; end; +//------------------------------------------------------------------------------ +// ENCODE SERVICE REQUEST_WITH EMPTY DATA_MATCHES NO DATA FORM +//------------------------------------------------------------------------------ procedure TServiceEncoderTests.EncodeServiceRequest_WithEmptyData_MatchesNoDataForm; var Encoder: TOBDService01RequestEncoder; diff --git a/tests/Tests.Service.Recorder.pas b/tests/Tests.Service.Recorder.pas index 178e88bb..a9d179a3 100644 --- a/tests/Tests.Service.Recorder.pas +++ b/tests/Tests.Service.Recorder.pas @@ -25,12 +25,18 @@ implementation System.SysUtils, System.IOUtils, OBD.Service.Recorder; +//------------------------------------------------------------------------------ +// SCRATCH FILE +//------------------------------------------------------------------------------ function ScratchFile(const Name: string): string; begin Result := TPath.Combine(TPath.GetTempPath, Format('obdrec-%d-%s', [GetCurrentProcessId, Name])); end; +//------------------------------------------------------------------------------ +// RECORDER CAPTURES ENTRIES IN ORDER +//------------------------------------------------------------------------------ procedure TRecorderTests.RecorderCapturesEntriesInOrder; var R: TOBDRecorder; @@ -55,6 +61,9 @@ procedure TRecorderTests.RecorderCapturesEntriesInOrder; end; end; +//------------------------------------------------------------------------------ +// SAVE AND LOAD ROUND TRIP +//------------------------------------------------------------------------------ procedure TRecorderTests.SaveAndLoadRoundTrip; var R: TOBDRecorder; @@ -90,6 +99,9 @@ procedure TRecorderTests.SaveAndLoadRoundTrip; end; end; +//------------------------------------------------------------------------------ +// REPLAY FIRES ENTRIES WITH SPEED ZERO +//------------------------------------------------------------------------------ procedure TRecorderTests.ReplayFiresEntriesWithSpeedZero; var R: TOBDRecorder; @@ -117,7 +129,9 @@ procedure TRecorderTests.ReplayFiresEntriesWithSpeedZero; Replay.LoadFromFile(Path); Replay.Speed := 0; // skip sleeps Replay.OnEntry := procedure(Sender: TObject; const Entry: TOBDRecordedEntry) - begin Inc(Fired); end; + begin + Inc(Fired); + end; Replay.Run; Assert.AreEqual(3, Fired); finally @@ -126,6 +140,9 @@ procedure TRecorderTests.ReplayFiresEntriesWithSpeedZero; end; end; +//------------------------------------------------------------------------------ +// ESCAPES TAB AND NEWLINE IN TEXT +//------------------------------------------------------------------------------ procedure TRecorderTests.EscapesTabAndNewlineInText; var R: TOBDRecorder; diff --git a/tests/Tests.Service06.Mode06.pas b/tests/Tests.Service06.Mode06.pas new file mode 100644 index 00000000..44f8c97e --- /dev/null +++ b/tests/Tests.Service06.Mode06.pas @@ -0,0 +1,235 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.Service06.Mode06.pas +// CONTENTS : Tests for OBD.Service06.Mode06 +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.Service06.Mode06; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TMode06Tests = class + public + /// + /// Request is two bytes. + /// + [Test] procedure RequestIsTwoBytes; + /// + /// Parse single record response. + /// + [Test] procedure ParseSingleRecordResponse; + /// + /// Parse multi record response. + /// + [Test] procedure ParseMultiRecordResponse; + /// + /// Parse rejects bad service id. + /// + [Test] procedure ParseRejectsBadServiceId; + /// + /// Parse rejects ragged payload. + /// + [Test] procedure ParseRejectsRaggedPayload; + /// + /// Parse rejects too short. + /// + [Test] procedure ParseRejectsTooShort; + /// + /// Record passed test when within limits. + /// + [Test] procedure RecordPassedTestWhenWithinLimits; + /// + /// Record failed test when above max. + /// + [Test] procedure RecordFailedTestWhenAboveMax; + /// + /// Scale factor returns unit scale. + /// + [Test] procedure ScaleFactorReturnsUnitScale; + /// + /// Find u c s i d returns unknown default. + /// + [Test] procedure FindUCSIDReturnsUnknownDefault; + /// + /// Find o b d m i d returns catalyst name. + /// + [Test] procedure FindOBDMIDReturnsCatalystName; + /// + /// Find test i d returns catalyst name. + /// + [Test] procedure FindTestIDReturnsCatalystName; + end; + +implementation + +uses + System.SysUtils, OBD.Service06.Mode06; + +//------------------------------------------------------------------------------ +// REQUEST IS TWO BYTES +//------------------------------------------------------------------------------ +procedure TMode06Tests.RequestIsTwoBytes; +var + Req: TBytes; +begin + Req := BuildMode06Request($21); + Assert.AreEqual(2, Length(Req)); + Assert.AreEqual($46, Integer(Req[0])); + Assert.AreEqual($21, Integer(Req[1])); +end; + +//------------------------------------------------------------------------------ +// PARSE SINGLE RECORD RESPONSE +//------------------------------------------------------------------------------ +procedure TMode06Tests.ParseSingleRecordResponse; +var + Resp: TOBDMode06Response; + Bytes: TBytes; +begin + // 46 21 [TID=81 UCSID=24 Val=0064 Min=0050 Max=0078] + Bytes := TBytes.Create($46, $21, $81, $24, $00, $64, $00, $50, $00, $78); + Resp := ParseMode06Response(Bytes); + Assert.AreEqual($21, Integer(Resp.OBDMID)); + Assert.AreEqual(1, Length(Resp.Records)); + Assert.AreEqual($81, Integer(Resp.Records[0].TestId)); + Assert.AreEqual($24, Integer(Resp.Records[0].UnitsAndScalingId)); + Assert.AreEqual(Word($0064), Resp.Records[0].TestValue); + Assert.AreEqual(Word($0050), Resp.Records[0].MinLimit); + Assert.AreEqual(Word($0078), Resp.Records[0].MaxLimit); +end; + +//------------------------------------------------------------------------------ +// PARSE MULTI RECORD RESPONSE +//------------------------------------------------------------------------------ +procedure TMode06Tests.ParseMultiRecordResponse; +var + Resp: TOBDMode06Response; + Bytes: TBytes; +begin + // 46 31 + 2 records of 8 bytes each + Bytes := TBytes.Create( + $46, $31, + $A1, $24, $00, $50, $00, $30, $00, $80, + $A2, $24, $00, $40, $00, $20, $00, $90); + Resp := ParseMode06Response(Bytes); + Assert.AreEqual(2, Length(Resp.Records)); + Assert.AreEqual($A1, Integer(Resp.Records[0].TestId)); + Assert.AreEqual($A2, Integer(Resp.Records[1].TestId)); +end; + +//------------------------------------------------------------------------------ +// PARSE REJECTS BAD SERVICE ID +//------------------------------------------------------------------------------ +procedure TMode06Tests.ParseRejectsBadServiceId; +var + Bytes: TBytes; +begin + Bytes := TBytes.Create($00, $21); + Assert.WillRaise( + procedure begin ParseMode06Response(Bytes); end, EOBDMode06); +end; + +//------------------------------------------------------------------------------ +// PARSE REJECTS RAGGED PAYLOAD +//------------------------------------------------------------------------------ +procedure TMode06Tests.ParseRejectsRaggedPayload; +var + Bytes: TBytes; +begin + // 46 21 + 5 bytes (not multiple of 8) + Bytes := TBytes.Create($46, $21, $81, $24, $00, $64, $00); + Assert.WillRaise( + procedure begin ParseMode06Response(Bytes); end, EOBDMode06); +end; + +//------------------------------------------------------------------------------ +// PARSE REJECTS TOO SHORT +//------------------------------------------------------------------------------ +procedure TMode06Tests.ParseRejectsTooShort; +begin + Assert.WillRaise( + procedure begin ParseMode06Response(TBytes.Create($46)); end, EOBDMode06); +end; + +//------------------------------------------------------------------------------ +// RECORD PASSED TEST WHEN WITHIN LIMITS +//------------------------------------------------------------------------------ +procedure TMode06Tests.RecordPassedTestWhenWithinLimits; +var + R: TOBDMode06TestRecord; +begin + R.TestValue := 100; + R.MinLimit := 80; + R.MaxLimit := 120; + Assert.IsTrue(R.PassedTest); +end; + +//------------------------------------------------------------------------------ +// RECORD FAILED TEST WHEN ABOVE MAX +//------------------------------------------------------------------------------ +procedure TMode06Tests.RecordFailedTestWhenAboveMax; +var + R: TOBDMode06TestRecord; +begin + R.TestValue := 200; + R.MinLimit := 80; + R.MaxLimit := 120; + Assert.IsFalse(R.PassedTest); +end; + +//------------------------------------------------------------------------------ +// SCALE FACTOR RETURNS UNIT SCALE +//------------------------------------------------------------------------------ +procedure TMode06Tests.ScaleFactorReturnsUnitScale; +var + R: TOBDMode06TestRecord; +begin + R.UnitsAndScalingId := $24; // °C, scale 1.0 + Assert.AreEqual(Single(1.0), R.ScaleFactor, 0.0001); + Assert.AreEqual('°C', R.UnitName); +end; + +//------------------------------------------------------------------------------ +// FIND UCSIDRETURNS UNKNOWN DEFAULT +//------------------------------------------------------------------------------ +procedure TMode06Tests.FindUCSIDReturnsUnknownDefault; +var + Info: TOBDMode06UnitInfo; +begin + Info := FindMode06Unit($FF); + Assert.AreEqual(Single(1.0), Info.Scale, 0.0001); + Assert.IsTrue(Info.Description.Contains('Unknown')); +end; + +//------------------------------------------------------------------------------ +// FIND OBDMIDRETURNS CATALYST NAME +//------------------------------------------------------------------------------ +procedure TMode06Tests.FindOBDMIDReturnsCatalystName; +begin + Assert.AreEqual('Catalyst Monitor Bank 1', FindMode06OBDMIDName($21)); + Assert.AreEqual('Catalyst Monitor Bank 2', FindMode06OBDMIDName($22)); +end; + +//------------------------------------------------------------------------------ +// FIND TEST IDRETURNS CATALYST NAME +//------------------------------------------------------------------------------ +procedure TMode06Tests.FindTestIDReturnsCatalystName; +begin + Assert.IsTrue(FindMode06TestIdName($81).Contains('Catalyst')); + Assert.IsTrue(FindMode06TestIdName($A1).Contains('EGR')); +end; + +initialization + TDUnitX.RegisterTestFixture(TMode06Tests); + +end. diff --git a/tests/Tests.Service09.Calibration.pas b/tests/Tests.Service09.Calibration.pas new file mode 100644 index 00000000..1026530b --- /dev/null +++ b/tests/Tests.Service09.Calibration.pas @@ -0,0 +1,246 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.Service09.Calibration.pas +// CONTENTS : Tests for OBD.Service09.Calibration +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.Service09.Calibration; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TCalibrationTests = class + public + /// + /// Cal i d request is two bytes. + /// + [Test] procedure CalIDRequestIsTwoBytes; + /// + /// C v n request is two bytes. + /// + [Test] procedure CVNRequestIsTwoBytes; + /// + /// Decode cal i d strips trailing nulls. + /// + [Test] procedure DecodeCalIDStripsTrailingNulls; + /// + /// Decode multi block cal i ds. + /// + [Test] procedure DecodeMultiBlockCalIDs; + /// + /// Decode cal i d rejects bad service id. + /// + [Test] procedure DecodeCalIDRejectsBadServiceId; + /// + /// Decode cal i d rejects truncated. + /// + [Test] procedure DecodeCalIDRejectsTruncated; + /// + /// Decode c v n big endian four bytes. + /// + [Test] procedure DecodeCVNBigEndianFourBytes; + /// + /// Decode multi block c v ns. + /// + [Test] procedure DecodeMultiBlockCVNs; + /// + /// Decode c v n rejects bad p i d. + /// + [Test] procedure DecodeCVNRejectsBadPID; + /// + /// Format c v n upper hex. + /// + [Test] procedure FormatCVNUpperHex; + /// + /// Pair matches positionally. + /// + [Test] procedure PairMatchesPositionally; + /// + /// Pair mismatched lengths raises. + /// + [Test] procedure PairMismatchedLengthsRaises; + end; + +implementation + +uses + System.SysUtils, OBD.Service09.Calibration; + +//------------------------------------------------------------------------------ +// CAL IDREQUEST IS TWO BYTES +//------------------------------------------------------------------------------ +procedure TCalibrationTests.CalIDRequestIsTwoBytes; +var + R: TBytes; +begin + R := EncodeCalIDRequest; + Assert.AreEqual(2, Length(R)); + Assert.AreEqual($09, Integer(R[0])); + Assert.AreEqual($04, Integer(R[1])); +end; + +//------------------------------------------------------------------------------ +// CVNREQUEST IS TWO BYTES +//------------------------------------------------------------------------------ +procedure TCalibrationTests.CVNRequestIsTwoBytes; +var + R: TBytes; +begin + R := EncodeCVNRequest; + Assert.AreEqual($09, Integer(R[0])); + Assert.AreEqual($06, Integer(R[1])); +end; + +//------------------------------------------------------------------------------ +// DECODE CAL IDSTRIPS TRAILING NULLS +//------------------------------------------------------------------------------ +procedure TCalibrationTests.DecodeCalIDStripsTrailingNulls; +var + Resp: TBytes; + IDs: TArray; +begin + Resp := TBytes.Create($49, $04, $01, + Ord('A'), Ord('B'), Ord('C'), Ord('1'), Ord('2'), Ord('3'), + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + IDs := DecodeCalIDResponse(Resp); + Assert.AreEqual(1, Length(IDs)); + Assert.AreEqual('ABC123', IDs[0].CalID); +end; + +//------------------------------------------------------------------------------ +// DECODE MULTI BLOCK CAL IDS +//------------------------------------------------------------------------------ +procedure TCalibrationTests.DecodeMultiBlockCalIDs; +var + Resp: TBytes; + IDs: TArray; + I: Integer; +begin + SetLength(Resp, 3 + 2 * 16); + Resp[0] := $49; Resp[1] := $04; Resp[2] := 2; + for I := 0 to 5 do Resp[3 + I] := Ord('1') + I; + for I := 0 to 5 do Resp[3 + 16 + I] := Ord('A') + I; + IDs := DecodeCalIDResponse(Resp); + Assert.AreEqual(2, Length(IDs)); + Assert.AreEqual('123456', IDs[0].CalID); + Assert.AreEqual('ABCDEF', IDs[1].CalID); +end; + +//------------------------------------------------------------------------------ +// DECODE CAL IDREJECTS BAD SERVICE ID +//------------------------------------------------------------------------------ +procedure TCalibrationTests.DecodeCalIDRejectsBadServiceId; +begin + Assert.WillRaise( + procedure begin DecodeCalIDResponse(TBytes.Create($00, $04, $01)); end, + EOBDCalibration); +end; + +//------------------------------------------------------------------------------ +// DECODE CAL IDREJECTS TRUNCATED +//------------------------------------------------------------------------------ +procedure TCalibrationTests.DecodeCalIDRejectsTruncated; +begin + // Declares 1 block of 16 bytes but only 4 follow + Assert.WillRaise( + procedure + begin + DecodeCalIDResponse(TBytes.Create($49, $04, $01, $41, $42, $43, $44)); + end, + EOBDCalibration); +end; + +//------------------------------------------------------------------------------ +// DECODE CVNBIG ENDIAN FOUR BYTES +//------------------------------------------------------------------------------ +procedure TCalibrationTests.DecodeCVNBigEndianFourBytes; +var + VNs: TArray; +begin + VNs := DecodeCVNResponse(TBytes.Create($49, $06, $01, $DE, $AD, $BE, $EF)); + Assert.AreEqual(1, Length(VNs)); + Assert.AreEqual(UInt32($DEADBEEF), VNs[0].CVN); +end; + +//------------------------------------------------------------------------------ +// DECODE MULTI BLOCK CVNS +//------------------------------------------------------------------------------ +procedure TCalibrationTests.DecodeMultiBlockCVNs; +var + VNs: TArray; +begin + VNs := DecodeCVNResponse(TBytes.Create( + $49, $06, $02, + $11, $22, $33, $44, + $55, $66, $77, $88)); + Assert.AreEqual(2, Length(VNs)); + Assert.AreEqual(UInt32($11223344), VNs[0].CVN); + Assert.AreEqual(UInt32($55667788), VNs[1].CVN); +end; + +//------------------------------------------------------------------------------ +// DECODE CVNREJECTS BAD PID +//------------------------------------------------------------------------------ +procedure TCalibrationTests.DecodeCVNRejectsBadPID; +begin + Assert.WillRaise( + procedure begin DecodeCVNResponse(TBytes.Create($49, $07, $01)); end, + EOBDCalibration); +end; + +//------------------------------------------------------------------------------ +// FORMAT CVNUPPER HEX +//------------------------------------------------------------------------------ +procedure TCalibrationTests.FormatCVNUpperHex; +begin + Assert.AreEqual('DEADBEEF', FormatCVN(UInt32($DEADBEEF))); + Assert.AreEqual('00000001', FormatCVN(UInt32(1))); +end; + +//------------------------------------------------------------------------------ +// PAIR MATCHES POSITIONALLY +//------------------------------------------------------------------------------ +procedure TCalibrationTests.PairMatchesPositionally; +var + IDs: TArray; + VNs: TArray; + Pairs: TArray; +begin + SetLength(IDs, 2); + IDs[0].CalID := 'CAL1'; IDs[1].CalID := 'CAL2'; + SetLength(VNs, 2); + VNs[0].CVN := $AA; VNs[1].CVN := $BB; + Pairs := PairCalIDsAndCVNs(IDs, VNs); + Assert.AreEqual('CAL1', Pairs[0].CalID); + Assert.AreEqual(UInt32($AA), Pairs[0].CVN); + Assert.AreEqual('CAL2', Pairs[1].CalID); +end; + +//------------------------------------------------------------------------------ +// PAIR MISMATCHED LENGTHS RAISES +//------------------------------------------------------------------------------ +procedure TCalibrationTests.PairMismatchedLengthsRaises; +var + IDs: TArray; + VNs: TArray; +begin + SetLength(IDs, 2); + SetLength(VNs, 1); + Assert.WillRaise( + procedure begin PairCalIDsAndCVNs(IDs, VNs); end, + EOBDCalibration); +end; + +initialization + TDUnitX.RegisterTestFixture(TCalibrationTests); + +end. diff --git a/tests/Tests.Smoke.pas b/tests/Tests.Smoke.pas index 12145ab9..de53ebcc 100644 --- a/tests/Tests.Smoke.pas +++ b/tests/Tests.Smoke.pas @@ -33,11 +33,17 @@ implementation { TSmokeTests } +//------------------------------------------------------------------------------ +// HARNESS IS ALIVE +//------------------------------------------------------------------------------ procedure TSmokeTests.HarnessIsAlive; begin Assert.Pass; end; +//------------------------------------------------------------------------------ +// INTEGER ADDITION WORKS +//------------------------------------------------------------------------------ procedure TSmokeTests.IntegerAdditionWorks(const A, B, Expected: Integer); begin Assert.AreEqual(Expected, A + B); diff --git a/tests/Tests.Tachograph.Signature.pas b/tests/Tests.Tachograph.Signature.pas new file mode 100644 index 00000000..22c9427d --- /dev/null +++ b/tests/Tests.Tachograph.Signature.pas @@ -0,0 +1,289 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.Tachograph.Signature.pas +// CONTENTS : Tests for OBD.Tachograph.Signature +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.Tachograph.Signature; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TTachographSignatureTests = class + public + /// + /// Parses empty file as zero blocks. + /// + [Test] procedure ParsesEmptyFileAsZeroBlocks; + /// + /// Parses single t l v. + /// + [Test] procedure ParsesSingleTLV; + /// + /// Truncated declared length raises. + /// + [Test] procedure TruncatedDeclaredLengthRaises; + /// + /// Verify chain succeeds when verifiers pass. + /// + [Test] procedure VerifyChainSucceedsWhenVerifiersPass; + /// + /// Verify chain fails when signature block missing. + /// + [Test] procedure VerifyChainFailsWhenSignatureBlockMissing; + /// + /// Verify chain fails when verifier returns false. + /// + [Test] procedure VerifyChainFailsWhenVerifierReturnsFalse; + /// + /// Verify chain fails when verifier not configured. + /// + [Test] procedure VerifyChainFailsWhenVerifierNotConfigured; + end; + +implementation + +uses + System.SysUtils, + OBD.ECU.Signature, + OBD.Tachograph.Signature; + +type + TConfigurableVerifier = class(TInterfacedObject, IFirmwareSignatureVerifier) + private + FAccept: Boolean; + public + constructor Create(Accept: Boolean); + function AlgorithmName: string; + function Verify(const Firmware, Signature: TBytes): Boolean; + end; + +//------------------------------------------------------------------------------ +// CREATE +//------------------------------------------------------------------------------ +constructor TConfigurableVerifier.Create(Accept: Boolean); +begin + inherited Create; + FAccept := Accept; +end; + +//------------------------------------------------------------------------------ +// ALGORITHM NAME +//------------------------------------------------------------------------------ +function TConfigurableVerifier.AlgorithmName: string; +begin + if FAccept then Result := 'TEST-ACCEPT' else Result := 'TEST-REJECT'; +end; + +//------------------------------------------------------------------------------ +// VERIFY +//------------------------------------------------------------------------------ +function TConfigurableVerifier.Verify(const Firmware, Signature: TBytes): Boolean; +begin + Result := FAccept; +end; + +//------------------------------------------------------------------------------ +// MAKE BLOCK +//------------------------------------------------------------------------------ +function MakeBlock(TagHi, TagLo: Byte; const Body: TBytes): TBytes; +var + Out_: TBytes; +begin + SetLength(Out_, 4 + Length(Body)); + Out_[0] := TagHi; + Out_[1] := TagLo; + Out_[2] := Byte(Length(Body) shr 8); + Out_[3] := Byte(Length(Body) and $FF); + if Length(Body) > 0 then + Move(Body[0], Out_[4], Length(Body)); + Result := Out_; +end; + +//------------------------------------------------------------------------------ +// CONCAT BYTES +//------------------------------------------------------------------------------ +function ConcatBytes(const Parts: array of TBytes): TBytes; +var + Total, I, Off: Integer; +begin + Total := 0; + for I := 0 to High(Parts) do Inc(Total, Length(Parts[I])); + SetLength(Result, Total); + Off := 0; + for I := 0 to High(Parts) do + if Length(Parts[I]) > 0 then + begin + Move(Parts[I][0], Result[Off], Length(Parts[I])); + Inc(Off, Length(Parts[I])); + end; +end; + +//------------------------------------------------------------------------------ +// PARSES EMPTY FILE AS ZERO BLOCKS +//------------------------------------------------------------------------------ +procedure TTachographSignatureTests.ParsesEmptyFileAsZeroBlocks; +var + Checker: TOBDTachographSignatureChecker; + Blocks: TArray; +begin + Checker := TOBDTachographSignatureChecker.Create; + try + Blocks := Checker.ParseBlocks(nil); + Assert.AreEqual(0, Length(Blocks)); + finally + Checker.Free; + end; +end; + +//------------------------------------------------------------------------------ +// PARSES SINGLE TLV +//------------------------------------------------------------------------------ +procedure TTachographSignatureTests.ParsesSingleTLV; +var + Checker: TOBDTachographSignatureChecker; + Blocks: TArray; + Body: TBytes; +begin + Checker := TOBDTachographSignatureChecker.Create; + try + Body := TBytes.Create($AA, $BB, $CC); + Blocks := Checker.ParseBlocks(MakeBlock($05, $01, Body)); + Assert.AreEqual(1, Length(Blocks)); + Assert.AreEqual(Word($0501), Blocks[0].Tag); + Assert.AreEqual(3, Blocks[0].Length); + Assert.AreEqual($AA, Integer(Blocks[0].Data[0])); + finally + Checker.Free; + end; +end; + +//------------------------------------------------------------------------------ +// TRUNCATED DECLARED LENGTH RAISES +//------------------------------------------------------------------------------ +procedure TTachographSignatureTests.TruncatedDeclaredLengthRaises; +var + Checker: TOBDTachographSignatureChecker; + Bad: TBytes; +begin + Checker := TOBDTachographSignatureChecker.Create; + try + // tag=0x0501, declared len=0x0010, but no body bytes follow + Bad := TBytes.Create($05, $01, $00, $10); + Assert.WillRaise( + procedure begin Checker.ParseBlocks(Bad); end, + EOBDTachographSignature); + finally + Checker.Free; + end; +end; + +//------------------------------------------------------------------------------ +// VERIFY CHAIN SUCCEEDS WHEN VERIFIERS PASS +//------------------------------------------------------------------------------ +procedure TTachographSignatureTests.VerifyChainSucceedsWhenVerifiersPass; +var + Checker: TOBDTachographSignatureChecker; + File_: TBytes; + R: TDDDChainResult; +begin + File_ := ConcatBytes([ + MakeBlock($05, $01, TBytes.Create($AA)), // overview + MakeBlock($05, $0E, TBytes.Create($01)), // signature + MakeBlock($05, $04, TBytes.Create($BB)), // activities + MakeBlock($05, $0E, TBytes.Create($02))]); // signature + + Checker := TOBDTachographSignatureChecker.Create; + try + Checker.SetCardVerifier(TConfigurableVerifier.Create(True)); + Checker.SetVUVerifier(TConfigurableVerifier.Create(True)); + R := Checker.VerifyChain(File_); + Assert.IsTrue(R.Verified, R.Reason); + Assert.AreEqual(2, R.SignaturesVerified); + finally + Checker.Free; + end; +end; + +//------------------------------------------------------------------------------ +// VERIFY CHAIN FAILS WHEN SIGNATURE BLOCK MISSING +//------------------------------------------------------------------------------ +procedure TTachographSignatureTests.VerifyChainFailsWhenSignatureBlockMissing; +var + Checker: TOBDTachographSignatureChecker; + File_: TBytes; + R: TDDDChainResult; +begin + File_ := ConcatBytes([ + MakeBlock($05, $01, TBytes.Create($AA)), // overview + MakeBlock($05, $04, TBytes.Create($BB))]); // activities — no signature between + Checker := TOBDTachographSignatureChecker.Create; + try + Checker.SetCardVerifier(TConfigurableVerifier.Create(True)); + R := Checker.VerifyChain(File_); + Assert.IsFalse(R.Verified); + Assert.IsTrue(R.Reason.Contains('not followed by a signature')); + finally + Checker.Free; + end; +end; + +//------------------------------------------------------------------------------ +// VERIFY CHAIN FAILS WHEN VERIFIER RETURNS FALSE +//------------------------------------------------------------------------------ +procedure TTachographSignatureTests.VerifyChainFailsWhenVerifierReturnsFalse; +var + Checker: TOBDTachographSignatureChecker; + File_: TBytes; + R: TDDDChainResult; +begin + File_ := ConcatBytes([ + MakeBlock($05, $01, TBytes.Create($AA)), + MakeBlock($05, $0E, TBytes.Create($DE, $AD))]); + Checker := TOBDTachographSignatureChecker.Create; + try + Checker.SetCardVerifier(TConfigurableVerifier.Create(False)); + R := Checker.VerifyChain(File_); + Assert.IsFalse(R.Verified); + Assert.IsTrue(R.Reason.Contains('failed verification')); + Assert.AreEqual(0, R.FirstFailureBlockIndex); + finally + Checker.Free; + end; +end; + +//------------------------------------------------------------------------------ +// VERIFY CHAIN FAILS WHEN VERIFIER NOT CONFIGURED +//------------------------------------------------------------------------------ +procedure TTachographSignatureTests.VerifyChainFailsWhenVerifierNotConfigured; +var + Checker: TOBDTachographSignatureChecker; + File_: TBytes; + R: TDDDChainResult; +begin + File_ := ConcatBytes([ + MakeBlock($05, $01, TBytes.Create($AA)), + MakeBlock($05, $0E, TBytes.Create($01))]); + Checker := TOBDTachographSignatureChecker.Create; + try + R := Checker.VerifyChain(File_); + Assert.IsFalse(R.Verified); + Assert.IsTrue(R.Reason.Contains('No verifier')); + finally + Checker.Free; + end; +end; + +initialization + TDUnitX.RegisterTestFixture(TTachographSignatureTests); + +end. diff --git a/tests/Tests.Tachograph.Workshop.pas b/tests/Tests.Tachograph.Workshop.pas new file mode 100644 index 00000000..807c9536 --- /dev/null +++ b/tests/Tests.Tachograph.Workshop.pas @@ -0,0 +1,264 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.Tachograph.Workshop.pas +// CONTENTS : Tests for OBD.Tachograph.Workshop +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.Tachograph.Workshop; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TTachographWorkshopTests = class + public + /// + /// U t c sync round trip. + /// + [Test] procedure UTCSyncRoundTrip; + /// + /// U t c sync bad card id raises. + /// + [Test] procedure UTCSyncBadCardIdRaises; + /// + /// K l w round trip. + /// + [Test] procedure KLWRoundTrip; + /// + /// K out of range raises. + /// + [Test] procedure KOutOfRangeRaises; + /// + /// Tyre size round trip. + /// + [Test] procedure TyreSizeRoundTrip; + /// + /// Tyre out of range raises. + /// + [Test] procedure TyreOutOfRangeRaises; + /// + /// V i n round trip. + /// + [Test] procedure VINRoundTrip; + /// + /// V i n bad length raises. + /// + [Test] procedure VINBadLengthRaises; + /// + /// V r plate round trip. + /// + [Test] procedure VRPlateRoundTrip; + /// + /// V r plate too long raises. + /// + [Test] procedure VRPlateTooLongRaises; + /// + /// Sealed activation layout. + /// + [Test] procedure SealedActivationLayout; + /// + /// Date time to time real round trips. + /// + [Test] procedure DateTimeToTimeRealRoundTrips; + end; + +implementation + +uses + System.SysUtils, System.DateUtils, + OBD.Tachograph.Workshop; + +//------------------------------------------------------------------------------ +// UTCSYNC ROUND TRIP +//------------------------------------------------------------------------------ +procedure TTachographWorkshopTests.UTCSyncRoundTrip; +var + In_, Out_: TTachoUTCSync; + Bytes: TBytes; +begin + In_.UTCTimestamp := 1700000000; + SetLength(In_.WorkshopCardId, 16); + In_.WorkshopCardId[0] := $AA; + In_.WorkshopCardId[15] := $77; + Bytes := EncodeUTCSync(In_); + Assert.AreEqual(20, Length(Bytes)); + Out_ := DecodeUTCSync(Bytes); + Assert.AreEqual(In_.UTCTimestamp, Out_.UTCTimestamp); + Assert.AreEqual(Integer($AA), Integer(Out_.WorkshopCardId[0])); + Assert.AreEqual(Integer($77), Integer(Out_.WorkshopCardId[15])); +end; + +//------------------------------------------------------------------------------ +// UTCSYNC BAD CARD ID RAISES +//------------------------------------------------------------------------------ +procedure TTachographWorkshopTests.UTCSyncBadCardIdRaises; +var + Op: TTachoUTCSync; +begin + Op.UTCTimestamp := 0; + SetLength(Op.WorkshopCardId, 8); + Assert.WillRaise( + procedure begin EncodeUTCSync(Op); end, EOBDTachoWorkshop); +end; + +//------------------------------------------------------------------------------ +// KLWROUND TRIP +//------------------------------------------------------------------------------ +procedure TTachographWorkshopTests.KLWRoundTrip; +var + In_, Out_: TTachoKLWFactors; + Bytes: TBytes; +begin + In_.K := 8000; + In_.L := 200000 mod $10000; // L is uint16; example value within range + In_.W := 8200; + Bytes := EncodeKLW(In_); + Assert.AreEqual(6, Length(Bytes)); + Out_ := DecodeKLW(Bytes); + Assert.AreEqual(In_.K, Out_.K); + Assert.AreEqual(In_.L, Out_.L); + Assert.AreEqual(In_.W, Out_.W); +end; + +//------------------------------------------------------------------------------ +// KOUT OF RANGE RAISES +//------------------------------------------------------------------------------ +procedure TTachographWorkshopTests.KOutOfRangeRaises; +var + Op: TTachoKLWFactors; +begin + Op.K := 100; + Op.L := 0; + Op.W := 0; + Assert.WillRaise( + procedure begin EncodeKLW(Op); end, EOBDTachoWorkshop); +end; + +//------------------------------------------------------------------------------ +// TYRE SIZE ROUND TRIP +//------------------------------------------------------------------------------ +procedure TTachographWorkshopTests.TyreSizeRoundTrip; +var + In_, Out_: TTachoTyreSize; + Bytes: TBytes; +begin + In_.CircumferenceMm := 2050; + Bytes := EncodeTyreSize(In_); + Assert.AreEqual(2, Length(Bytes)); + Out_ := DecodeTyreSize(Bytes); + Assert.AreEqual(2050, Integer(Out_.CircumferenceMm)); +end; + +//------------------------------------------------------------------------------ +// TYRE OUT OF RANGE RAISES +//------------------------------------------------------------------------------ +procedure TTachographWorkshopTests.TyreOutOfRangeRaises; +var + Op: TTachoTyreSize; +begin + Op.CircumferenceMm := 500; + Assert.WillRaise( + procedure begin EncodeTyreSize(Op); end, EOBDTachoWorkshop); +end; + +//------------------------------------------------------------------------------ +// VINROUND TRIP +//------------------------------------------------------------------------------ +procedure TTachographWorkshopTests.VINRoundTrip; +var + In_, Out_: TTachoVINUpdate; + Bytes: TBytes; +begin + In_.VIN := 'WVWZZZ8N8Z1234567'; + Bytes := EncodeVIN(In_); + Assert.AreEqual(17, Length(Bytes)); + Out_ := DecodeVIN(Bytes); + Assert.AreEqual('WVWZZZ8N8Z1234567', Out_.VIN); +end; + +//------------------------------------------------------------------------------ +// VINBAD LENGTH RAISES +//------------------------------------------------------------------------------ +procedure TTachographWorkshopTests.VINBadLengthRaises; +var + Op: TTachoVINUpdate; +begin + Op.VIN := 'TOO-SHORT'; + Assert.WillRaise( + procedure begin EncodeVIN(Op); end, EOBDTachoWorkshop); +end; + +//------------------------------------------------------------------------------ +// VRPLATE ROUND TRIP +//------------------------------------------------------------------------------ +procedure TTachographWorkshopTests.VRPlateRoundTrip; +var + In_, Out_: TTachoVRPlate; + Bytes: TBytes; +begin + In_.PlateText := 'NL-12-AB-34'; + In_.NationalSymbol := $1F; // arbitrary + Bytes := EncodeVRPlate(In_); + Out_ := DecodeVRPlate(Bytes); + Assert.AreEqual('NL-12-AB-34', Out_.PlateText); + Assert.AreEqual(Integer($1F), Integer(Out_.NationalSymbol)); +end; + +//------------------------------------------------------------------------------ +// VRPLATE TOO LONG RAISES +//------------------------------------------------------------------------------ +procedure TTachographWorkshopTests.VRPlateTooLongRaises; +var + Op: TTachoVRPlate; +begin + Op.PlateText := 'THIS-PLATE-IS-TOO-LONG-EXCEEDS-13'; + Op.NationalSymbol := 0; + Assert.WillRaise( + procedure begin EncodeVRPlate(Op); end, EOBDTachoWorkshop); +end; + +//------------------------------------------------------------------------------ +// SEALED ACTIVATION LAYOUT +//------------------------------------------------------------------------------ +procedure TTachographWorkshopTests.SealedActivationLayout; +var + Op: TTachoSealedActivation; + Bytes: TBytes; +begin + Op.UTCTimestamp := 1700000000; + SetLength(Op.WorkshopCardId, 16); + Op.PostSealNote := 'OK'; + Bytes := EncodeSealedActivation(Op); + // 4 + 16 + 1 (note-len) + 2 (note bytes) = 23 + Assert.AreEqual(23, Length(Bytes)); + Assert.AreEqual(2, Integer(Bytes[20])); + Assert.AreEqual(Integer(Ord('O')), Integer(Bytes[21])); + Assert.AreEqual(Integer(Ord('K')), Integer(Bytes[22])); +end; + +//------------------------------------------------------------------------------ +// DATE TIME TO TIME REAL ROUND TRIPS +//------------------------------------------------------------------------------ +procedure TTachographWorkshopTests.DateTimeToTimeRealRoundTrips; +var + T: TDateTime; + R: UInt32; +begin + T := EncodeDate(2026, 5, 9) + EncodeTime(12, 0, 0, 0); + R := DateTimeToTimeReal(T); + Assert.AreEqual(SecondsBetween(EncodeDate(2026, 5, 9) + EncodeTime(12, 0, 0, 0), + TimeRealToDateTime(R)), Int64(0)); +end; + +initialization + TDUnitX.RegisterTestFixture(TTachographWorkshopTests); + +end. diff --git a/tests/Tests.UDS.NRC.pas b/tests/Tests.UDS.NRC.pas new file mode 100644 index 00000000..96a0848c --- /dev/null +++ b/tests/Tests.UDS.NRC.pas @@ -0,0 +1,178 @@ +//------------------------------------------------------------------------------ +// UNIT : Tests.UDS.NRC.pas +// CONTENTS : Tests for OBD.UDS.NRC +// VERSION : 1.0 +// TARGET : Embarcadero Delphi 11 or higher +// AUTHOR : Ernst Reidinga (ERDesigns) +// STATUS : Open source under Apache 2.0 library +// COMPATIBILITY : Windows / macOS / Linux +// RELEASE DATE : 09/05/2026 +// COPYRIGHT : © 2024-2026 Ernst Reidinga (ERDesigns) +//------------------------------------------------------------------------------ +unit Tests.UDS.NRC; + +interface + +uses + DUnitX.TestFramework; + +type + [TestFixture] + TUDSNrcTests = class + public + /// + /// Describe known general reject by name. + /// + [Test] procedure DescribeKnownGeneralRejectByName; + /// + /// Describe security access denied. + /// + [Test] procedure DescribeSecurityAccessDenied; + /// + /// Describe request correctly received response pending. + /// + [Test] procedure DescribeRequestCorrectlyReceivedResponsePending; + /// + /// Describe reserved falls back. + /// + [Test] procedure DescribeReservedFallsBack; + /// + /// Format produces hex and short name. + /// + [Test] procedure FormatProducesHexAndShortName; + /// + /// Transient n r c detected. + /// + [Test] procedure TransientNRCDetected; + /// + /// Non transient not flagged. + /// + [Test] procedure NonTransientNotFlagged; + /// + /// Security category classified correctly. + /// + [Test] procedure SecurityCategoryClassifiedCorrectly; + /// + /// Condition category classified correctly. + /// + [Test] procedure ConditionCategoryClassifiedCorrectly; + end; + +implementation + +uses + System.SysUtils, OBD.UDS.NRC; + +//------------------------------------------------------------------------------ +// DESCRIBE KNOWN GENERAL REJECT BY NAME +//------------------------------------------------------------------------------ +procedure TUDSNrcTests.DescribeKnownGeneralRejectByName; +var + Info: TOBDUDSNrcInfo; +begin + Info := DescribeNRC($10); + Assert.AreEqual('GR', Info.ShortName); + Assert.AreEqual('generalReject', Info.Description); + Assert.AreEqual(Ord(nrcGeneral), Ord(Info.Category)); +end; + +//------------------------------------------------------------------------------ +// DESCRIBE SECURITY ACCESS DENIED +//------------------------------------------------------------------------------ +procedure TUDSNrcTests.DescribeSecurityAccessDenied; +var + Info: TOBDUDSNrcInfo; +begin + Info := DescribeNRC($33); + Assert.AreEqual('SAD', Info.ShortName); + Assert.IsTrue(Info.Description.Contains('securityAccessDenied')); + Assert.AreEqual(Ord(nrcSecurity), Ord(Info.Category)); +end; + +//------------------------------------------------------------------------------ +// DESCRIBE REQUEST CORRECTLY RECEIVED RESPONSE PENDING +//------------------------------------------------------------------------------ +procedure TUDSNrcTests.DescribeRequestCorrectlyReceivedResponsePending; +var + Info: TOBDUDSNrcInfo; +begin + Info := DescribeNRC($78); + Assert.AreEqual('RCRRP', Info.ShortName); + Assert.AreEqual(Ord(nrcCondition), Ord(Info.Category)); +end; + +//------------------------------------------------------------------------------ +// DESCRIBE RESERVED FALLS BACK +//------------------------------------------------------------------------------ +procedure TUDSNrcTests.DescribeReservedFallsBack; +var + Info: TOBDUDSNrcInfo; +begin + Info := DescribeNRC($AB); + Assert.AreEqual(Ord(nrcReserved), Ord(Info.Category)); + Assert.IsTrue(Info.Description.Contains('AB')); +end; + +//------------------------------------------------------------------------------ +// FORMAT PRODUCES HEX AND SHORT NAME +//------------------------------------------------------------------------------ +procedure TUDSNrcTests.FormatProducesHexAndShortName; +begin + Assert.IsTrue(FormatNRC($35).Contains('0x35')); + Assert.IsTrue(FormatNRC($35).Contains('IK')); + Assert.IsTrue(FormatNRC($35).Contains('invalidKey')); +end; + +//------------------------------------------------------------------------------ +// TRANSIENT NRCDETECTED +//------------------------------------------------------------------------------ +procedure TUDSNrcTests.TransientNRCDetected; +begin + Assert.IsTrue(IsTransientNRC($21)); // BRR + Assert.IsTrue(IsTransientNRC($22)); // CNC + Assert.IsTrue(IsTransientNRC($78)); // RCRRP + Assert.IsTrue(IsTransientNRC($94)); // RTNT +end; + +//------------------------------------------------------------------------------ +// NON TRANSIENT NOT FLAGGED +//------------------------------------------------------------------------------ +procedure TUDSNrcTests.NonTransientNotFlagged; +begin + Assert.IsFalse(IsTransientNRC($10)); // GR + Assert.IsFalse(IsTransientNRC($33)); // SAD + Assert.IsFalse(IsTransientNRC($72)); // GPF +end; + +//------------------------------------------------------------------------------ +// SECURITY CATEGORY CLASSIFIED CORRECTLY +//------------------------------------------------------------------------------ +procedure TUDSNrcTests.SecurityCategoryClassifiedCorrectly; +const + SecurityNrcs: array[0..6] of Byte = ($33, $34, $35, $36, $37, $38, $5A); +var + N: Byte; +begin + for N in SecurityNrcs do + Assert.AreEqual(Ord(nrcSecurity), Ord(DescribeNRC(N).Category), + Format('NRC 0x%.2x should be security', [N])); +end; + +//------------------------------------------------------------------------------ +// CONDITION CATEGORY CLASSIFIED CORRECTLY +//------------------------------------------------------------------------------ +procedure TUDSNrcTests.ConditionCategoryClassifiedCorrectly; +const + CondNrcs: array[0..6] of Byte = ($21, $22, $24, $78, $7E, $7F, $81); +var + N: Byte; +begin + for N in CondNrcs do + Assert.AreEqual(Ord(nrcCondition), Ord(DescribeNRC(N).Category), + Format('NRC 0x%.2x should be condition', [N])); +end; + +initialization + TDUnitX.RegisterTestFixture(TUDSNrcTests); + +end. diff --git a/tests/Tests.VIN.Decoder.pas b/tests/Tests.VIN.Decoder.pas index b2feaab5..2b558b6f 100644 --- a/tests/Tests.VIN.Decoder.pas +++ b/tests/Tests.VIN.Decoder.pas @@ -92,12 +92,18 @@ implementation { TVinDecoderTests } +//------------------------------------------------------------------------------ +// CALCULATE CHECK DIGIT_PRODUCES EXPECTED DIGIT +//------------------------------------------------------------------------------ procedure TVinDecoderTests.CalculateCheckDigit_ProducesExpectedDigit( const VIN: string; const Expected: Char); begin Assert.AreEqual(Expected, TOBDVinDecoder.CalculateCheckDigit(VIN)); end; +//------------------------------------------------------------------------------ +// VALIDATE CHECK DIGIT_ACCEPTS KNOWN GOLDEN VINS +//------------------------------------------------------------------------------ procedure TVinDecoderTests.ValidateCheckDigit_AcceptsKnownGoldenVins( const VIN: string); begin @@ -105,6 +111,9 @@ procedure TVinDecoderTests.ValidateCheckDigit_AcceptsKnownGoldenVins( Format('ValidateCheckDigit rejected golden VIN %s', [VIN])); end; +//------------------------------------------------------------------------------ +// VALIDATE CHECK DIGIT_REJECTS TAMPERED VIN +//------------------------------------------------------------------------------ procedure TVinDecoderTests.ValidateCheckDigit_RejectsTamperedVin; var Tampered: string; @@ -116,6 +125,9 @@ procedure TVinDecoderTests.ValidateCheckDigit_RejectsTamperedVin; 'Tampered VIN was incorrectly accepted'); end; +//------------------------------------------------------------------------------ +// CHECK DIGIT_ROUND TRIP_ALWAYS VALIDATES +//------------------------------------------------------------------------------ procedure TVinDecoderTests.CheckDigit_RoundTrip_AlwaysValidates( const Source: string); var @@ -133,6 +145,9 @@ procedure TVinDecoderTests.CheckDigit_RoundTrip_AlwaysValidates( [Source, CalculatedDigit])); end; +//------------------------------------------------------------------------------ +// VALIDATE_ACCEPTS CANONICAL VIN +//------------------------------------------------------------------------------ procedure TVinDecoderTests.Validate_AcceptsCanonicalVin; var Err: string; @@ -142,6 +157,9 @@ procedure TVinDecoderTests.Validate_AcceptsCanonicalVin; Assert.AreEqual('', Err); end; +//------------------------------------------------------------------------------ +// VALIDATE_REJECTS BAD LENGTH +//------------------------------------------------------------------------------ procedure TVinDecoderTests.Validate_RejectsBadLength(const VIN: string); var Err: string; @@ -150,6 +168,9 @@ procedure TVinDecoderTests.Validate_RejectsBadLength(const VIN: string); Assert.IsTrue(Err.Contains('17'), 'Error should mention 17-character rule'); end; +//------------------------------------------------------------------------------ +// VALIDATE_REJECTS FORBIDDEN CHARACTERS +//------------------------------------------------------------------------------ procedure TVinDecoderTests.Validate_RejectsForbiddenCharacters( const VIN: string); var @@ -159,6 +180,9 @@ procedure TVinDecoderTests.Validate_RejectsForbiddenCharacters( Assert.IsNotEmpty(Err); end; +//------------------------------------------------------------------------------ +// PARSE_EXTRACTS WMI_VDS_VIS +//------------------------------------------------------------------------------ procedure TVinDecoderTests.Parse_ExtractsWMI_VDS_VIS; var Parsed: TVINParseResult; @@ -170,6 +194,9 @@ procedure TVinDecoderTests.Parse_ExtractsWMI_VDS_VIS; Assert.AreEqual('KP042788', Parsed.VIS); end; +//------------------------------------------------------------------------------ +// PARSE_POPULATES CHECK DIGIT +//------------------------------------------------------------------------------ procedure TVinDecoderTests.Parse_PopulatesCheckDigit; var Parsed: TVINParseResult; @@ -179,6 +206,9 @@ procedure TVinDecoderTests.Parse_PopulatesCheckDigit; Assert.IsTrue(Parsed.CheckDigitValid); end; +//------------------------------------------------------------------------------ +// PARSE_FLAGS INVALID VIN +//------------------------------------------------------------------------------ procedure TVinDecoderTests.Parse_FlagsInvalidVin; var Parsed: TVINParseResult; @@ -193,6 +223,9 @@ procedure TVinDecoderTests.Parse_FlagsInvalidVin; Assert.IsFalse(Parsed.CheckDigitValid); end; +//------------------------------------------------------------------------------ +// PARSE_PRODUCES EMPTY RESULT_FOR INVALID LENGTH +//------------------------------------------------------------------------------ procedure TVinDecoderTests.Parse_ProducesEmptyResult_ForInvalidLength; var Parsed: TVINParseResult; @@ -205,6 +238,9 @@ procedure TVinDecoderTests.Parse_ProducesEmptyResult_ForInvalidLength; Assert.AreEqual('', Parsed.VIS); end; +//------------------------------------------------------------------------------ +// GET MODEL YEAR_DECODES KNOWN YEAR CODES +//------------------------------------------------------------------------------ procedure TVinDecoderTests.GetModelYear_DecodesKnownYearCodes( const YearCode: Char; const ExpectedMin: Integer); var diff --git a/tests/Tests.dpr b/tests/Tests.dpr index 9240c71d..534bf624 100644 --- a/tests/Tests.dpr +++ b/tests/Tests.dpr @@ -30,6 +30,38 @@ uses Tests.VIN.Decoder in 'Tests.VIN.Decoder.pas', Tests.RadioCode.Smoke in 'Tests.RadioCode.Smoke.pas', Tests.RadioCode.Becker4 in 'Tests.RadioCode.Becker4.pas', + Tests.RadioCode.Registry in 'Tests.RadioCode.Registry.pas', + Tests.RadioCode.VinResolver in 'Tests.RadioCode.VinResolver.pas', + Tests.OEM.Coding.Diff in 'Tests.OEM.Coding.Diff.pas', + Tests.OEM.Coding.AuditLog in 'Tests.OEM.Coding.AuditLog.pas', + Tests.ECU.Flashing.Checkpoint in 'Tests.ECU.Flashing.Checkpoint.pas', + Tests.OEM.Coding.NewOEMs in 'Tests.OEM.Coding.NewOEMs.pas', + Tests.ECU.Signature.PQC in 'Tests.ECU.Signature.PQC.pas', + Tests.ECU.Flashing.VoltageGate in 'Tests.ECU.Flashing.VoltageGate.pas', + Tests.Protocol.DoIP.Discovery in 'Tests.Protocol.DoIP.Discovery.pas', + Tests.Adapter.Capabilities in 'Tests.Adapter.Capabilities.pas', + Tests.Protocol.SecOC in 'Tests.Protocol.SecOC.pas', + Tests.Protocol.IsoTp.Timing in 'Tests.Protocol.IsoTp.Timing.pas', + Tests.Adapter.PassThrough.J2534v2 in 'Tests.Adapter.PassThrough.J2534v2.pas', + Tests.EV.BatteryHealth in 'Tests.EV.BatteryHealth.pas', + Tests.Tachograph.Signature in 'Tests.Tachograph.Signature.pas', + Tests.OEM.ServiceRoutines in 'Tests.OEM.ServiceRoutines.pas', + Tests.Tachograph.Workshop in 'Tests.Tachograph.Workshop.pas', + Tests.Service06.Mode06 in 'Tests.Service06.Mode06.pas', + Tests.Protocol.WWHOBD in 'Tests.Protocol.WWHOBD.pas', + Tests.J1939.PGNs in 'Tests.J1939.PGNs.pas', + Tests.UDS.NRC in 'Tests.UDS.NRC.pas', + Tests.Protocol.WWHOBD.Readiness in 'Tests.Protocol.WWHOBD.Readiness.pas', + Tests.Service09.Calibration in 'Tests.Service09.Calibration.pas', + Tests.DriveCycle.Advisor in 'Tests.DriveCycle.Advisor.pas', + Tests.OEM.KeyAdaptation.BMW in 'Tests.OEM.KeyAdaptation.BMW.pas', + Tests.OEM.ComponentProtection.VAG in 'Tests.OEM.ComponentProtection.VAG.pas', + Tests.OEM.SCN.Mercedes in 'Tests.OEM.SCN.Mercedes.pas', + Tests.OEM.KeyAdaptation.HMG in 'Tests.OEM.KeyAdaptation.HMG.pas', + Tests.OEM.KeyAdaptation.Ford in 'Tests.OEM.KeyAdaptation.Ford.pas', + Tests.OEM.KeyAdaptation.Toyota in 'Tests.OEM.KeyAdaptation.Toyota.pas', + Tests.OEM.SessionHelper in 'Tests.OEM.SessionHelper.pas', + Tests.DriveCycle.Resolvers in 'Tests.DriveCycle.Resolvers.pas', Tests.Service.Encoders in 'Tests.Service.Encoders.pas', Tests.Service.Decoders in 'Tests.Service.Decoders.pas', Tests.Protocol.IsoTp in 'Tests.Protocol.IsoTp.pas',