Range Estimator Bench

main/data/range_estimator.c driven by synthetic trips
0
KM REMAINING
raw 0 km
Blended0 Wh/km
Recent trip0 Wh/km
Learned0 Wh/km
True (simulated)0 Wh/km
Trip confidence0%
Trip distance0.0 km
Samples learned0
NVS writes0
Vehicle
SOC90.0%
Speed0 km/h
Pack power0.0 kW
Odometer0.0 km
Drive cycle
Displayed vs raw estimate
displayed (slew limited) raw estimate SOC
Learned Wh/km by speed and temperature
Each cell is an exponentially weighted average with its own sample count. Cells brighten as they gain confidence. A cell is trusted on its own at 20 samples, which is 2 km of driving in those conditions. This is the table that persists to NVS and makes the vehicle learn its own consumption.
Why it works this way

It is a data problem, not a compute problem

The arithmetic is a few dozen floating point operations per second, which is nothing for an ESP32-S3 with a hardware FPU. Every bad range readout in the industry is bad because of signal quality and filtering choices, not because the processor could not keep up. So the effort goes into modelling consumption honestly and presenting it so people trust it.

Three layers

Energy remaining. SOC through a piecewise curve, scaled by state of health, less a 3% reserve so a displayed 0 km is not an actual 0 km.

Consumption. Blended across a recent trip window, the learned histogram, and a factory seed.

Presentation. Slew limited to 2 km/s, so the number can never jump.

Why a histogram, not an average

A Defender is close to a brick aerodynamically. Consumption runs roughly a + b·v², so 80 versus 110 km/h is easily a 30% swing. Cold hits twice over, through pack internal resistance and cabin heating.

A single lifetime average cannot tell those apart. Twenty cells and 160 bytes of flash can, and the car ends up knowing its own consumption rather than a factory guess.

Why blend by trip distance

Start a trip and the answer comes from learned data. Fifteen kilometres in, it comes mostly from what is happening right now. Every level blends by its own confidence, so there is never a visible step as data accumulates.

That single choice removes most of the swing that makes factory range readouts feel like they are lying to you.

Why the display is deliberately slow

A number that lurches on every hill teaches people to ignore it. So the displayed value is rate limited, snaps on first wake rather than animating up from zero, and is allowed to climb on a long descent because that is honest.

What is not calibrated yet

The SOC curve ships as a straight line. It is an 11-point table so calibration is a data change, but nobody has measured it yet, so do not trust a bend that is not there.

380 Wh/km is an estimate, not a measurement. The pack current sign convention is assumed. Road speed is not yet mapped from CAN, and without it the whole thing is inert.

Full write-up, integration notes and the calibration procedure live in the repo at documentation/range-estimation.md. The C in main/data/range_estimator.c is the source of truth; range_estimator.js is a behaviourally identical port so this page runs the same algorithm the vehicle does.