calculate fuel usage on current lap

I don't like the current state of affairs, need to change it
This commit is contained in:
2026-05-31 16:41:45 +01:00
parent 32dc35d008
commit b6075e2138
+99 -23
View File
@@ -4,13 +4,21 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"strconv" "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 type fuelCalcState int
const ( const (
// leaves pits or whenever this starts
outlap fuelCalcState = iota 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 firstLap
// We already have history and can therefore do averages
normal normal
) )
@@ -22,6 +30,8 @@ type FuelCalculator struct {
lastLapNumber int // Tracks when we cross the start/finish line lastLapNumber int // Tracks when we cross the start/finish line
fuelAtLapStart float64 // Snapshot of fuel when the lap began fuelAtLapStart float64 // Snapshot of fuel when the lap began
setup *sync.Once
lapHistory []float64 // Buffer holding the last N valid laps lapHistory []float64 // Buffer holding the last N valid laps
maxHistoryLaps int // How many laps to keep in the buffer (e.g., 3) 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 { func NewFuelCalculator(logger *slog.Logger) *FuelCalculator {
return &FuelCalculator{ return &FuelCalculator{
Logger: logger, Logger: logger,
setup: &sync.Once{},
lapHistory: make([]float64, 0, 5), // Pre-allocate space for 5 laps lapHistory: make([]float64, 0, 5), // Pre-allocate space for 5 laps
maxHistoryLaps: 3, // We want a 3-lap rolling average maxHistoryLaps: 3, // We want a 3-lap rolling average
lastLapNumber: 0, lastLapNumber: 0,
@@ -49,43 +60,108 @@ func NewFuelCalculator(logger *slog.Logger) *FuelCalculator {
} }
} }
// func (fc *FuelCalculator) isOutlap(lap int) { func (fc *FuelCalculator) transitionState(state fuelCalcState) {
// if fc.isOutLap fc.state = state
// } }
func (fc *FuelCalculator) Process(td *TelemetryData) { func (fc *FuelCalculator) crossedStartFinishLine(lap int) bool {
currentLap := int(td.Values[LapNumber].Raw) return lap > fc.lastLapNumber
// NOTE: this is stupid as, fix it }
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) currentFuelLevel, err := strconv.ParseFloat(td.Values[FuelLevel].Str, 64)
if err != nil { if err != nil {
fc.Logger.Debug(fmt.Sprintf("error parsing fuel level: %+v", err)) fc.Logger.Debug(fmt.Sprintf("error parsing fuel level: %+v", err))
return 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].Type = DataTypeSTRING
td.Values[FuelLastLap].Str = "No Data" td.Values[FuelLastLap].Str = "No Data"
td.Values[FuelCurrentLap].Type = DataTypeSTRING td.Values[FuelCurrentLap].Type = DataTypeSTRING
td.Values[FuelCurrentLap].Str = "No Data" 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 // Simple helper to wipe state on session changes