IMPORTANT: Keep this file updated when making changes.
A web-based "datalogger simulator" for testing the DovesLapTimer library. The core library has been ported from C++ to JavaScript for rapid development. This will be ported back to C++ when done.
The app is NOT a unit test for crossing detection (that's done on real hardware). It's a functions test - verifying the library API works correctly as we add new features (course detection, automatic laptimes).
- Open
index.htmlin a browser (needs internet for Leaflet tiles) - The map shows Orlando Kart Center with all course lines drawn
- Drag the red dot around the map
- Click "Start" to begin the 25Hz GPS simulation
- When speed >= 20mph a blue waypoint blip appears (course detection starts)
- Complete a lap back past the waypoint - course is auto-detected
- Non-matching course lines are removed, timing continues for the detected course
- Click "Stop" to pause (downloads .dove log file), "Reset" to clear all data
- Paste track JSON and click "Apply" to change tracks
DovesSchizoTest/
├── index.html # Single-page app entry
├── serve.bat # Local dev server (python -m http.server)
├── css/
│ └── styles.css # All styling
├── js/
│ ├── app.js # Main entry - wires everything together
│ ├── lib/ # *** THE LIBRARY PORT ***
│ │ ├── constants.js # #defines and constants (incl. course detection)
│ │ ├── geo-math.js # haversine, haversine3D, pointLineSegmentDistance
│ │ ├── crossing-detection.js # isObtuseTriangle, insideLineThreshold, pointOnSideOfLine
│ │ ├── interpolation.js # catmullRom, interpolateWeight, interpolateCrossingPoint
│ │ ├── DovesLapTimer.js # Main class - faithful port of the C++ class
│ │ ├── direction-detector.js # Forward/reverse direction detection
│ │ ├── course-detector.js # Course detection state machine (ranked matching)
│ │ ├── course-manager.js # Multi-course timer orchestrator + sanity check
│ │ └── waypoint-lap-timer.js # Universal fallback lap timer ("Lap Anything")
│ ├── sim/
│ │ ├── gps-simulator.js # 25Hz loop that feeds any duck-typed target
│ │ ├── track-data.js # Legacy track coordinates (still importable)
│ │ ├── track-data-json.js # Track JSON format, default OKC data, parser
│ │ └── session-logger.js # .dove CSV file logger & download
│ └── ui/
│ ├── map-manager.js # Leaflet map, multi-course lines, waypoint marker
│ ├── data-display.js # DOM updates for timing + detection + course panels
│ └── controls.js # Button handlers, track JSON textarea
└── CLAUDE.md # This file
- Load ALL courses from track JSON, create a DovesLapTimer per course
- Feed ALL timers the same GPS data simultaneously (25Hz)
- When speed >= 20 mph: create a "waypoint" at current position (blue blip on map)
- When driver returns near the waypoint (after traveling >= 200m):
- Calculate total distance driven since waypoint (meters -> feet)
- Compare to each course's
lengthFt - Select closest match within 25% tolerance
- Remove non-selected course timers, continue with detected course
- First lap timing is preserved since all timers were running from the start
Same format as DovesDataLogger SD card files (/TRACKS/*.json):
{
"longName": "Orlando Kart Center",
"shortName": "OKC",
"defaultCourse": "Normal",
"courses": [
{
"name": "Normal",
"lengthFt": 3383,
"start_a_lat": 28.4127..., "start_a_lng": -81.3797...,
"start_b_lat": 28.4127..., "start_b_lng": -81.3795...,
"sector_2_a_lat": ..., "sector_2_a_lng": ...,
"sector_2_b_lat": ..., "sector_2_b_lng": ...,
"sector_3_a_lat": ..., "sector_3_a_lng": ...,
"sector_3_b_lat": ..., "sector_3_b_lng": ...
}
]
}Sector lines are optional per course. lengthFt is required for detection.
| JS Module | C++ Equivalent |
|---|---|
js/lib/constants.js |
#define macros + class constants in DovesLapTimer.h |
js/lib/geo-math.js |
Class methods: haversine(), haversine3D(), pointLineSegmentDistance() |
js/lib/crossing-detection.js |
Class methods: isObtuseTriangle(), insideLineThreshold(), pointOnSideOfLine() |
js/lib/interpolation.js |
Private methods: catmullRom(), interpolateWeight(), interpolateCrossingPoint() |
js/lib/DovesLapTimer.js |
DovesLapTimer class in .h/.cpp |
js/lib/direction-detector.js |
DirectionDetector class (embedded or separate .h/.cpp) |
js/lib/course-detector.js |
CourseDetector class (new .h/.cpp or in DovesLapTimer) |
js/lib/course-manager.js |
CourseManager class owning array of DovesLapTimer + CourseDetector |
js/lib/waypoint-lap-timer.js |
WaypointLapTimer class (new .h/.cpp) |
-
No Stream debug* - JS uses a callback function instead of
Stream*. Port back: restoreStream* _serialanddebug_print/debug_printlnmacros. -
crossingThresholdMetersas parameter - In C++ it's a class member accessed directly. In JS the standalone utility functions (insideLineThreshold) take it as a parameter. Port back: these become class methods with direct member access again. -
Crossing flag by string key -
_checkSectorLineusesthis[crossingFlagKey]because JS has no pass-by-reference for booleans. Port back: restorebool& crossingFlagparameter. -
Buffer is Array of objects - C++ uses a struct array with
memsetfor clearing. Port back: restorecrossingPointBufferEntry crossingPointBuffer[size]andmemset. -
No altitude - Simulator passes altitude=0 always. The
haversine3Dfunction exists and works, but the sim doesn't use real altitude. Port back: reconnect to GPS altitude when you trust it. -
Time is simulated - JS sim uses a fake clock starting at noon. Port back: reconnect to
getGpsTimeInMilliseconds()from real GPS. -
Speed is calculated from position deltas - Real GPS provides speed directly via
gps->speed(knots). Port back: use GPS-reported speed. -
Math.round()on crossing time - JS doesn't haveunsigned longimplicit truncation. Port back: C++unsigned longassignment handles this naturally. -
CourseManager owns multiple timers - JS creates them dynamically. Port back: fixed-size array of
DovesLapTimer(max 6-8 courses) withactiveflags. Reduce buffer size per timer if RAM is tight. -
CourseDetector uses haversine as import - In C++ it's a class method or can use the existing
DovesLapTimer::haversine(). Port back: either make it a friend class or duplicate the haversine call. -
DirectionDetector owned by DovesLapTimer - Simple state machine, minimal RAM. Port back: embed as member or small helper class. Reset in
reset(). -
WaypointLapTimer is independent - Duck-typed to match DovesLapTimer's public API. Port back: separate class with same getter signatures. Circular buffer of fixed size (50 entries). Uses haversine for proximity checks.
-
CourseDetector ranked matching -
_matchCourseRanked()builds sorted candidate list, CourseManager validates viaraceStartedsanity check. Port back: fixed-size candidate array (max 8), CourseManager walks it.
- Add new state to
_resetState()inDovesLapTimer.js- maps toreset()in C++ - Line config goes in
_initLineConfig()- only called from constructor, NOT fromreset() - Add new methods to
DovesLapTimer.jsclass - maps to methods in.h/.cpp - If a method is pure math/geometry, consider putting it in
geo-math.jsorcrossing-detection.jsinstead - Keep the sim layer (
js/sim/) separate from the library (js/lib/) - onlyjs/lib/gets ported back - ONLY
js/lib/gets ported back to C++. Everything else is test infrastructure.
- Course Detection - Auto-detect which track/course the driver is on (IMPLEMENTED)
- Automatic Laptimes - Auto-detect start/finish line without manual configuration
These features will be developed in JS first for rapid iteration, then ported to C++.
- Leaflet 1.9.4 - Map rendering (CDN, no install needed)
- No build step - Pure ES modules, just open
index.html - No npm/node - Intentionally zero tooling
- Repo: https://github.com/TheAngryRaven/DovesLapTimer
- Version ported from: 3.1.1
- The JS port is a faithful 1:1 translation of every method
- DovesLapTimer: https://github.com/TheAngryRaven/DovesLapTimer - Core timing library (C++)
- DovesDataLogger: https://github.com/TheAngryRaven/DovesDataLogger - Hardware datalogger (Arduino)