Skip to content

FEATURE: Service sector detection (pit lane, refuelling, tyre stops) #30

Description

@FabioRoss

What problem are you trying to solve?

Circuit racing — especially endurance karting — involves mandatory service stops:
pit lane entry/exit, refuelling windows, tyre changes. A lap timer that can only
detect the start/finish line and race sectors has no way to:

  • Notify the driver/display when they enter or exit the pit lane
  • Time how long a service stop took
  • Log which type of service was performed

Currently users must maintain separate crossing-line detection logic using the
same underlying haversine/side-of-line math that DovesLapTimer already implements.
This is boilerplate duplication.

The library's crossing detection infrastructure (insideLineThreshold,
pointOnSideOfLine, side-change confirmation) is exactly the right foundation
for service sector detection — it just needs to be exposed for non-race lines.

Proposed solution

Add "service sector" as a first-class line type alongside race sectors.

DATA TYPES:

struct serviceSectorStruct {
  double lat1, lon1;       // line endpoint A
  double lat2, lon2;       // line endpoint B
  bool isEntrance;         // true = entry line, false = exit line
  uint8_t serviceType;     // one of the SERVICE_TYPE_* constants
};

// Type discriminators for sector_crossed_callback context
#define SECTOR_TYPE_RACE     0
#define SECTOR_TYPE_SERVICE  1
#define SECTOR_TYPE_OTHER    2

// Service types
#define SERVICE_TYPE_PITLANE_START   0
#define SERVICE_TYPE_PITLANE_END     1
#define SERVICE_TYPE_FUEL_START      2
#define SERVICE_TYPE_FUEL_END        3
#define SERVICE_TYPE_TYRES_START     4
#define SERVICE_TYPE_TYRES_END       5
#define SERVICE_TYPE_OTHER_START     6
#define SERVICE_TYPE_OTHER_END       7

API:

// Register callback: fires when any service sector line is confirmed crossed
typedef void (*ServiceCallbackFunction)(uint8_t serviceType, bool isEntry);
void setServiceSectorCallback(ServiceCallbackFunction cb);

// Public state readable by display code
bool inServiceSector = false;
unsigned long lastServiceSectorTime = 0;  // ms duration of last completed stop

// Config: max 8 service sectors (avoids heap pressure from unbounded array)
serviceSectorStruct* serviceSector = nullptr;
int numberOfServiceSectors = 0;

DETECTION LOGIC:

For each registered service sector line, per GPS fix:
1. insideLineThreshold() — quick reject if far from line
2. pointOnSideOfLine() — determine which side driver is on
3. Per-sector side history serviceSideLastSeen[8] — confirmed crossing
only when side changes from a non-zero previous side
4. Update inServiceSector and serviceSectorStartTime on entry
5. Compute lastServiceSectorTime = now - serviceSectorStartTime on exit
6. Fire callback(serviceType, isEntrance)

Service sector detection is independent of race sector crossing state —
both can be checked in the same loop() call without interference.

ARCHITECTURE NOTE:
For consistency with the shared _detectLineCrossing() zone state machine
added in v4.1, service sectors should ideally go through the same pipeline.
The main consideration is buffer sharing — service sectors are unlikely to
coincide with race sector crossings, but the architecture should be explicit
about this (separate flags, or confirmed non-overlapping zones in the track
config).

Alternatives considered

  1. User implements their own crossing logic — current workaround. Requires
    duplicating insideLineThreshold, pointOnSideOfLine, and side-history logic.

  2. Treat service sectors as race sectors — mixes race and non-race timing in
    the same sectorTimes[] array, polluting race stats with pit durations.

  3. Separate library — over-engineered for what is ultimately reusing the same
    crossing detection math that already lives in DovesLapTimer.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions