Skip to content

Commit dd01e1e

Browse files
Merge pull request #11 from TheAngryRaven/code_review_asn83
Code review: fix interpolation bugs, license, and cleanup
2 parents fcec3ef + 9302a47 commit dd01e1e

7 files changed

Lines changed: 588 additions & 319 deletions

File tree

.gitignore

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# IDE and editor files
2+
.vscode/
3+
.idea/
4+
*.swp
5+
*.swo
6+
*~
7+
8+
# OS files
9+
.DS_Store
10+
Thumbs.db
11+
12+
# Build artifacts
13+
build/
14+
*.o
15+
*.elf
16+
*.hex
17+
*.bin

CLAUDE.md

Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
# DovesLapTimer - Project Guide
2+
3+
> **IMPORTANT**: Always keep this CLAUDE.md file updated when making changes to the codebase.
4+
> When adding new features, modifying APIs, fixing bugs, or changing architecture, update the
5+
> relevant sections below so future sessions don't waste tokens re-exploring the project.
6+
7+
## Project Overview
8+
9+
**What**: GPS-based lap timing Arduino library for go-kart / racing applications.
10+
**Author**: Michael Champagne (crimsondove)
11+
**Version**: 3.1.1
12+
**Repo**: https://github.com/TheAngryRaven/DovesLapTimer
13+
**License**: GPL v3
14+
**Dependency**: ArxTypeTraits (auto-included by Arduino Library Manager)
15+
16+
The library does NOT interface with GPS hardware directly. You feed it coordinates and time,
17+
it handles crossing detection, lap timing, sector splits, distance tracking, and pace comparison.
18+
19+
## Directory Structure
20+
21+
```
22+
DovesLapTimer/
23+
├── src/
24+
│ ├── DovesLapTimer.h # Header - class definition, structs, public API (535 lines)
25+
│ └── DovesLapTimer.cpp # Implementation - all logic (929 lines)
26+
├── examples/
27+
│ ├── basic_oled_example/ # Full working example with OLED + uBlox GPS
28+
│ │ ├── basic_oled_example.ino
29+
│ │ ├── display_config.h # OLED init (SSD1306 or SH110X)
30+
│ │ ├── gps_config.h # uBlox binary config commands
31+
│ │ └── images.h # Bitmap data for UI
32+
│ ├── sector_timing_example/ # Demonstrates 3-sector timing
33+
│ │ └── sector_timing_example.ino
34+
│ └── real_track_data_debug/ # Replays real NMEA data (no GPS needed)
35+
│ ├── real_track_data_debug.ino
36+
│ ├── gps_race_data_2laps.h
37+
│ ├── gps_race_data_lap.h
38+
│ ├── gps_race_data_long_lap.h
39+
│ └── gps_race_data_praga_laps.h
40+
├── wokwi/ # Wokwi simulator custom GPS chip
41+
│ ├── gps-fake.chip.c
42+
│ └── gps-fake.chip.json
43+
├── images/ # Diagrams for HELPME.md
44+
├── library.properties # Arduino library metadata
45+
├── README.md # User-facing docs and API reference
46+
├── HELPME.md # Technical deep-dive on crossing detection algorithm
47+
├── LICENSE # GPL v3 (conflicts with library.properties MIT claim)
48+
└── .github/FUNDING.yml
49+
```
50+
51+
## Architecture & Key Concepts
52+
53+
### Core Class: `DovesLapTimer`
54+
55+
**Constructor**: `DovesLapTimer(double crossingThresholdMeters = 7, Stream *debugSerial = NULL)`
56+
57+
### Main Loop Flow
58+
1. User calls `updateCurrentTime(millisecondsSinceMidnight)` with GPS time
59+
2. User calls `loop(lat, lng, altMeters, speedKnots)` on each GPS fix
60+
3. `loop()` updates odometer (haversine3D), speed, then checks all crossing lines
61+
4. `checkStartFinish()` and `checkSectorLine()` handle detection + interpolation
62+
63+
### Crossing Detection Algorithm (the hard part)
64+
- Uses **hypotenuse-based threshold** (not simple distance-to-line)
65+
- Calculates hypotenuse from `crossingLineWidth` and `crossingThresholdMeters`
66+
- Checks driver distance to EACH endpoint of crossing line vs hypotenuse
67+
- When inside threshold: buffers GPS points in `crossingPointBuffer`
68+
- When exiting threshold: calls `interpolateCrossingPoint()` to find exact crossing
69+
70+
### Interpolation Methods
71+
- **Linear** (default, `forceLinear = true`): distance+speed weighted interpolation
72+
- **Catmull-Rom spline**: uses 4 control points, falls back to linear if insufficient points
73+
- Known issue: Catmull-Rom has a bug (noted in basic_oled_example setup comment)
74+
75+
### Sector Timing
76+
- 3 sectors: Start/Finish -> Sector2 -> Sector3 -> Start/Finish
77+
- Requires both sector 2 and sector 3 lines to be configured
78+
- Validates crossing order (rejects out-of-order crossings)
79+
- Tracks best sector times and lap numbers independently
80+
81+
### Memory Management
82+
- Circular buffer `crossingPointBuffer` sized by available RAM:
83+
- `>3000 bytes RAM`: 100 entries
84+
- `<=3000 bytes RAM`: 25 entries
85+
- Buffer is shared between all line crossings (start/finish + sectors)
86+
- Mutual exclusion: only one line can be "crossing" at a time
87+
88+
## Public API Quick Reference
89+
90+
### Setup Methods
91+
| Method | Description |
92+
|--------|-------------|
93+
| `setStartFinishLine(aLat, aLng, bLat, bLng)` | Define start/finish line |
94+
| `setSector2Line(aLat, aLng, bLat, bLng)` | Define sector 2 split line |
95+
| `setSector3Line(aLat, aLng, bLat, bLng)` | Define sector 3 split line |
96+
| `updateCurrentTime(millisSinceMidnight)` | Set GPS time (call before loop) |
97+
| `forceLinearInterpolation()` | Use linear interpolation (default) |
98+
| `forceCatmullRomInterpolation()` | Use Catmull-Rom spline (has known issues) |
99+
| `reset()` | Reset all state to zero |
100+
101+
### Timing Getters
102+
| Method | Returns |
103+
|--------|---------|
104+
| `getCurrentLapTime()` | Current lap elapsed ms |
105+
| `getLastLapTime()` | Last completed lap ms |
106+
| `getBestLapTime()` | Best lap ms |
107+
| `getCurrentLapSector1Time()` | Current lap S1 ms |
108+
| `getCurrentLapSector2Time()` | Current lap S2 ms |
109+
| `getCurrentLapSector3Time()` | Current lap S3 ms |
110+
| `getBestSector1Time()` | Best S1 ms (any lap) |
111+
| `getBestSector2Time()` | Best S2 ms (any lap) |
112+
| `getBestSector3Time()` | Best S3 ms (any lap) |
113+
| `getOptimalLapTime()` | Sum of best sectors |
114+
115+
### State/Distance Getters
116+
| Method | Returns |
117+
|--------|---------|
118+
| `getRaceStarted()` | True after first line crossing |
119+
| `getCrossing()` | True while inside crossing zone |
120+
| `getLaps()` | Completed lap count |
121+
| `getBestLapNumber()` | Lap # of best time |
122+
| `getCurrentSector()` | 0=not started, 1/2/3 |
123+
| `areSectorLinesConfigured()` | True if both sector lines set |
124+
| `getCurrentLapDistance()` | Current lap meters |
125+
| `getLastLapDistance()` | Last lap meters |
126+
| `getBestLapDistance()` | Best lap meters |
127+
| `getTotalDistanceTraveled()` | Odometer meters |
128+
| `getPaceDifference()` | Pace delta vs best lap |
129+
130+
### Public Utility Methods (also used by examples for display)
131+
| Method | Description |
132+
|--------|-------------|
133+
| `insideLineThreshold(...)` | Check if point is in crossing detection zone |
134+
| `pointLineSegmentDistance(...)` | Distance from point to line segment (meters) |
135+
| `haversine(lat1, lon1, lat2, lon2)` | Great-circle distance (meters) |
136+
| `haversine3D(...)` | 3D distance including altitude |
137+
| `isObtuseTriangle(...)` | Triangle type check |
138+
| `pointOnSideOfLine(...)` | Which side of line a point is on |
139+
140+
## Known Issues & TODOs (from code comments)
141+
142+
1. ~~**Catmull-Rom interpolation bug**~~: Fixed - spline now only used for lat/lng (not time/odometer), and previous GPS fix inserted as pre-crossing control point
143+
2. **Altitude messing up distance**: `loop()` line 27 has TODO: "I think alt is messing up, investigate more... maybe flag?"
144+
3. **Early abort bug**: checkStartFinish line 148-149 TODO about aborting early causing re-check immediately
145+
4. **`checkStartFinish` portability**: Line 106 TODO to make more portable for split timing
146+
5. ~~**License mismatch**~~: Resolved - GPL v3, library.properties updated
147+
6. ~~**Header comment outdated**~~: Fixed - updated to mention 3-sector timing
148+
7. ~~**No keywords.txt**~~: Added
149+
8. ~~**No .gitignore**~~: Added
150+
151+
## Supported Hardware
152+
153+
- **MCU**: Seeed XIAO nRF52840 (recommended), Arduino Mega+ (minimum)
154+
- **GPS**: Matek SAM-M10Q (25Hz), Matek SAM-M8Q (18Hz) - uBlox modules
155+
- **Display**: 128x64 I2C OLED (SSD1306 or SH110X)
156+
- **GPS Library**: Adafruit_GPS
157+
- **Display Library**: Adafruit GFX + Adafruit_SSD1306 or Adafruit_SH110x
158+
159+
## Testing
160+
161+
Use the `real_track_data_debug` example to replay real NMEA data without GPS hardware.
162+
Track data from Orlando Kart Center is included. Compare results against MyLaps timing.
163+
164+
## Cross-Reference: Related Repos
165+
166+
(Update this section as the other two repos are set up)
167+
- DovesLapTimer (this repo) - Core timing library
168+
- [Add other repos here as they are created]

0 commit comments

Comments
 (0)