Amateur endurance racing team · 2026
LapTimer: lap timer and endurance race strategist
A self-built Android app that times laps to millisecond precision with an external GPS receiver, and works out the optimal driver rotation for an endurance race.
Goal
A six-driver amateur team (8-hour endurance races judged on how consistent lap speeds are, not on speed) had no affordable, accurate way to time laps or plan driver changes. Off-the-shelf systems are expensive or don't give the team live access to its own data. So I built all of it: an Android app, a web-based pit wall and the strategy analysis.
Results in numbers
What changed
10 Hz+
GPS refresh rate with the external receiver (a phone's own GPS is about 1 Hz)
±2 s
target accuracy of the lap-finish prediction with the new logic (the old one was off by up to ±30 s)
191
laps timed at one 8-hour race
56%
of the team's consistency penalty came from pit stops: exactly what the strategy now improves
The situation
In this endurance race the fastest team doesn’t win, the most consistent one does. Each lap’s average speed is calculated, and the score is how much those speeds vary over the whole 8 hours (standard deviation, km/h). Every lap counts, including the ones with a pit stop and a driver change inside them. So the driver order and the speed of the changes matter as much as how well anyone drives.
The team had the track and the cars but lacked two things. First, a tool that tells the driver in real time whether the current lap is on target. Second, data to decide who drives when and for how long. I built both on my own: an Android app that reads a separate GPS unit, predicts the end of the lap and sends everything to the cloud, and an analysis that works out the strategy from there.
The first real test was an 8-hour race at the Audru circuit in April 2026. Many of the solutions below came straight from what didn’t work there or in testing before it.
Constraints
- The phone's own GPS updates about once a second and jumps around too much to know at which fraction of a second the car crossed the finish line.
- The app has to run for hours with the screen off, without the phone switching off location in the background.
- The phone has to last a full 8-hour race day, including long stretches parked in the pits.
- Track length and shape can't be hard-coded. The app has to work on any track.
- Different cars report different engine data. A wrong request makes reading slow or returns nothing at all.
Solution
How I solved it
Accuracy
How do you get the finish-line crossing to a fraction of a second when the phone's GPS updates rarely and isn't precise enough?
The phone’s own GPS gives about one point a second. At 144 km/h (40 m/s) the points are 40 metres apart and the finish line can be anywhere between them, so the error can be a whole second. That’s why the app connects over USB to a separate GPS unit (u-blox) and reads its data directly, 10 times a second. Then the points are 4 metres and 0.1 seconds apart. Every data line is checked, corrupted lines are thrown away immediately and a dropped USB connection recovers on its own.
Even 0.1 seconds is too much to simply take the time of the first point past the line. So the app calculates the moment of crossing from two points: the last point before the line (A) and the first point after it (B). It works in four steps.
1. Was the line crossed? For every new point B the app takes the segment from the previous point A to B and checks whether it crosses the finish line. The line is extended about 15 metres on each side so a little sideways GPS drift doesn’t make it miss a crossing. Optionally the direction of travel is checked too, so crossing the wrong way doesn’t end a lap. There have to be at least 25 seconds between two crossings, so GPS jumping around near the line can’t count one lap as two.
2. Where exactly was the line? The app works out what percentage of the way from A to B the line sits. In the second figure A is 2.8 m before the line and B is 1.2 m after it, 4 m in total. The line is 70% of the way, or 2.8 metres from A.
3. When did the car get there? The GPS unit measures speed itself, from the frequency shift of the radio signal rather than from the gap between positions, and that speed is far more precise than the position. With 40 m/s at A, the car reached the line 2.8 / 40 = 0.070 seconds after A. Just taking B’s time would make that lap 30 ms off.
4. Is the result believable? GPS positions are noisy. If the points look further apart than the car actually drove, the time worked out from speed can land outside A and B: in the third figure it comes to 120 ms, although B was measured only 100 ms after A. The app doesn’t trust a result like that and falls back to the percentage: the line was 80% of the way, so 80% × 100 ms = 80 ms. The same fallback is used when the unit reported no speed or the car was almost standing still.
Finally the antenna position is corrected. The antenna sits on the roof, but timing measures the car’s nose. The gap between them goes into the settings in millimetres and the app subtracts the matching time: 2 metres at 40 m/s is 50 ms. Time comes from the phone’s clock that counts from when the phone started and never jumps, even when the phone corrects its time from the network. A lap time is the difference between two consecutive crossings.
How accurate was it for real? At the Audru race I compared the app’s lap times with official timing. For the 182 laps that could be matched one to one, the median difference was 56 ms and 92% of laps were within 0.5 seconds. The bigger differences often came in pairs: when one crossing was measured 1.3 s late, that lap came out 1.3 s long and the next one almost exactly as much short. There was a real miss too: once a crossing wasn’t detected and two laps became one. In total the app logged 189 laps and official timing 191, because around one pit stop the two systems also split the laps differently.
Prediction
The first version predicted the end of the lap as if the car drove the whole lap at one speed. It was off by up to 30 seconds.
What didn’t work: the old formula divided distance covered by speed. It had three flaws at once. A track isn’t driven at a steady speed: 120 on the straight, 40 in the corner. The track length (3,200 m) was hard-coded. And distance summed up from GPS drifts during a lap, so “60% done” might really be 55% or 65%. The result was off by up to ±30 seconds.
How it works now: each driver has their own reference lap, stored as 200 evenly spaced points. For every new GPS point the app finds the nearest spot on the reference lap and looks up how long the reference took to get there. The difference between the current time and the reference time is exactly how far ahead or behind the driver is.
The search only moves forward, in a narrow window (20 points), so it can’t jump to the wrong part of the track where the circuit passes close to itself. The window also moves on with time: if the car is stuck in traffic, the prediction doesn’t freeze. The target accuracy of the new logic is ±2 seconds.
Traffic
How do you stop one slow lap (traffic, a cold lap after a change) from spoiling the reference, while still showing the driver their real time honestly?
The prediction never freezes, not even in traffic. The driver always sees honestly how much the lap is dragging. But a lap driven in traffic must not change the reference. A lap counts as a traffic lap when the car spent more than 15% of it well below its normal pace (under 60% of the reference speed).
Only “clean” laps update the reference: not the out lap from the pits, not a traffic lap and not a lap more than 25% away from the driver’s average. A new clean lap moves the reference by 30%, and after ten laps by only 15%. That way the reference shows the driver’s normal pace, not their best or worst lap. Small GPS jitter is smoothed with a weighted average of the last five points.
When a driver has no reference of their own yet, the app uses a stored lap from that track or another driver’s lap scaled to this driver’s pace. So the prediction works straight away, not only after several laps have been collected.
Bug fix
In testing no reference lap got built at all and the prediction stayed empty.
What didn’t work: lap recording had a quality filter that threw out every point with GPS accuracy worse than 20 metres. Sensible on paper, but on a real track it threw out so many points that too little of the lap was left and the search found nothing. The filter made the data “cleaner” and the prediction useless.
How it works now: the limit is 50 metres, because resampling to 200 points and smoothing remove small noise anyway. There’s now one out lap that isn’t used as a reference instead of two, so the reference appears a lap earlier. I found a second bug too: if an empty, 0-point reference lap had ever been saved, the app crashed loading it. Now such a lap isn’t loaded and the app builds a new one.
Live monitoring
The pit crew wants to see lap times, GPS status and the current driver live, without walking to the track.
The app sends every lap start and finish, running time, speed and GPS status to the cloud straight away. A web-based pit wall shows it in the browser, together with a live consistency ranking. When it’s time to call the driver in, the app plays the driver a sound. Driver and session names can be edited on the web and the change syncs back to the phone.
Eight hours produces a lot of data, so each message is kept as small as possible: only what changed is sent. A separate script reads competitors’ lap times from the race’s public timing every 15 seconds and writes them to the same place, so the pit wall shows the other teams too.
Competitors’ lap times also give away their strategy. A lap under 40 km/h means a pit stop, so the pit wall shows how many driver changes each team has made and how many they probably still have to go. On the first race the two closest rivals clearly took different routes: the winner made one pit stop, the nearest rival three, but drove the most laps.
Car data
How do you read the car's own data (RPM, coolant temperature, voltage) when different cars report different things?
The app connects over Bluetooth to an adapter in the car’s diagnostic port (OBD-II) on its own, without anyone setting up the connection by hand. It first asks the car which data it supports at all. After that it only asks for what actually returns an answer, instead of waiting on every missing value. That keeps the update rate high even on cars that report little.
Analysis
The first 8-hour race ended at 11.5 km/h. Where did that variation actually come from?
The data from 191 laps showed something no one would have seen by eye. Without the pit laps the team’s score would have been 5.1 km/h. So pit stops caused 56% of all the damage. Seven driver changes produced 12 slow laps (some changes spilled over two laps), and the three slowest laps (6:49, 5:13 and 5:06) accounted for most of it on their own.
The reason is in the scoring maths. Standard deviation squares every deviation: twice the deviation costs four times as much. One very slow lap does more damage than ten slightly slow ones. The winner confirmed it: they made only one pit stop in the whole race and scored 6.5 km/h.
That gave the first concrete rule: every change has to fit within 2:30 to 3:00. Not by rushing, but by a practised routine.
In numbers it looks like this. If the team’s average lap speed is 81 km/h and a pit lap averages 30 km/h, that lap’s “penalty” is (30 − 81)² = 2,601. Drive the pit lap at 60 km/h and the penalty is (60 − 81)² = 441, 83% smaller, at every driver change. On the first race, the three slowest pit laps alone produced 7,893 of those units, most of the team’s total.
Strategy
How do you decide from data in what order and for how long each driver drives?
The gap in consistency between drivers was bigger than anyone thought. The most consistent driver was five times steadier than the least consistent: a lap-time spread of 1.9 seconds versus 10.1 seconds. The less consistent driver’s data showed a clear pattern: they started with 1:39 to 1:43 laps and then dropped to 1:53 to 1:55. The problem wasn’t speed, it was starting too fast. The fix is a target range the app shows the driver as they go.
Each driver’s target is their own normal pace (the median), not the fastest driver’s. The gap between the fastest and slowest was 13.6 seconds a lap (10.1 km/h), and one race won’t change that. Consistency within each driver’s own pace can change.
Based on this data the plan is: the most consistent driver starts and finishes, 8 stints and 7 driver changes in total. The simulation puts this plan at about 6.0 to 7.0 km/h, instead of the previous 11.5. There was hardly any fatigue in the data: only one stint slowed towards the end (+3.9 seconds), the others actually got faster as drivers warmed up.
Track conditions
When lap times slow down, did the driver get tired or did the track change?
Without the answer you might swap a driver for nothing, which means yet another pit lap. So the analysis compares every team’s pace all the time. If more than 60% of teams slow down by more than 3% at once, the cause is the track, not the driver.
At the first race this showed clearly twice: at 1 p.m. all teams slowed by 8.8 km/h on average, and at 3 p.m. by 11.9 km/h, most likely because of rain. If the same happens next time, the pit wall shows straight away that the driver is fine.
Result
At the first real endurance race the app timed laps for 8 hours straight, with a median difference of 56 ms from official timing. The analysis turned the team’s gut feeling into numbers: 56% of the consistency loss came from pit stops, the least consistent driver’s problem was starting too fast, and the two big slowdowns came from the track, not the drivers.
That produced concrete rules for the next race: changes in 2:30 to 3:00, each driver with their own target range, and a driver order whose simulated score is about 6 to 7 km/h instead of the previous 11.5.
What's next
The next race puts the plan to a real test: does the simulated 6 to 7 km/h hold up on track? After that I’ll compare the result with the simulation and fix the model where it was wrong.