Skip to content

Add LR4 commands - #5

Open
mwvolo wants to merge 92 commits into
OBDb:mainfrom
mwvolo:main
Open

mwvolo wants to merge 92 commits into
OBDb:mainfrom
mwvolo:main

Conversation

@mwvolo

@mwvolo mwvolo commented Jul 12, 2025

Copy link
Copy Markdown

Partially working on 2016 LR4
Adds (additional to core - confirmed working):

  • LANDROVER_PARKED (yes/no, reversed on main LR repo)
  • LANDROVER_ENGINE_LOAD (0-100%)
  • LANDROVER_IAT - Intake Air Temp (ºC)
  • LANDROVER_MAF - Mass Air Flow (g/Sec)
  • LANDROVER_ENGINE_OIL_TEMP (ªC)
  • LANDROVER_BOOST (1-5)

Other commands are [WIP]

@mwvolo mwvolo closed this Jul 18, 2025
@jverkoey

Copy link
Copy Markdown
Member

Heya! Was following along; did you need some help getting this one over the finish line?

@mwvolo

mwvolo commented Jul 19, 2025

Copy link
Copy Markdown
Author

Ha! Figured I was making a lot of noise, but I'll reopen for you to follow along!
Is there a way for me to validate json locally?

I am pretty excited by how much I've uncovered so far--big trip coming up and excited to use it!

@mwvolo mwvolo reopened this Jul 19, 2025
Thirteen DIDs returned a positive response on every read and the
identical value every time, across the whole log. They are
configuration or identity values, not sensors. The six that do vary
stay and now get looked at twice as often.

2A36 is worth watching: it advanced 602 while the odometer advanced
722 km, so it counts distance on some condition.
033E outpolls its SAE alias F423 15910 samples to 158, and 03F3 outpolls
F45C 347015 to 1169; F423 stopped answering entirely on 2026-09-01. Drop
both aliases, and with F423 the rail pressure crosscheck that divided one
by the other. 197C and D11C are undecoded raws that took zero polls in
ten days, and dropping them empties header 761.

F41F, F421 and F430 answer 100% of the time and decode through the F4xx
PID mirror this file already relies on: run time, distance with MIL on,
warm-ups since codes cleared.
22F191 identifies 734 as a Hella 14C243 headlamp leveller and 760 as the
ABS module a published L319 scan reports under CH32-14C227-AB. The other
four unmined modules rejected F191 but each returned a distinct serial,
so they are real and separate. A 0000-03FF sweep of 726 found one hit,
0202.

The poll audit is the uncomfortable half: F40C is set to the same freq as
vehicle speed and logged 12 samples in ten days against its 17130.
The starvation reading was wrong. It is not the budget - demand is 10.21
req/s against the 11-13 available - and it is not freq, because F40C and
F40D are both freq 1 and only F40D runs. Eight commands simply stopped on
2026-08-30, the day f782e4d and 527a47f rewrote every freq in the file.

The cost is concrete: engineSpeed has never been filled in fourteen months.
010C carries rpm 2511 times per drive and Pelican eats standard PIDs
internally, so the F40C alias was the only path to a recorded signal and it
is not running. THROTTLE_TRACKING and PEDAL_AGREEMENT cannot compute either.
1153 and 1154 are a charging pair at 1/256 V that 1cf20db called dead; 1154
sits at exactly 14.00 V and both dip on crank, and every sample behind the
original call was taken parked. F408 and F409 are the bank-2 trims, never
once requested on this truck, which is the only way to measure the
asymmetry directly now that F407 is silent. 113F and 2104 move but parked
data cannot tell a raw Celsius reading from a -40 one, so they go in as
scalars. 726/0202 is the single hit from today's sweep.

Demand goes to 10.76 req/s, still inside the budget, and F41F against
standard 011F tells us whether the alias path works at all.
Everything the truck has ever replied to that this file does not already
name, recorded as a raw scalar at the observed byte width. Cadence follows
what the logs already show each one doing: 60s for the six that vary, 120s
for the nine that flip between two values, 600s for the thirty-six that
have never moved - though most of those were only ever sampled parked, and
the suspension ones at a single ride height.

