diff --git a/packages/server/src/controllers/LapController/LapController.ts b/packages/server/src/controllers/LapController/LapController.ts index 14751d63..d9d6ec71 100644 --- a/packages/server/src/controllers/LapController/LapController.ts +++ b/packages/server/src/controllers/LapController/LapController.ts @@ -22,6 +22,7 @@ import type { } from "@shared/helios-types"; const logger = createLightweightApplicationLogger("LapController.ts"); +const MIN_PACKETS_FOR_LAP = 5; /** * * There is some general documentation on this file in the docs, but it is not very detailed @@ -39,6 +40,7 @@ const logger = createLightweightApplicationLogger("LapController.ts"); */ export class LapController implements LapControllerType { public lastLapPackets: ITelemetryData[] = [] as ITelemetryData[]; + public previousLapDigital: boolean | null = null; public previouslyInFinishLineProximity = false; public passedDebouncedCheckpoint = false; public totalTime = 3600 * 1000 * 8; // 1000 ms/sec * 3600 sec/hr * 8 hr @@ -170,6 +172,10 @@ export class LapController implements LapControllerType { } public async handlePacket(packet: ITelemetryData) { + // Always buffer packets so a lap boundary has data available for calculations. + // NOTE: The lastLapPackets will grow indefinitely if LapDigital is never triggered. + this.lastLapPackets.push(packet); + const motorDetails0 = packet.MotorDetails0.VehicleVelocity; const motorDetails1 = packet.MotorDetails1.VehicleVelocity; if (this.raceInfo.raceDay >= 3) @@ -185,15 +191,37 @@ export class LapController implements LapControllerType { this.calculateRaceDistance(motorDetails0, motorDetails1); } - if (this.checkLap(packet) && this.lastLapPackets.length > 5) { - logger.info("lap completed for geofence"); + const lapDigital = Boolean(packet.B3.LapDigital); + + // Single source of truth for LapDigital-triggered laps: + // only fire on a false -> true transition after we have a previous sample. + const canInsertLapFromDigital = + this.previousLapDigital !== null && + this.previousLapDigital === false && + lapDigital === true; + + // Update state for next packet. + this.previousLapDigital = lapDigital; + + if (lapDigital) { + // logger.info("lap completed for geofence"); + logger.info("LapDigital is true, lap completed."); } if ( // TEST: The condition was commented out because, in the current fakePacket data, packet.B3.LapDigital is always false. As a result, the code for broadcasting lap data (broadcastLapData) would never execute during testing with fakePacket. By commenting out this condition, it allows the code to proceed and broadcast lap data even when LapDigital is false. // packet.B3.LapDigital && - this.lastLapPackets.length > 5 + // this.lastLapPackets.length > 5 + canInsertLapFromDigital ) { + if (this.lastLapPackets.length < MIN_PACKETS_FOR_LAP) { + logger.warn( + `Ignoring lap boundary: only ${this.lastLapPackets.length} packet(s) buffered, need at least ${MIN_PACKETS_FOR_LAP}.`, + ); + this.backendController.socketIO.broadcastRaceInfo(this.raceInfo); + return; + } + await this.backendController.socketIO.broadcastLapComplete(); // mark lap, calculate lap, and add to lap table in database // send lap over socket @@ -210,8 +238,8 @@ export class LapController implements LapControllerType { AveragePackCurrent: averagePackCurrent, AverageSpeed: this.calculateAverageLapSpeed(this.lastLapPackets), BatterySecondsRemaining: this.getSecondsRemainingUntilChargedOrDepleted( - amphoursValue, averagePackCurrent, + amphoursValue, ), Distance: this.getDistanceTravelled(this.lastLapPackets), // CHANGE THIS BASED ON ODOMETER/MOTOR INDEX OR CHANGE TO ITERATE EnergyConsumed: this.getEnergyConsumption(this.lastLapPackets), @@ -224,11 +252,11 @@ export class LapController implements LapControllerType { }; this.handleLapData(lapData); + this.raceInfo.lapNumber += 1; this.lastLapPackets = []; } this.backendController.socketIO.broadcastRaceInfo(this.raceInfo); - this.lastLapPackets.push(packet); } public getLastPacket(): ITelemetryData[] { @@ -251,6 +279,7 @@ export class LapController implements LapControllerType { } // checks if lap has been acheived (using geofencing) + // if geofence approach is used, use this function to check if lap has been completed instead of using LapDigital private checkLap(packet: ITelemetryData) { const inProximity = haversineDistance(