Skip to content

Lap Tracking

The backend runs its own lap counter, independent of the firmware's onboard lap tracker on the Telemetry Node. It works from the GPS fields already present in the CSV stream and needs no CAN frame from the car; the finish line and its thresholds are tunable at runtime from the web dashboard, which is the point of running a second tracker here rather than relying only on the board.

Two lap trackers exist in this system

The Telemetry Node also has a lap counter (lap_app.c; see the Telemetry firmware's Lap Tracking docs, currently work in progress on feature/lap-tracking). It is a separate implementation with separate state, and the two are not synchronized. This backend tracker is what the pit dashboard and Marple's Lap column use today.

Where it runs

analysis/lap_tracker.py consumes analysis_q, fed by udp-receiver whenever the analysis sink is enabled. It only looks at packets with a valid GPS fix (gps_fix > 0); everything else is skipped.

The finish line

The finish line is two GPS points, (finish_lat_1, finish_lon_1) and (finish_lat_2, finish_lon_2), set from .env at startup and updatable live via POST /api/settings.

For every fix, the tracker computes:

  • Side of line: the sign of the 2D cross product of the line vector against the vector to the car's position (-1, 0, or +1).
  • Distance to the line: the perpendicular distance from the car to the finish line segment, clamped to the segment rather than its infinite extension.

Debounce and re-arm

A crossing only counts when all of these hold:

  • The tracker is armed (a lap wasn't just closed on the same side without the car moving away first).
  • The car's side of the line flipped since the last fix.
  • The distance to the line is within line_tolerance_m.
  • At least min_lap_time_s has elapsed since the lap started.
  • The car has covered at least min_lap_distance_m since the lap started.

After a lap closes, the tracker disarms until the car moves at least rearm_distance_m away from the line, so idling on the line (in the pits, under a red flag) can't rack up phantom laps.

Runtime tunables

All adjustable via POST /api/system_state:

ParameterDefaultMeaning
min_lap_time_s20.0Minimum seconds before a lap can close
min_lap_distance_m250.0Minimum metres travelled before a lap can close
rearm_distance_m50.0Distance from the line required to re-arm after a lap
line_tolerance_m15.0How close to the line segment counts as a crossing
force_lap_closefalseClose the current lap immediately, bypassing the gates above
force_session_resetfalseWipe all lap state and start over at lap 0

Finish line coordinates are set separately, via POST /api/settings (finish_lat_1/finish_lon_1/finish_lat_2/finish_lon_2).

Session handling

Lap state is keyed by session (the active sd_filename). When the session changes mid-stream, the tracker writes a lap_reset event for the old session, clears all state, and starts a fresh lap_0_start event for the new one. force_session_reset triggers the same reset on demand, without needing an actual session change.

What gets published

  • analysis/lap_state.py holds a thread-safe LAP_METRICS snapshot (current/last lap time and distance, total distance, lap index) that the web API and the Marple sender both read live.
  • Every closed lap writes a lap_tracking point to INFLUX_ANALYSIS_BUCKET with the lap number, time, distance, and the finish line coordinates used, alongside session_start, lap_0_start, and lap_reset events. See InfluxDB.
  • The Marple sender attaches the current lap_index as a Lap column to every row it sends, whenever the analysis sink is enabled.

Released under the MIT License.