Costs 0.235 req/s, putting total demand at 10.99.
Cadence per tier, and the caveat that matters: every 'never moved' reading
came from a parked truck, and 3B00/3B01/3B08 were read at one ride height
where they could not have moved regardless. Records the 197C and D11C
reversal rather than quietly undoing it.

Also the suspension decode audit - pressures, signed height offset and
module voltage all check out against every logged sample; 3B4D has returned
zero on all 171 samples, and only 36 minutes in fourteen months have all
four corners at once.
CLAUDE.md is what someone needs before touching this repo: layout, the
F4xx convention and why it exists, that freq is an interval and the budget
has 0.01 req/s of slack, runnable validation, and five traps that all bit
during this work. The load-bearing one is that a command's presence in the
signalset does not mean it is being collected.

TESTING.md tracks what can only be answered in the truck - eight open
tasks, and a per-DID table of what each debug param is believed to be and
what would count as a result. Deciding a DID is dead requires a drive with
a ride-height change and a terrain-mode switch, because everything in the
600s tier was classified from a parked vehicle.
The ATSH/ATCRA pair is the part that is not obvious: one sets who you are
addressing, the other filters what comes back, and the block repeats
because each pair retargets the same three questions at the next module.
ATAR at the end matters more than it looks - leave a filter set and the
next healthy request reads as dead.

Worked examples are real frames from this truck's logs rather than
invented ones, so they can be checked: 7E80462F40582 decodes to 90 C
coolant, and 726's multi-frame F18C reply spells serial 5224293808.
Command meanings are taken from the Elm Electronics AT command list.
…answer

Trade the SAE duplicates for three PIDs that answer, name two modules
All 109 commands were polled. F40C answered 172 of 172 and engine speed
reached the signal database for the first time in fourteen months, with
nothing in this file having changed to cause it - the app had been holding
an older copy. The budget and freq theories were both wrong, and they were
worth ruling out only because doing so left no other explanation.

Thirty-six debug params came straight back out. The 7E0 11xx family and the
7E1 03xx/101A/21xx/2B1x block answer only inside an extended diagnostic
session; they answered in December 2025 because that session sent 10 03
first, and nobody had noticed. They are not dead probes, they are
unreachable by polling, and they now live in TESTING.md with the 10 03
recipe and its warning.

F408 and F409 answered, so there is bank-2 data at last, and it says the
banks agree: long term +9.31 and +7.32 percent. Both lean together, which
is a shared cause rather than the asymmetry we thought we had.

The ride-height change moved 3B01, 3B3C, both height sensors, all four
corner pressures and the compressor. Access height did register, in the
pressures rather than the state fields, which were sampled too slowly to
catch it - 3B01 goes to freq 10. 3B4D read zero throughout and is no longer
called Drive Mode.

73 commands at 10.55 req/s.
F423 and F45C came out of the signalset when their proprietary equivalents
won on sample count, but the signal tables kept listing them. Note the
reason on the surviving rows so nobody re-adds them.
First drive on the debug batch: the stale signalset explained, 36 params are session-gated
Decoding the ECM's own 0100/0120/0140/0160 bitmasks says it supports 54
mode-01 PIDs; 25 had no entry here. Dropping the metadata ones and the two
SAE aliases that already lost to their proprietary twins leaves sixteen.

Three of them exist to prosecute the lean finding rather than to collect
data: F403 says whether the engine was even in closed loop when those trims
were measured, F42E tests the stuck-purge-valve hypothesis that would
explain both banks drifting together, and F438 is the bank 2 oxygen sensor
sitting behind the bank 2 trim we only just started reading.

Eight go in as single raw bytes because we do not know what they are. len 8
is deliberate - reading the first byte is safe whatever the real width turns
out to be, and they get widened once something replies.

89 commands at 10.93 req/s.
Probe the sixteen PIDs the ECM says it supports and we never read
F466, F467, F468 and F470 went in at len 8 because nobody knew how wide
they were. The first byte of each turns out to be a sensor-support mask, so
all four have been faithfully recording the constant 3. The real payloads
are two mass airflow sensors, two coolant sensors, two intake air sensors
and ten bytes of boost control, and every number we have from them was read
out of raw frames by hand rather than by the signalset.

