From 32dc35d008d3327520a99059e586dd926ce695ed Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Sun, 31 May 2026 10:44:16 +0100 Subject: [PATCH] implementing a bare bones fuel calculator --- layouts/layout.yaml | 746 ++++++++++++++++-------------- providers/iracing/fieldMapping.go | 7 +- providers/iracing/iracing.go | 37 +- telemetry/data.go | 17 +- telemetry/fuel_calculator.go | 101 ++++ telemetry/rpm_lights.go | 4 + 6 files changed, 553 insertions(+), 359 deletions(-) create mode 100644 telemetry/fuel_calculator.go diff --git a/layouts/layout.yaml b/layouts/layout.yaml index a38dadb..46ff413 100755 --- a/layouts/layout.yaml +++ b/layouts/layout.yaml @@ -1,341 +1,5 @@ Windows: 1: - uiwindow: - Dims: - X0: 9 - Y0: 413 - Width: 57 - Height: 57 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 2 - TextSize: 4 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 2 - PreviewValue: "12" - Title: ABS - uidata: - WID: 1 - TelemetryField: ABS Control - 2: - uiwindow: - Dims: - X0: 140 - Y0: 412 - Width: 57 - Height: 57 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 2 - TextSize: 4 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 2 - PreviewValue: "6" - Title: Thr - uidata: - WID: 2 - TelemetryField: Throttle Control - 3: - uiwindow: - Dims: - X0: 60 - Y0: 155 - Width: 226 - Height: 57 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 2 - TextSize: 4 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 2 - PreviewValue: "06:45.123" - Title: Last Lap - uidata: - WID: 3 - TelemetryField: Last Lap Time - 4: - uiwindow: - Dims: - X0: 74 - Y0: 413 - Width: 57 - Height: 57 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 2 - TextSize: 4 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 2 - PreviewValue: "12" - Title: TC - uidata: - WID: 4 - TelemetryField: TC Control - 5: - uiwindow: - Dims: - X0: 590 - Y0: 367 - Width: 100 - Height: 50 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 2 - TextSize: 3 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 2 - PreviewValue: "101.5" - Title: Water T - uidata: - WID: 5 - TelemetryField: Water Temperature - 6: - uiwindow: - Dims: - X0: 354 - Y0: 71 - Width: 74 - Height: 111 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 2 - TextSize: 12 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 2 - PreviewValue: R - Title: Box - uidata: - WID: 6 - TelemetryField: Gear - 7: - uiwindow: - Dims: - X0: 694 - Y0: 423 - Width: 100 - Height: 50 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 2 - TextSize: 3 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 2 - PreviewValue: "4.56" - Title: Oil P - uidata: - WID: 7 - TelemetryField: Oil Pressure - 8: - uiwindow: - Dims: - X0: 694 - Y0: 367 - Width: 100 - Height: 50 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 2 - TextSize: 3 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 2 - PreviewValue: "101.4" - Title: Oil T - uidata: - WID: 8 - TelemetryField: Oil Temperature - 9: - uiwindow: - Dims: - X0: 436 - Y0: 71 - Width: 206 - Height: 57 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 2 - TextSize: 4 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 2 - PreviewValue: "9999" - Title: Dig Tacho - uidata: - WID: 9 - TelemetryField: RPM - 10: - uiwindow: - Dims: - X0: 207 - Y0: 412 - Width: 111 - Height: 57 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 2 - TextSize: 4 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 2 - PreviewValue: "61.4" - Title: BBias - uidata: - WID: 10 - TelemetryField: BrakeBias - 11: - uiwindow: - Dims: - X0: 710 - Y0: 8 - Width: 84 - Height: 57 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 2 - TextSize: 4 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 2 - PreviewValue: PIT - Title: Pit Sp - uidata: - WID: 11 - TelemetryField: Pit Speed Limiter - 12: - uiwindow: - Dims: - X0: 60 - Y0: 91 - Width: 85 - Height: 57 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 2 - TextSize: 4 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 2 - PreviewValue: "123" - Title: Lap - uidata: - WID: 12 - TelemetryField: Lap Number - 13: - uiwindow: - Dims: - X0: 142 - Y0: 19 - Width: 500 - Height: 20 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 1 - TextSize: 2 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 1 - PreviewValue: "5678" - Title: TACHO - uidata: - WID: 13 - TelemetryField: RPM - 14: - uiwindow: - Dims: - X0: 331 - Y0: 190 - Width: 116 - Height: 70 - Decor: - BGColour: 4161 - FGColour: 65535 - TitleColour: 65535 - BorderColour: 63488 - TitleSize: 2 - TextSize: 6 - HasBorder: 1 - Padding: 0 - Opts: - ShowID: 0 - WinType: 2 - PreviewValue: "234" - Title: SPEEDO - uidata: - WID: 14 - TelemetryField: Speed - 15: uiwindow: Dims: X0: 359 @@ -357,5 +21,413 @@ Windows: PreviewValue: YELLOW Title: RPMColour uidata: - WID: 15 + WID: 1 TelemetryField: RPM State Colour + 2: + uiwindow: + Dims: + X0: 74 + Y0: 413 + Width: 57 + Height: 57 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 4 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: "12" + Title: TC + uidata: + WID: 2 + TelemetryField: TC Control + 3: + uiwindow: + Dims: + X0: 60 + Y0: 91 + Width: 85 + Height: 57 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 4 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: "123" + Title: Lap + uidata: + WID: 3 + TelemetryField: Lap Number + 4: + uiwindow: + Dims: + X0: 9 + Y0: 413 + Width: 57 + Height: 57 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 4 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: "12" + Title: ABS + uidata: + WID: 4 + TelemetryField: ABS Control + 5: + uiwindow: + Dims: + X0: 694 + Y0: 367 + Width: 100 + Height: 50 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 3 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: "101.4" + Title: Oil T + uidata: + WID: 5 + TelemetryField: Oil Temperature + 6: + uiwindow: + Dims: + X0: 60 + Y0: 155 + Width: 226 + Height: 57 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 4 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: "06:45.123" + Title: Last Lap + uidata: + WID: 6 + TelemetryField: Last Lap Time + 7: + uiwindow: + Dims: + X0: 590 + Y0: 367 + Width: 100 + Height: 50 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 3 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: "101.5" + Title: Water T + uidata: + WID: 7 + TelemetryField: Water Temperature + 8: + uiwindow: + Dims: + X0: 642 + Y0: 188 + Width: 150 + Height: 50 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 3 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: "2.7" + Title: Fuel Cur Lap + uidata: + WID: 8 + TelemetryField: Fuel Current Lap + 9: + uiwindow: + Dims: + X0: 470 + Y0: 188 + Width: 168 + Height: 50 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 3 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: 12.3 L + Title: Fuel Last Lap + uidata: + WID: 9 + TelemetryField: Fuel Last Lap + 10: + uiwindow: + Dims: + X0: 354 + Y0: 71 + Width: 74 + Height: 111 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 12 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: R + Title: Box + uidata: + WID: 10 + TelemetryField: Gear + 11: + uiwindow: + Dims: + X0: 142 + Y0: 19 + Width: 500 + Height: 20 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 1 + TextSize: 2 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 1 + PreviewValue: "5678" + Title: TACHO + uidata: + WID: 11 + TelemetryField: RPM + 12: + uiwindow: + Dims: + X0: 140 + Y0: 412 + Width: 57 + Height: 57 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 4 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: "6" + Title: Thr + uidata: + WID: 12 + TelemetryField: Throttle Control + 13: + uiwindow: + Dims: + X0: 694 + Y0: 423 + Width: 100 + Height: 50 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 3 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: "4.56" + Title: Oil P + uidata: + WID: 13 + TelemetryField: Oil Pressure + 14: + uiwindow: + Dims: + X0: 436 + Y0: 71 + Width: 206 + Height: 57 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 4 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: "9999" + Title: Dig Tacho + uidata: + WID: 14 + TelemetryField: RPM + 15: + uiwindow: + Dims: + X0: 710 + Y0: 8 + Width: 84 + Height: 57 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 4 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: PIT + Title: Pit Sp + uidata: + WID: 15 + TelemetryField: Pit Speed Limiter + 16: + uiwindow: + Dims: + X0: 331 + Y0: 190 + Width: 116 + Height: 70 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 6 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: "234" + Title: SPEEDO + uidata: + WID: 16 + TelemetryField: Speed + 17: + uiwindow: + Dims: + X0: 207 + Y0: 412 + Width: 111 + Height: 57 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 4 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: "61.4" + Title: BBias + uidata: + WID: 17 + TelemetryField: BrakeBias + 18: + uiwindow: + Dims: + X0: 470 + Y0: 132 + Width: 169 + Height: 50 + Decor: + BGColour: 4161 + FGColour: 65535 + TitleColour: 65535 + BorderColour: 63488 + TitleSize: 2 + TextSize: 3 + HasBorder: 1 + Padding: 0 + Opts: + ShowID: 0 + WinType: 2 + PreviewValue: "123.4" + Title: Fuel Level + uidata: + WID: 18 + TelemetryField: Fuel Level diff --git a/providers/iracing/fieldMapping.go b/providers/iracing/fieldMapping.go index 6f116b2..6df98f3 100644 --- a/providers/iracing/fieldMapping.go +++ b/providers/iracing/fieldMapping.go @@ -7,9 +7,10 @@ import ( ) var internalToSDKFieldNames = map[telemetry.FieldID]string{ - telemetry.Speed: "Speed", - telemetry.Gear: "Gear", - telemetry.RPM: "RPM", + telemetry.Speed: "Speed", + telemetry.Gear: "Gear", + telemetry.RPM: "RPM", + telemetry.FuelLevel: "FuelLevel", // Engine Data telemetry.OilPress: "OilPress", telemetry.OilTemp: "OilTemp", diff --git a/providers/iracing/iracing.go b/providers/iracing/iracing.go index f40939b..fdfc608 100644 --- a/providers/iracing/iracing.go +++ b/providers/iracing/iracing.go @@ -161,11 +161,29 @@ func (i *IRacing) Subscribe(requestFields map[int16]telem.FieldID) { i.data.ActiveBinds = make([]telem.BoundField, 0, len(requestFields)) - boundCheck := make(map[telem.FieldID]bool) + // First we must add the virtual fields + // we will add their dependencies and the primitives to a slice + pendingBinds := make([]telem.FieldID, telem.MaxFields) for winID, id := range requestFields { i.data.Values[id].IDs = append(i.data.Values[id].IDs, winID) + switch id { + case telem.RPMStateColour: + i.data.VirtualBinds = append(i.data.VirtualBinds, telem.NewRPMLights()) + case telem.FuelCurrentLap: + i.data.VirtualBinds = append(i.data.VirtualBinds, + telem.NewFuelCalculator(i.logger.WithGroup("FUEL CALC"))) + default: + // primitive telemetry field + pendingBinds = append(pendingBinds, id) + } + } + + boundCheck := make(map[telem.FieldID]bool) + + // Now that we know all the fields we need to bind we follow the binding procedure + for _, id := range pendingBinds { // Check if we already bound this FieldID if boundCheck[id] { continue @@ -220,6 +238,8 @@ func (i *IRacing) Subscribe(requestFields map[int16]telem.FieldID) { out.Type = telem.DataTypeUINT16 out.Raw = uint64(uint16(v.(float32))) } + case telem.FuelLevel: + binding.Transform = FloatToStringTransform // Engine Data case telem.OilPress: binding.Transform = FloatToStringTransform @@ -266,20 +286,5 @@ func (i *IRacing) Subscribe(requestFields map[int16]telem.FieldID) { boundCheck[id] = true } - // Subscribe to whatever we need of virtual binds I guess - we need to ensure - // the virtual binds fields are being read - needsRPMColour := false - for _, id := range requestFields { - if id == telem.RPMStateColour { - needsRPMColour = true - } - } - - i.logger.Debug(fmt.Sprintf("Subscribing to virtual bind? %+v", needsRPMColour)) - if needsRPMColour { - i.logger.Debug("Subscribing to virtual bind") - i.data.VirtualBinds = append(i.data.VirtualBinds, telem.NewRPMLights()) - } - i.logger.Debug(fmt.Sprintf("Subscribed: %+v\n", i.data.ActiveBinds)) } diff --git a/telemetry/data.go b/telemetry/data.go index d2f233a..4e855bf 100644 --- a/telemetry/data.go +++ b/telemetry/data.go @@ -21,6 +21,7 @@ type FieldMapper struct { // So fuel per lap predictions, compound gauge lights and so on type VirtualField interface { Process(td *TelemetryData) + EnsureSubscribed() []FieldID } // NOTE: Update the iracing SDK to write data to the same map ALWAYS, then @@ -135,6 +136,7 @@ const ( Speed FieldID = iota RPM Gear + FuelLevel // Engine Data OilPress OilTemp @@ -167,15 +169,20 @@ const ( ReplaySessionTime Empty // Virtual Fields -- fields derived from primitive fields + // RPM Dash Lights RPMStateColour + // Fuel Calculator + FuelCurrentLap + FuelLastLap MaxFields ) const FirstField = Speed var FieldNames = [MaxFields]string{ - Speed: "Speed", - RPM: "RPM", - Gear: "Gear", + Speed: "Speed", + RPM: "RPM", + Gear: "Gear", + FuelLevel: "Fuel Level", // Engine Data OilPress: "Oil Pressure", OilTemp: "Oil Temperature", @@ -207,7 +214,11 @@ var FieldNames = [MaxFields]string{ SessionTime: "SessionTime", ReplaySessionTime: "ReplaySessionTime", // Virtual Fields + // RPM Dash Lights RPMStateColour: "RPM State Colour", + // Fuel Calculator + FuelCurrentLap: "Fuel Current Lap", + FuelLastLap: "Fuel Last Lap", Empty: "Emtpy", } diff --git a/telemetry/fuel_calculator.go b/telemetry/fuel_calculator.go new file mode 100644 index 0000000..8acc91a --- /dev/null +++ b/telemetry/fuel_calculator.go @@ -0,0 +1,101 @@ +package telemetry + +import ( + "fmt" + "log/slog" + "strconv" +) + +type fuelCalcState int + +const ( + outlap fuelCalcState = iota + firstLap + normal +) + +// FuelCalculator acts as a Virtual Field middleware +type FuelCalculator struct { + Logger *slog.Logger + + // --- Internal State (The Memory) --- + lastLapNumber int // Tracks when we cross the start/finish line + fuelAtLapStart float64 // Snapshot of fuel when the lap began + + lapHistory []float64 // Buffer holding the last N valid laps + maxHistoryLaps int // How many laps to keep in the buffer (e.g., 3) + + // --- Validation Flags --- + + isOutLap bool // True if we just left the pits + isUnderCaution bool // True if safety car is out (optional, based on flags) + + // --- The Outputs (What gets pushed back to the TUI) --- + + lastLapUsage float64 + avgUsage float64 + maxUsage float64 + + state fuelCalcState +} + +func NewFuelCalculator(logger *slog.Logger) *FuelCalculator { + return &FuelCalculator{ + Logger: logger, + lapHistory: make([]float64, 0, 5), // Pre-allocate space for 5 laps + maxHistoryLaps: 3, // We want a 3-lap rolling average + lastLapNumber: 0, + state: outlap, + } +} + +// func (fc *FuelCalculator) isOutlap(lap int) { +// if fc.isOutLap +// } + +func (fc *FuelCalculator) Process(td *TelemetryData) { + currentLap := int(td.Values[LapNumber].Raw) + // NOTE: this is stupid as, fix it + 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 { + td.Values[FuelLastLap].Type = DataTypeSTRING + td.Values[FuelLastLap].Str = "No Data" + + td.Values[FuelCurrentLap].Type = DataTypeSTRING + td.Values[FuelCurrentLap].Str = "No Data" + + return + } + + 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 +func (fc *FuelCalculator) resetHistory() { + fc.lapHistory = make([]float64, 0, fc.maxHistoryLaps) + fc.lastLapNumber = 0 + fc.avgUsage = 0 + fc.maxUsage = 0 +} + +func (fc *FuelCalculator) EnsureSubscribed() []FieldID { + return []FieldID{FuelLevel, LapNumber} +} diff --git a/telemetry/rpm_lights.go b/telemetry/rpm_lights.go index 8cc4bd1..16d4ae1 100644 --- a/telemetry/rpm_lights.go +++ b/telemetry/rpm_lights.go @@ -24,3 +24,7 @@ func (rl *RPMLights) Process(td *TelemetryData) { td.Values[RPMStateColour].Str = "WHITE" } } + +func (rl *RPMLights) EnsureSubscribed() []FieldID { + return []FieldID{RPM} +}