A PC diagnostic tool for the Land Rover Discovery 4 (L319) using a Vgate iCar Pro WiFi (or any ELM327-compatible WiFi adapter). Pure Python 3, no dependencies to install.
- Plug the iCar Pro into the OBD port (under the dash, driver's side).
- Ignition ON (engine can be off).
- On your PC, join the adapter's WiFi network (
V-LINK/iCar_xxxx). - Run:
python3 run.py
Choose 1) Connect (defaults 192.168.0.10:35000 are correct for the
iCar Pro WiFi).
While you're on the adapter's WiFi you have no internet, so the log is
the only record of what happened. Each run writes a complete transcript to
logs/lrdiag_YYYYMMDD_HHMMSS.log: every screen line, everything you typed,
every raw byte to/from the adapter (with timings), and every parsed UDS
request/response. Crashes land there with full tracebacks.
Tips for car sessions:
- Press n in the menu to drop a timestamped note into the log ("compressor started now", "truck dropped to bumpstops").
- The
logs/folder is not gitignored on purpose: after a car session, commit and push the log so it can be analyzed from any other machine.
- Connect (option 1) — should show the VIN.
- Scan bus (option 4) — discovers every ECU that answers on your car and prints its part number / system name. Find the air suspension module in the list (system name or a suspension part number).
- Edit
modules.json: set the EAS module'sreq/respto what the scan found and flip"verified": true. The shipped address (711/719) is a best-effort guess and must be confirmed by the scan. - Air suspension panel (option 7) — reads the module's identity and
fault codes (e.g.
C1A20-64), with plain-language hints for common codes.
| Option | What it does |
|---|---|
| 2 | Live engine data (RPM, temps, MAF, fuel rate...) — standard OBD2 |
| 3 | Generic engine fault codes |
| 4 | ECU discovery scan (0x700–0x7F7) |
| 5/6 | Read / clear fault codes per module (UDS 19 02, KWP 18 fallback) |
| 7 | Air suspension: identification, DTCs with hints, configured live data |
| 8 | DID scanner — probe a module for supported data IDs |
| 9 | Active tests (gated, see below) |
| 10 | Raw UDS console for manual exploration |
| 11 | CCF explorer — read-only car-config dump + feature decode (see below) |
JLR never published the EAS data identifiers, so the app finds them
empirically: run the DID scanner (option 8) against the suspension
module over a range like 0000–01FF. Note which DIDs answer, then watch
their values while the vehicle changes height (engine running, switch
between off-road/normal/access height). Values that track movement are
your height/pressure DIDs — add them to live_data_dids in modules.json
and they'll show in the suspension panel.
Optional lighting features aren't plug-and-play on the D4 — they're switched on in the CCF (Car Configuration File), a central config record the body electronics read to know which features the car has. Installing the lamps in the door harness isn't enough; the CCF flag has to be set (what SDD / IIDTool do).
Two realities to know before you start:
- Bus access. The CCF host (Central Junction Box) is on the MS-CAN, which is on OBD pins 3/11. The iCar Pro only wires HS-CAN (pins 6/14), so the CJB may simply not answer without a custom pin 3/11 → 6/14 adapter cable. Option 11 tells you empirically whether anything is reachable.
- The write needs security access. Reading the CCF is often possible; writing it normally requires a seed/key unlock whose algorithm JLR hasn't published. A bad CCF write can disable working features — it's the riskiest operation short of flashing.
So option 11 is read-only by design. It probes the configured body/cluster
modules, dumps whatever CCF DIDs answer, and decodes known feature flags
(welcome lamps, puddle lamps, follow-me-home) against a byte map in
modules.json. It reports if a DID demands security access, and never writes.
The byte/bit locations shipped in modules.json under "ccf" are
unverified placeholders — the real offsets must be confirmed by diffing a
dump from your own car against a community CCF byte-map spreadsheet (D4
forums). Once you've confirmed the DID + byte, the actual enable step can be
added with the same typed-confirmation gating used for active tests. For a
one-shot change, a GAP IIDTool remains the pragmatic route since it handles
the security access and validates the CCF.
Reading and clearing faults is low-risk. Active functions (deploy/ retract, valve tests, height calibration) use routine IDs that JLR keeps proprietary, and most require a seed/key security unlock that is not publicly documented for this module. I have not shipped guessed routine IDs — sending wrong routines to the EAS can drop the truck or latch a hard fault.
The supported path: if you can capture a CAN log of a GAP IIDTool or
dealer SDD session performing the test on your own car, the routine/IO
requests will be visible in the log. Add them to active_tests in
modules.json (templates included) and the app will run them with
session handling, typed confirmation, and a stop/return-control step.
Safety rules the app enforces for active tests: typed RUN confirmation,
extended diagnostic session only for the duration, and an explicit
stop/return-control step. Your rules: vehicle outdoors, nobody and
nothing under it, wheels free.
- Connection refused / timeout: you're not on the adapter's WiFi, or another app (phone) is hogging the single TCP slot.
- NO DATA everywhere: ignition not on, or adapter seated badly.
- EAS doesn't answer: address unconfirmed — run the bus scan. (The EAS sits on the HS-CAN which is on OBD pins 6/14, so the iCar Pro can reach it.)
- Garbled/shifted responses: power-cycle the adapter; the app resynchronizes automatically on reconnect.
- DTC hint texts in
modules.jsonare community-sourced approximations — always cross-check the full code. - Clearing codes doesn't fix faults; codes return if the fault is present.
- Cheap ELM327 clones vary; the iCar Pro is one of the better ones, but if multi-frame responses misbehave, try again — the parser tolerates both header styles.
- This tool is read-mostly by design. It is not a replacement for an IIDTool/SDD for calibration or programming.
python3 tests/test_integration.py # runs against a built-in mock ELM327 + fake D4
python3 tests/mock_elm.py # run the fake car standalone on port 35000
You can point the real app at the mock (IP 127.0.0.1) to explore the UI
without the car.