Skip to content

Lap Tracking

The lap thread counts laps by watching the car cross a virtual finish line. You set the line once (at the start/finish, while driving through it), and from then on every crossing in the same direction increments the lap count. The logic lives in lap_app.c.

Feature branch: not fully wired yet

This subsystem lives on feature/lap-tracking. The detection algorithm below is implemented, but three pieces it depends on are not yet defined in the tree:

  • The tunables LAP_MIN_DISTANCE_M, LAP_MIN_TIME_MS, LAP_LINE_HALF_WIDTH_M, LAP_THREAD_STACK_SIZE, LAP_THREAD_PRIORITY (belong in sys_conf.h).
  • The CAN emitters send_lap_completed_can_frame() / send_lap_reset_can_frame().
  • The telemetry lap-domain setters telemetry_lap_count_set() / telemetry_lap_write_begin/_end().

Define these before expecting the node to build and publish a lap count.

How the finish line is defined

Lap tracking is driven entirely by GPS. When a set request comes in (lap_app_request_set()), the thread captures the current fix and freezes a finish line:

  • Center = the car's current lat/lon.
  • Normal = the car's current heading (yaw). The finish line is the segment perpendicular to the direction of travel, so you "break the tape" as you drive straight through it.

The normal is stored as an East/North unit vector (sin(yaw), cos(yaw)).

You need a fix to set the line

If there's no GPS fix at set-time the request is ignored: there's no position to anchor the line to.

How a crossing is detected

On every GPS update (lap_app_notify_gps_update()), the thread takes a snapshot and, for the current position, computes two quantities relative to the finish line:

  • Signed distance along the normal: how far in front of (+) or behind (−) the line you are.
  • Lateral distance along the line: how far sideways from the line's center you are.

Positions are projected to local metres using an equirectangular approximation around the line center (dx = Δlon·cos(lat)·R·π/180, dy = Δlat·R·π/180).

A lap is counted when all of these hold:

  1. Sign flip: the signed distance went from negative on the previous fix to ≥ 0 now (you crossed the line in the correct direction).
  2. Within the line width: the lateral distance is ≤ LAP_LINE_HALF_WIDTH_M (you crossed the actual line segment, not its infinite extension somewhere across the paddock).
  3. Minimum distance: you've travelled at least LAP_MIN_DISTANCE_M since the last lap.
  4. Minimum time: at least LAP_MIN_TIME_MS has elapsed since the last lap.

Conditions 3 and 4 are debounces: they stop a car that's crawling back and forth over the line (in the pits, or under yellow) from racking up phantom laps.

The geometry the test is built on: the crossing happens when the signed distance flips sign while staying within the line's half-width.

signed distance:   ── prev(−) ───┼─── now(+) ──►     crossing candidate
finish line:                     │
                          ◄──────┼──────►  lateral must be ≤ half width

Distance accumulation

Between fixes the thread adds the great-circle (haversine) distance travelled to distance_since_last_lap_m. That running total is what condition 3 checks, and it resets to zero on every counted lap.

Set / reset / count

TriggerEffect
lap_app_request_set()Freeze a new finish line at the current position, reset count to 0, emit a lap-completed frame with count 0
lap_app_request_reset()Clear the finish line and count, emit a reset frame
finish-line crossingIncrement count, reset per-lap distance/time, publish count, emit a lap-completed frame

The request flags are set from elsewhere (e.g. dashboard buttons) and consumed by the lap thread, which is woken through its own semaphore, so requests and GPS updates funnel through the same event loop.

Geometry notes

  • Distances use EARTH_RADIUS_M = 6,371,000 m.
  • The haversine result is clamped (a ≤ 1.0) to stay numerically safe near zero.
  • The equirectangular projection is only used locally around the finish line, where its small-angle error is negligible: much cheaper than a full haversine on every fix for the crossing test.

Released under the MIT License.