DovesLapTimer 4.0.0
GPS-based lap timing Arduino library — go-karts to race cars
Loading...
Searching...
No Matches
DovesLapTimer.h
Go to the documentation of this file.
1
9#ifndef _DOVES_LAP_TIMER_H
10#define _DOVES_LAP_TIMER_H
11
12#include "ArxTypeTraits.h"
13using TRITYPE = double;
14
15#define CROSSING_LINE_SIDE_A -1
16#define CROSSING_LINE_SIDE_EXACT 0
17#define CROSSING_LINE_SIDE_B 1
18
19// Course detection constants
20#define COURSE_DETECT_SPEED_THRESHOLD_MPH 20
21#define COURSE_DETECT_WAYPOINT_PROXIMITY_METERS 10.0
22#define COURSE_DETECT_MIN_DISTANCE_METERS 200.0
23#define COURSE_DETECT_DISTANCE_TOLERANCE_PCT 0.25
24#define COURSE_DETECT_MAX_REJECTIONS 3
25#define METERS_TO_FEET 3.28084
26
27// Waypoint lap timer constants
28#define WAYPOINT_LAP_MIN_DISTANCE_METERS 100.0
29#define WAYPOINT_LAP_PROXIMITY_METERS 30.0
30#define WAYPOINT_LAP_BUFFER_SIZE 50
31
32// Direction detection
33#define DIR_UNKNOWN 0
34#define DIR_FORWARD 1
35#define DIR_REVERSE 2
36
37// Course detection states
38#define DETECT_STATE_IDLE 0
39#define DETECT_STATE_WAITING_FOR_SPEED 1
40#define DETECT_STATE_WAYPOINT_SET 2
41#define DETECT_STATE_CANDIDATES_READY 3
42#define DETECT_STATE_DETECTED 4
43
44// Maximum courses supported
45#define MAX_COURSES 8
46
47// Outcome of a single _detectLineCrossing pass.
49 LINE_DETECT_NONE, // not in / near the crossing zone
50 LINE_DETECT_IN_ZONE, // inside the zone (entered this fix or continuing)
51 LINE_DETECT_COMPLETED, // exited the zone this fix — interpolated outputs valid
52};
53
54
56 double lat; // latitude
57 double lng; // longitude
58 unsigned long time; // current time in milliseconds
59 float odometer; // time traveled since device start and this entry
60 float speedKmh; // speed in kmph
61};
62
64 int direction; // DIR_UNKNOWN, DIR_FORWARD, DIR_REVERSE
66 // Per-lap window: timestamps of physical S2/S3 crossings since the last
67 // start/finish. Direction is decided only when BOTH are present, by their
68 // temporal order — independent of the lap calculation's sector output.
69 unsigned long lapS2CrossingTime;
70 unsigned long lapS3CrossingTime;
71
75 void reset() {
77 raceSeen = false;
80 }
81 void onLineCrossing(int sectorNumber, unsigned long crossingTime);
82 bool isReverse() const { return direction == DIR_REVERSE; }
83 bool isResolved() const { return direction != DIR_UNKNOWN; }
84};
85
87public:
88 DovesLapTimer(double crossingThresholdMeters = 7, Stream *debugSerial = NULL);
89
101 int loop(double currentLat, double currentLng, float currentAltitudeMeters, float currentSpeedKnots);
102
104
116 bool isObtuseTriangle(double lat1, double lon1, double lat2, double lon2, double lat3, double lon3);
117
132 bool insideLineThreshold(double driverLat, double driverLon, double crossingPointALat, double crossingPointALon, double crossingPointBLat, double crossingPointBLon);
133
148 int pointOnSideOfLine(double driverLat, double driverLng, double pointALat, double pointALng, double pointBLat, double pointBLng);
166 double pointLineSegmentDistance(double pointX, double pointY, double startX, double startY, double endX, double endY);
182 double haversine(double lat1, double lon1, double lat2, double lon2);
198 double haversine3D(double prevLat, double prevLng, double prevAlt, double currentLat, double currentLng, double currentAlt);
199
201
205 void reset();
214 void setStartFinishLine(double pointALat, double pointALng, double pointBLat, double pointBLng);
223 void setSector2Line(double pointALat, double pointALng, double pointBLat, double pointBLng);
232 void setSector3Line(double pointALat, double pointALng, double pointBLat, double pointBLng);
238 void updateCurrentTime(unsigned long currentTimeMilliseconds);
260 bool getRaceStarted() const;
266 bool getCrossing() const;
272 unsigned long getCurrentLapStartTime() const;
278 unsigned long getCurrentLapTime() const;
284 unsigned long getLastLapTime() const;
290 unsigned long getBestLapTime() const;
296 float getCurrentLapOdometerStart() const;
302 float getCurrentLapDistance() const;
308 float getLastLapDistance() const;
314 float getBestLapDistance() const;
320 float getTotalDistanceTraveled() const;
326 int getBestLapNumber() const;
332 int getLaps() const;
340 float getPaceDifference() const;
341
343 // Sector timing methods
344
350 unsigned long getBestSector1Time() const;
356 unsigned long getBestSector2Time() const;
362 unsigned long getBestSector3Time() const;
368 unsigned long getCurrentLapSector1Time() const;
374 unsigned long getCurrentLapSector2Time() const;
380 unsigned long getCurrentLapSector3Time() const;
386 unsigned long getOptimalLapTime() const;
392 int getBestSector1LapNumber() const;
398 int getBestSector2LapNumber() const;
404 int getBestSector3LapNumber() const;
410 int getCurrentSector() const;
416 bool areSectorLinesConfigured() const;
417
419 // Direction detection methods
420
426 int getDirection() const;
432 bool isDirectionResolved() const;
433
434private:
435 template<typename... Args>
436 void debug_print(Args&&... args) {
437 if(_serial) {
438 _serial->print(std::forward<Args>(args)...);
439 }
440 }
441 template<typename... Args>
442 void debug_println(Args&&... args) {
443 if(_serial) {
444 _serial->println(std::forward<Args>(args)...);
445 }
446 }
447
461 bool checkStartFinish(double currentLat, double currentLng);
475 bool checkSectorLine(double currentLat, double currentLng, double pointALat, double pointALng, double pointBLat, double pointBLng, bool& crossingFlag, int sectorNumber);
490 LineDetectResult _detectLineCrossing(
491 double currentLat, double currentLng,
492 double pointALat, double pointALng,
493 double pointBLat, double pointBLng,
494 bool& crossingFlag,
495 int lineLabel,
496 double& outLat, double& outLng,
497 unsigned long& outTime,
498 double& outOdometer);
505 void handleLineCrossing(unsigned long crossingTime, int sectorNumber);
509 void updateBestSectors();
520 double catmullRom(double p0, double p1, double p2, double p3, double t);
530 double interpolateWeight(double distA, double distB, float speedA, float speedB);
547 void interpolateCrossingPoint(double& crossingLat, double& crossingLng, unsigned long& crossingTime, double& crossingOdometer, double pointALat, double pointALng, double pointBLat, double pointBLng);
548
549 Stream *_serial;
550 DirectionDetector _directionDetector;
551
552 unsigned long millisecondsSinceMidnight = 0;
553 // Timing variables
554 double crossingThresholdMeters;
555 bool raceStarted = false;
556 bool crossing = false;
557 bool forceLinear = true;
558 unsigned long currentLapStartTime = 0;
559 unsigned long lastLapTime = 0;
560 unsigned long bestLapTime = 0;
561 float currentLapOdometerStart = 0.0;
562 float lastLapDistance = 0.0;
563 float bestLapDistance = 0.0;
564 float currentSpeedkmh = 0.0;
565 int bestLapNumber = 0;
566 int laps = 0;
567
568 // Sector timing state
569 int currentSector = 0; // 0=not started, 1/2/3=in sector
570 unsigned long currentSectorStartTime = 0;
571 bool crossingSector2 = false;
572 bool crossingSector3 = false;
573
574 // Current lap sector times (reset each lap)
575 unsigned long currentLapSector1Time = 0;
576 unsigned long currentLapSector2Time = 0;
577 unsigned long currentLapSector3Time = 0;
578
579 // Best sector times (persistent across laps)
580 unsigned long bestSector1Time = 0;
581 unsigned long bestSector2Time = 0;
582 unsigned long bestSector3Time = 0;
583
584 // Lap numbers that achieved best sectors
585 int bestSector1LapNumber = 0;
586 int bestSector2LapNumber = 0;
587 int bestSector3LapNumber = 0;
588
589 float totalDistanceTraveled = 0;
590 float positionPrevAlt = 0.00;
591 double positionPrevLat = 0.00;
592 double positionPrevLng = 0.00;
593 bool firstPositionReceived = false; // Explicit flag for first GPS fix detection
594
595 // Previous GPS fix snapshot (used as Catmull-Rom pre-crossing control point)
596 double prevFixLat = 0;
597 double prevFixLng = 0;
598 unsigned long prevFixTime = 0;
599 float prevFixOdometer = 0;
600 float prevFixSpeedKmh = 0;
601 bool hasPrevFix = false;
602
603 double startFinishPointALat;
604 double startFinishPointALng;
605 double startFinishPointBLat;
606 double startFinishPointBLng;
607
608 // Sector 2 line coordinates
609 double sector2PointALat;
610 double sector2PointALng;
611 double sector2PointBLat;
612 double sector2PointBLng;
613
614 // Sector 3 line coordinates
615 double sector3PointALat;
616 double sector3PointALng;
617 double sector3PointBLat;
618 double sector3PointBLng;
619
620 // Sector line configuration flags
621 bool sector2LineConfigured = false;
622 bool sector3LineConfigured = false;
623
624 // Earth's radius in meters
625 static constexpr double radiusEarth = 6371.0 * 1000;
626
627 // Buffer sizing: AVR exposes RAMEND/RAMSTART so we can tell a Mega (8KB) from a Uno (2KB).
628 // On modern 32-bit cores (nRF52, ESP32, SAMD, RP2040, etc.) those macros are undefined
629 // and would silently evaluate to 0 - defaulting such targets to the small buffer would
630 // defeat the whole point of the hotfix. Assume anyone not on classic AVR has plenty of RAM.
631 #if defined(RAMEND) && defined(RAMSTART)
632 #if ((RAMEND - RAMSTART) > 3000)
633 static const int crossingPointBufferSize = 100;
634 #else
635 static const int crossingPointBufferSize = 25;
636 #endif
637 #else
638 static const int crossingPointBufferSize = 100;
639 #endif
640
641 crossingPointBufferEntry crossingPointBuffer[crossingPointBufferSize];
642 int crossingPointBufferIndex = 0;
643 bool crossingPointBufferFull = false;
644};
645
646#endif
double TRITYPE
Definition DovesLapTimer.h:13
#define DIR_REVERSE
Definition DovesLapTimer.h:35
#define DIR_UNKNOWN
Definition DovesLapTimer.h:33
LineDetectResult
Definition DovesLapTimer.h:48
@ LINE_DETECT_IN_ZONE
Definition DovesLapTimer.h:50
@ LINE_DETECT_NONE
Definition DovesLapTimer.h:49
@ LINE_DETECT_COMPLETED
Definition DovesLapTimer.h:51
const double crossingPointBLat
Definition basic_oled_example.ino:21
const double crossingPointALat
Definition basic_oled_example.ino:19
void loop()
Definition basic_oled_example.ino:116
Definition DovesLapTimer.h:86
unsigned long getBestSector2Time() const
Gets the best sector 2 time.
Definition DovesLapTimer.cpp:843
unsigned long getLastLapTime() const
Gets the last lap time.
Definition DovesLapTimer.cpp:792
unsigned long getCurrentLapStartTime() const
Gets the current lap start time.
Definition DovesLapTimer.cpp:786
double pointLineSegmentDistance(double pointX, double pointY, double startX, double startY, double endX, double endY)
Calculate the shortest distance between a point and a line segment.
Definition DovesLapTimer.cpp:336
void updateCurrentTime(unsigned long currentTimeMilliseconds)
Updates the current GPS time since midnight.
Definition DovesLapTimer.cpp:771
unsigned long getOptimalLapTime() const
Gets the optimal lap time calculated from best sector times.
Definition DovesLapTimer.cpp:858
float getTotalDistanceTraveled() const
Gets the total distance traveled.
Definition DovesLapTimer.cpp:810
void forceLinearInterpolation()
forces linear interpolation when checking crossing line
Definition DovesLapTimer.cpp:774
float getLastLapDistance() const
Gets the last lap distance.
Definition DovesLapTimer.cpp:804
int getBestSector1LapNumber() const
Gets the lap number that achieved the best sector 1 time.
Definition DovesLapTimer.cpp:865
unsigned long getCurrentLapTime() const
Gets the current lap time.
Definition DovesLapTimer.cpp:789
float getPaceDifference() const
Calculates the pace difference between the current lap and the best lap in milliseconds.
Definition DovesLapTimer.cpp:819
bool getRaceStarted() const
Gets the race started status (passed the line one time).
Definition DovesLapTimer.cpp:780
int getDirection() const
Gets the detected driving direction.
Definition DovesLapTimer.cpp:883
unsigned long getCurrentLapSector2Time() const
Gets the current lap sector 2 time.
Definition DovesLapTimer.cpp:852
void reset()
Reset all parameters back to 0.
Definition DovesLapTimer.cpp:698
void setSector2Line(double pointALat, double pointALng, double pointBLat, double pointBLng)
Sets the sector 2 line using two points (A and B).
Definition DovesLapTimer.cpp:757
int getBestSector3LapNumber() const
Gets the lap number that achieved the best sector 3 time.
Definition DovesLapTimer.cpp:871
bool isObtuseTriangle(double lat1, double lon1, double lat2, double lon2, double lat3, double lon3)
Checks if the triangle formed by the given coordinates is an obtuse triangle.
Definition DovesLapTimer.cpp:288
bool insideLineThreshold(double driverLat, double driverLon, double crossingPointALat, double crossingPointALon, double crossingPointBLat, double crossingPointBLon)
Check if a driver is within a threshold distance of the line formed by the two crossing points.
Definition DovesLapTimer.cpp:262
bool getCrossing() const
Gets the crossing status.
Definition DovesLapTimer.cpp:783
int pointOnSideOfLine(double driverLat, double driverLng, double pointALat, double pointALng, double pointBLat, double pointBLng)
Determines which side of a line a driver is on.
Definition DovesLapTimer.cpp:318
unsigned long getBestSector1Time() const
Gets the best sector 1 time.
Definition DovesLapTimer.cpp:840
void setSector3Line(double pointALat, double pointALng, double pointBLat, double pointBLng)
Sets the sector 3 line using two points (A and B).
Definition DovesLapTimer.cpp:764
int getBestLapNumber() const
Gets the best lap number.
Definition DovesLapTimer.cpp:813
unsigned long getBestSector3Time() const
Gets the best sector 3 time.
Definition DovesLapTimer.cpp:846
float getCurrentLapOdometerStart() const
Gets the current lap odometer start.
Definition DovesLapTimer.cpp:798
unsigned long getCurrentLapSector1Time() const
Gets the current lap sector 1 time.
Definition DovesLapTimer.cpp:849
void setStartFinishLine(double pointALat, double pointALng, double pointBLat, double pointBLng)
Sets the start/finish line using two points (A and B).
Definition DovesLapTimer.cpp:751
bool isDirectionResolved() const
Checks if the driving direction has been resolved.
Definition DovesLapTimer.cpp:886
unsigned long getBestLapTime() const
Gets the best lap time.
Definition DovesLapTimer.cpp:795
void forceCatmullRomInterpolation()
Forces Catmull-Rom spline interpolation when checking crossing line.
Definition DovesLapTimer.cpp:777
float getBestLapDistance() const
Gets the best lap distance.
Definition DovesLapTimer.cpp:807
float getCurrentLapDistance() const
Gets the current lap distance.
Definition DovesLapTimer.cpp:801
double haversine3D(double prevLat, double prevLng, double prevAlt, double currentLat, double currentLng, double currentAlt)
Calculates the distance between two GPS points, including altitude difference.
Definition DovesLapTimer.cpp:371
unsigned long getCurrentLapSector3Time() const
Gets the current lap sector 3 time.
Definition DovesLapTimer.cpp:855
bool areSectorLinesConfigured() const
Checks if sector lines are configured.
Definition DovesLapTimer.cpp:877
int getLaps() const
Gets the total number of laps completed.
Definition DovesLapTimer.cpp:816
int getBestSector2LapNumber() const
Gets the lap number that achieved the best sector 2 time.
Definition DovesLapTimer.cpp:868
double haversine(double lat1, double lon1, double lat2, double lon2)
Calculates the great-circle distance between two points on the Earth's surface using the Haversine fo...
Definition DovesLapTimer.cpp:367
int getCurrentSector() const
Gets the current sector the driver is in.
Definition DovesLapTimer.cpp:874
Definition DovesLapTimer.h:63
DirectionDetector()
Definition DovesLapTimer.h:72
void reset()
Definition DovesLapTimer.h:75
unsigned long lapS2CrossingTime
Definition DovesLapTimer.h:69
void onLineCrossing(int sectorNumber, unsigned long crossingTime)
Definition DovesLapTimer.cpp:524
bool isReverse() const
Definition DovesLapTimer.h:82
bool raceSeen
Definition DovesLapTimer.h:65
unsigned long lapS3CrossingTime
Definition DovesLapTimer.h:70
int direction
Definition DovesLapTimer.h:64
bool isResolved() const
Definition DovesLapTimer.h:83
Definition DovesLapTimer.h:55
double lng
Definition DovesLapTimer.h:57
float speedKmh
Definition DovesLapTimer.h:60
unsigned long time
Definition DovesLapTimer.h:58
float odometer
Definition DovesLapTimer.h:59
double lat
Definition DovesLapTimer.h:56