The dual MAF is the reason to care: 2.09 g/s against 1.78, a 17.5% split
between intake tracts, summing to roughly what F410 reports on its own. MAF
calibration was one of three explanations for both banks trimming lean
together and this is the first evidence touching it, so F466 goes to freq 5.

F416, F419 and F41A answer FFFF, not available, every time and are gone.
F413 said 0x77 once - both banks have sensors 1 through 3 - which can never
change, so it is written down rather than polled.

Re-tiered for a two-hour drive with off-roading: the diff-lock pair to 30,
boost control to 15, purge to 10. Barometric pressure pays for it at freq 5
instead of 1, which frees 0.9 req/s and loses nothing.

85 commands at 10.53 req/s.
…-long-drive

Widen the four PIDs whose first byte was a support bitmask, and set up for a long off-road drive
Read against the SAE J1979 definition of PID 70, the ten bytes we were
storing as three anonymous words are commanded and measured boost for two
channels at 1/32 kPa. The support byte says channel A exists and it reads
24.72 kPa at idle. First direct boost measurement on this truck - the boost
ratio synthetic has only ever inferred it from MAP over barometric - and it
costs nothing, because the reply was already arriving.

Checked every other command the same way against the canonical SAE
signalset. Nothing else is being left behind: the unread fields are
sensor-present bits and channels a V6 does not have.

Also recorded what Jaguar does for tire pressures, since it fills eight
metric slots we leave empty. Its 751 is silent here across 1386 attempts,
but its temperature DIDs 2A0A-2A0D have never been sent to a module that
answers, which is a parked sweep rather than a drive.
Twenty-nine signals are raw probes or byte splits that nobody can read yet.
They belong in the log, not on the screen, so they get hidden: true. SAE
J1979 hides 76 of its 294 the same way; we were hiding none of 98.

Twenty-three get a description, aimed at the ones that would mislead a
reader who trusted the name: the height sensors are inverted, bank 2 lambda
rails at FFFF about half the time and those samples are invalid rather than
lean, altitude is a barometric lookup and not GPS, and 3B4D is emphatically
not the drive mode its old name claimed.

Also recorded why sixteen metric slots is the ceiling rather than an
oversight. Of the twenty unfilled, ten are electric-vehicle only, eight are
tires we cannot read yet, and the last two need data this ECM does not
produce. fuelRate cannot be faked with a synthetic either - the only
supported formula op is a plain ratio with no constant, so the number would
be wrong by a factor of fourteen. An empty slot beats a wrong gauge.
F470 is boost pressure, and what the other OBDb repos suggest
Hide the raw probes, describe the misleading ones, and record why 16 metric slots is the ceiling
The app never gives signalset traffic more than about 4.2 req/s. Measured
over seven sessions: 108 distinct DIDs got 4.19 req/s, 23 DIDs got 4.22.
The adapter's 13.4 req/s is real but the app spends 3.3 on AT commands and
5.9 on its own mode-01 polling. We had been asking for 10.53 against a
ceiling of 11 that was never ours to spend.

Overdrawing it has a cost that isn't slower polling. The app retires ECUs
and never retries them: 726 and 761 went at 10:59, 732 and 792 at 12:40,
7D3 at 14:07, all while answering correctly. On the 137-minute drive that
followed, 61 of 84 commands were never sent once. The off-road tests were
all aimed at 7D3 and collected nothing.

Cut to 66 commands at 3.922 req/s. Nineteen deleted as dead across all
logged history, not one drive, including 3B4D, which was the Terrain
Response candidate and is constant zero across 236 samples spanning
several modes. Engine speed is back on a real cadence.

Three duplicates proven by correlation and resolved: PID 67 sensor 1 is
the coolant PID to 0.0 C over 135 pairs; PID 70 channel A is manifold
pressure to 1.3 kPa over 525 pairs, not a separate boost sensor; the
proprietary oil temp DID is standard PID 5C to 0.7 C over 1,135 pairs.
PID 67 sensor 2 turns out to be the supercharger intercooler's low
temperature circuit, which is new.

Ride height Access never registered because 3B3C is a bitfield, not an
ordinal. 0x04 is Access and was not in the map; the 0 we had labelled
Access never occurs. Pinned against the inverted front height sensor.

