From b6075e213872dc4193baca2e4e127843044b313b Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sun, 31 May 2026 16:41:45 +0100 Subject: [PATCH] calculate fuel usage on current lap I don't like the current state of affairs, need to change it --- telemetry/fuel_calculator.go | 122 ++++++++++++++++++++++++++++------- 1 file changed, 99 insertions(+), 23 deletions(-) diff --git a/telemetry/fuel_calculator.go b/telemetry/fuel_calculator.go index 8acc91a..deb0a02 100644 --- a/telemetry/fuel_calculator.go +++ b/telemetry/fuel_calculator.go @@ -4,13 +4,21 @@ import ( "fmt" "log/slog" "strconv" + "sync" ) +// NOTE: for managing fuel consumption and predictions we need to filter out +// abnormal laps like going into the pits and refueling and whatnot + type fuelCalcState int const ( + // leaves pits or whenever this starts outlap fuelCalcState = iota + // we cross the line for the first time, we can count real time usage here + // but don't have history yet firstLap + // We already have history and can therefore do averages normal ) @@ -22,6 +30,8 @@ type FuelCalculator struct { lastLapNumber int // Tracks when we cross the start/finish line fuelAtLapStart float64 // Snapshot of fuel when the lap began + setup *sync.Once + lapHistory []float64 // Buffer holding the last N valid laps maxHistoryLaps int // How many laps to keep in the buffer (e.g., 3) @@ -42,6 +52,7 @@ type FuelCalculator struct { func NewFuelCalculator(logger *slog.Logger) *FuelCalculator { return &FuelCalculator{ Logger: logger, + setup: &sync.Once{}, lapHistory: make([]float64, 0, 5), // Pre-allocate space for 5 laps maxHistoryLaps: 3, // We want a 3-lap rolling average lastLapNumber: 0, @@ -49,43 +60,108 @@ func NewFuelCalculator(logger *slog.Logger) *FuelCalculator { } } -// func (fc *FuelCalculator) isOutlap(lap int) { -// if fc.isOutLap -// } +func (fc *FuelCalculator) transitionState(state fuelCalcState) { + fc.state = state +} -func (fc *FuelCalculator) Process(td *TelemetryData) { - currentLap := int(td.Values[LapNumber].Raw) - // NOTE: this is stupid as, fix it +func (fc *FuelCalculator) crossedStartFinishLine(lap int) bool { + return lap > fc.lastLapNumber +} + +func (fc *FuelCalculator) refueled(currentFuelLevel float64) bool { + return currentFuelLevel > fc.fuelAtLapStart +} + +func (fc *FuelCalculator) storeStartFinishLineFuelLevels(curFuel float64, lap int) { + fc.fuelAtLapStart = curFuel + fc.lastLapNumber = lap +} + +func (fc *FuelCalculator) calculateCurLapFuelUsage(td *TelemetryData, fuel float64) { + td.Values[FuelCurrentLap].Type = DataTypeSTRING + td.Values[FuelCurrentLap].Str = fmt.Sprintf("%.2f", fc.fuelAtLapStart-fuel) +} + +func (fc *FuelCalculator) processOutlap(td *TelemetryData, lap int) { currentFuelLevel, err := strconv.ParseFloat(td.Values[FuelLevel].Str, 64) if err != nil { fc.Logger.Debug(fmt.Sprintf("error parsing fuel level: %+v", err)) return } - if currentLap == 0 { + if fc.crossedStartFinishLine(lap) { + fc.storeStartFinishLineFuelLevels(currentFuelLevel, lap) + fc.transitionState(firstLap) + } + + if fc.refueled(currentFuelLevel) { + fc.transitionState(outlap) + } +} + +func (fc *FuelCalculator) processFirstlap(td *TelemetryData, lap int) { + currentFuelLevel, err := strconv.ParseFloat(td.Values[FuelLevel].Str, 64) + if err != nil { + fc.Logger.Debug(fmt.Sprintf("error parsing fuel level: %+v", err)) + return + } + + if fc.crossedStartFinishLine(lap) { + fc.storeStartFinishLineFuelLevels(currentFuelLevel, lap) + fc.transitionState(normal) + } + + if fc.refueled(currentFuelLevel) { + fc.transitionState(outlap) + } + + fc.calculateCurLapFuelUsage(td, currentFuelLevel) +} + +func (fc *FuelCalculator) processNormal(td *TelemetryData, lap int) { + currentFuelLevel, err := strconv.ParseFloat(td.Values[FuelLevel].Str, 64) + if err != nil { + fc.Logger.Debug(fmt.Sprintf("error parsing fuel level: %+v", err)) + return + } + + if fc.crossedStartFinishLine(lap) { + fc.storeStartFinishLineFuelLevels(currentFuelLevel, lap) + fc.transitionState(normal) + } + + if fc.refueled(currentFuelLevel) { + fc.transitionState(outlap) + } + + fc.calculateCurLapFuelUsage(td, currentFuelLevel) + + // And here we add how to calculate last lap usage or whatever yo +} + +func (fc *FuelCalculator) Process(td *TelemetryData) { + // This setup function will set the default values for the fields we work + // with on this fuel calculator + fc.setup.Do(func() { td.Values[FuelLastLap].Type = DataTypeSTRING td.Values[FuelLastLap].Str = "No Data" td.Values[FuelCurrentLap].Type = DataTypeSTRING td.Values[FuelCurrentLap].Str = "No Data" + }) - return + currentLap := int(td.Values[LapNumber].Raw) + // NOTE: this is stupid as, fix it. I mean don't store fuel level as a string + // currentFuelLevel, err := strconv.ParseFloat(td.Values[FuelLevel].Str, 64) + + switch fc.state { + case outlap: + fc.processOutlap(td, currentLap) + case firstLap: + fc.processFirstlap(td, currentLap) + case normal: + fc.processNormal(td, currentLap) } - - if currentLap > fc.lastLapNumber { - fc.Logger.Debug(fmt.Sprintf("crossed the line: %+v -> %+v", currentLap, fc.lastLapNumber)) - fc.state = firstLap - fc.lastLapNumber = currentLap - fc.fuelAtLapStart = currentFuelLevel - } - - td.Values[FuelLastLap].Type = DataTypeSTRING - td.Values[FuelLastLap].Str = "No Data" - - td.Values[FuelCurrentLap].Type = DataTypeSTRING - fc.Logger.Debug(fmt.Sprintf("calculate: %+v - %+v = %+v", fc.fuelAtLapStart, - currentFuelLevel, fc.fuelAtLapStart-currentFuelLevel)) - td.Values[FuelCurrentLap].Str = fmt.Sprintf("%.2f", fc.fuelAtLapStart-currentFuelLevel) } // Simple helper to wipe state on session changes