Fuel rate is computed. The ECM's bitmask says PID 5E is unsupported, so
it is derived from MAF and commanded lambda with the constant folded into
a hidden operand, since synthetics only allow a bare ratio. Integrates to
11.21 L against 9.48 L from the tank gauge over 91.5 km.

Corrects two claims made earlier today: F470 is manifold pressure at finer
resolution rather than boost, and the two MAF channels do not sum to the
single MAF PID. That rested on one idle sample; 1,474 samples give a
least-squares slope of 0.88 with scatter too wide to trust the split.

Also documents that the app only ever stores signals carrying a
suggestedMetric. Fifteen distinct signals in fifteen months. Everything
else is polled and thrown away.
The suspension section, the off-road no-data section and the new charge
cooler signal all needed the same caveat spelled out locally: resetting
the app's vehicle profile brings those modules back to life, but none of
their signals carry a suggestedMetric, so none of them will ever be
recorded as history either way. Live readouts and scan logs only.

Claude-Session: https://claude.ai/code/session_01Kzsf15spEW5oNrt7zCt8Z7
I read one day of sessions and called it a one-way ratchet in which the
app permanently retires ECUs, then told the owner to delete and re-add the
vehicle. He pushed back and asked whether this was new. It wasn't.

Taking the longest session of each day since 09-05, coverage has been 12
to 23 distinct DIDs every time. The 137-minute off-road drive polled 23 --
the widest of any long session in the period. The drive was not degraded,
it was the best long session on record, and 61-of-84 is simply what this
app has always done.

The stable core explains itself. Thirteen commands appear in every long
session since 09-05 and they are exactly thirteen of the sixteen carrying
a suggestedMetric at the time. The app converges on polling what it will
store. Modules with no metric-bearing command drift out of the rotation,
which covers 7D3, 792, 732, 726 and 761 without needing a ratchet.
Coverage recovers on its own every time the signalset changes.

Nothing is stuck and nothing needs resetting from inside the app. The
retraction is recorded in place rather than deleted, because the wrong
version was convincing.

The signalset itself is untouched: the budget cut, the nineteen deletions,
the duplicate decodes, the ride height bitfield and the fuel rate all rest
on direct measurement, not on this explanation.

Claude-Session: https://claude.ai/code/session_01Kzsf15spEW5oNrt7zCt8Z7
The cull was wrong. I deleted nineteen commands for reading constant, but
most had between 6 and 30 samples and none of those samples covered the
event the command would report. 1E88's six readings were all taken with
the differential unlocked, which says nothing about a diff lock DID. A
signal that only moves during a rare event looks dead until the event
happens, and deleting it guarantees it never gets caught.

F458 was not weak evidence, it was a misread. 62F458 7F is a positive
reply carrying data byte 0x7F, not a 7F negative response. PID 58 is long
term secondary O2 trim bank 2, the counterpart to PID 56 already in the
file, and 0x7F is -0.78%. Now mapped as LR4_SEC_O2_TRIM_B2.

All nineteen are back at exploratory cadences: the event-driven ones
(1E88, 1E89, 3B4D) at 60s, 3B00 and 3B08 at 120s, the rest at 600s. The
whole set costs 0.03 req/s against a 4.2 ceiling, so the budget was never
the real argument for removing them. 3B4D stays despite being the one
genuinely conclusive case, because at 600s it is free.

Also adds six probes the ECM's own supported-PID bitmask says exist and
that this file has never requested: PID 23 fuel rail gauge pressure (158
logged samples, 35 to 197 bar, the right shape for this DI rail and worth
comparing against 033E), PID 01 and PID 41 monitor status, PID 13 O2
sensors present, PID 51 fuel type, PID 1C OBD standard.

91 commands at 4.043 req/s, still under the ceiling, with twenty-five more
things to look at than before the cut.

Claude-Session: https://claude.ai/code/session_01Kzsf15spEW5oNrt7zCt8Z7
…-gives

Fit the budget the adapter actually gives us
The restored probes paid off within hours, and the merge did what it was
supposed to: coverage went from 23 distinct DIDs across 10 modules to 70
across 13, with 87 of 90 commands polled. Every module that had drifted
out came back on its own. No app reset, and deleting the vehicle would
have been wrong.

3B4D is alive. It was the one deletion I called conclusive -- flat 0x00
across 236 samples -- and it returned 0x04, 0x01 and 0x00 within eight
minutes of the off-road features being used. It does not track the ride
height mode PID, so Terrain Response is back to being the leading
candidate. Now at 30s.

Six of the eleven 792 counters advance: 2A32 through 2A37 gained 14000,
480, 960, 2170, 88 and 1 over 111 km and nine hours. They are counters,
not constants. What they count is still unknown because that window mixed
driving with parked time, so they now poll at 120s to make one drive
enough to regress against distance and running time.

3B01's last bit is pinned. 0x400 was Off-Road by elimination; it was
caught twice at a front reading of 85 to 89 with the mode PID reading
Off-Road. 3B01 is a mapped signal now: 0x100 Normal, 0x400 Off-Road,
0x800 Access. The ride height map checks out against a screenshot showing
Access, which had never displayed before the map was fixed.

All six new probes answered. MIL off with zero stored DTCs, six oxygen
sensors, gasoline, OBD-II, and a rail gauge pressure that ranges 29 to
197 bar against 033E's 41 to 46 over the same evening.

Also corrects two documented claims. hidden: true does not hide anything
-- fifteen hidden signals are on screen in the screenshots. And non-metric
signals do display live, which resolves the open question: the bandwidth
buys a readout, just never history.

Claude-Session: https://claude.ai/code/session_01Kzsf15spEW5oNrt7zCt8Z7
Holding the lower button puts the truck into a held-Access mode that stays
down instead of self-levelling. It appears in none of the 484 3B3C or 142
3B01 samples, and both words have exactly one unfilled slot: 3B01 bit 9
(0x200), between Normal at 0x100, Off-Road at 0x400 and Access at 0x800;
and 3B3C bit 3 (0x08), seen only inside 0x0D with the truck in motion.

The problem with catching it is that a mapped signal renders nothing for a
value it has not been taught, so a new state would show as a blank in the
app and survive only in the scan logs. That is exactly how 3B4D went
unnoticed. Both DIDs now carry an untranslated twin, Ride Height State Raw
and Ride Height Mode Raw, deliberately not hidden. They cost no extra
request and turn an unknown state into a number.

Two more suspension DIDs are not flat either. 3B00 reads 0x03 in 111 of 113
samples with one 0x103 taken at Access, and 3B02 carries a bit-8 flag that
toggles the same way; 3B08 reads 0x00 in 130 of 131 with one 0x01 taken
with Off-Road engaged. One sample each is not a decode, but none is dead.

Claude-Session: https://claude.ai/code/session_01Kzsf15spEW5oNrt7zCt8Z7
Decoding every signal in the file against the evening log turned up more
than the three headline movers.

The second word of F434 and F438 was being read as a voltage. SAE J1979
PIDs 34 to 3B carry equivalence ratio plus sensor current; the voltage
variants are 24 to 2B, which this ECM does not support. Read as voltage it
produced a flat 4.0 across 28,432 samples, which should have been the
giveaway. Read as current it spans -0.95 to +1.36 mA around +0.16, which is
what a wideband pump cell does. Both become O2 Pump Current in milliamps.

D11A was called Suspension Module Voltage. It is not battery voltage: over
221 paired samples its raw value swings 57 to 105 while control module
voltage moves 12.51 to 14.75, and the implied scale scatters 9%. The old
scaling put it at 16.8 V. Best correlation is 0.58 against compressor
activity, not enough to name it, so it goes back to an undecoded raw byte.

The two fuel rail pressures are different quantities, not one scaled two
ways. Over 157 pairs the ratio drifts 0.41 to 0.56, and in one stretch
PID 23 sat pinned near 197 bar while 033E climbed 79 to 110. A scaling
error would hold a fixed ratio.

Warm-ups since codes cleared is saturated at 255 and says only that they
were cleared more than 255 cycles ago.

Claude-Session: https://claude.ai/code/session_01Kzsf15spEW5oNrt7zCt8Z7
…oved

Three things I called dead moved as soon as a feature was used
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants