From 1d022a4fa855f9c8eda6b15435077151ee9d0c9f Mon Sep 17 00:00:00 2001 From: Eduardo Silva Date: Thu, 10 Sep 2026 15:11:37 +0100 Subject: [PATCH] update the iracing provider to be on the same page as the BeamNG one Deprecated the Fetch and Transform methods --- providers/beamng/utils.go | 6 ++ providers/iracing/fieldMapping.go | 61 ++++------- providers/iracing/iracing.go | 172 +++++++++++------------------- providers/iracing/transforms.go | 125 ++++++++++++++++++++-- providers/iracing/utils.go | 27 ++++- providers/providers.go | 10 +- 6 files changed, 231 insertions(+), 170 deletions(-) diff --git a/providers/beamng/utils.go b/providers/beamng/utils.go index 2854b74..98eb459 100644 --- a/providers/beamng/utils.go +++ b/providers/beamng/utils.go @@ -31,3 +31,9 @@ func IsRunning() bool { // Think of a better number or something return n >= 80 } + +// Stalled +// TODO: needs to be implemented +func (i *BeamNG) Stalled() bool { + return false +} diff --git a/providers/iracing/fieldMapping.go b/providers/iracing/fieldMapping.go index 6df98f3..76948fa 100644 --- a/providers/iracing/fieldMapping.go +++ b/providers/iracing/fieldMapping.go @@ -2,44 +2,23 @@ package iracing // This file maps the data from the desktop provider data structure to iRacing -import ( - "esdi/telemetry" -) - -var internalToSDKFieldNames = map[telemetry.FieldID]string{ - telemetry.Speed: "Speed", - telemetry.Gear: "Gear", - telemetry.RPM: "RPM", - telemetry.FuelLevel: "FuelLevel", - // Engine Data - telemetry.OilPress: "OilPress", - telemetry.OilTemp: "OilTemp", - telemetry.WaterTemp: "WaterTemp", - // Engine Warnings - telemetry.PitSpeedLimiter: "irsdk_pitSpeedLimiter", - // Ajudstements - telemetry.BrakeBias: "dcBrakeBias", - telemetry.ABSSetting: "dcABS", - telemetry.TCSetting: "dcTractionControl", - telemetry.ThrottleSetting: "dcThrottleShape", - // Lap Data - telemetry.LapLastLapTime: "LapLastLapTime", - telemetry.LapNumber: "Lap", - // Tire data - telemetry.LFtempL: "LFtempCL", - telemetry.LFtempM: "LFtempCM", - telemetry.LFtempR: "LFtempCR", - telemetry.RFtempL: "RFtempCL", - telemetry.RFtempM: "RFtempCM", - telemetry.RFtempR: "RFtempCR", - telemetry.LRtempL: "LRtempCL", - telemetry.LRtempM: "LRtempCM", - telemetry.LRtempR: "LRtempCR", - telemetry.RRtempL: "RRtempCL", - telemetry.RRtempM: "RRtempCM", - telemetry.RRtempR: "RRtempCR", - // Session Data - telemetry.SessionTime: "SessionTime", - telemetry.ReplaySessionTime: "ReplaySessionTime", - telemetry.Empty: "empty", -} +// TODO: pass these to the new Updaters system +// var internalToSDKFieldNames = map[telemetry.FieldID]string{ +// // Tire data +// telemetry.LFtempL: "LFtempCL", +// telemetry.LFtempM: "LFtempCM", +// telemetry.LFtempR: "LFtempCR", +// telemetry.RFtempL: "RFtempCL", +// telemetry.RFtempM: "RFtempCM", +// telemetry.RFtempR: "RFtempCR", +// telemetry.LRtempL: "LRtempCL", +// telemetry.LRtempM: "LRtempCM", +// telemetry.LRtempR: "LRtempCR", +// telemetry.RRtempL: "RRtempCL", +// telemetry.RRtempM: "RRtempCM", +// telemetry.RRtempR: "RRtempCR", +// // Session Data +// telemetry.SessionTime: "SessionTime", +// telemetry.ReplaySessionTime: "ReplaySessionTime", +// telemetry.Empty: "empty", +// } diff --git a/providers/iracing/iracing.go b/providers/iracing/iracing.go index 395fbdc..65b5c01 100644 --- a/providers/iracing/iracing.go +++ b/providers/iracing/iracing.go @@ -7,11 +7,9 @@ import ( "context" "fmt" "log/slog" - "strconv" "sync" "time" - conv "esdi/conversions" "esdi/telemetry" "github.com/ESilva15/goirsdk" @@ -57,26 +55,71 @@ func NewIRacingProvider( data: telemetry.NewTelemetryData(), streamCh: make(chan telemetry.TelemetryData, 1), // NOTE: This is because I stupidly recorded a test IBT file in 240 + // TODO: make this configurable from the user side ticker: time.NewTicker(time.Second / 240), } - // provider.updaters = [telemetry.MaxFields]func(*telemetry.TelemetryField){} + provider.updaters = [telemetry.MaxFields]func(*telemetry.TelemetryField){ + telemetry.Speed: provider.speed, + telemetry.Gear: provider.gear, + telemetry.RPM: provider.rpm, + telemetry.FuelLevel: provider.fuelLevel, + // Engine Data + telemetry.OilPress: provider.oilPress, + telemetry.OilTemp: provider.oilTemp, + telemetry.WaterTemp: provider.waterTemp, + // EngineWarnings + telemetry.PitSpeedLimiter: provider.pitSpeedLimiter, + // Adjustements + telemetry.BrakeBias: provider.brakeBias, + telemetry.ABSSetting: provider.absSetting, + telemetry.TCSetting: provider.tcSetting, + telemetry.ThrottleSetting: provider.throttleSetting, + // Lap Data + telemetry.LapLastLapTime: provider.lapTime, + telemetry.LapNumber: provider.lapNumber, + // case telemetry.LFtempM: + // binding.Transform = func(v any, out *telemetry.TelemetryField) { + // out.Type = telemetry.DataTypeSTRING + // out.Str = strconv.FormatFloat(float64(v.(float32)), 'f', 1, 32) + // } + // case telemetry.SessionTime: + // binding.Transform = func(v any, out *telemetry.TelemetryField) { + // out.Type = telemetry.DataTypeSTRING + // out.Str = strconv.FormatFloat(v.(float64), 'f', 1, 32) + // } + // case telemetry.ReplaySessionTime: + // binding.Transform = func(v any, out *telemetry.TelemetryField) { + // out.Type = telemetry.DataTypeSTRING + // out.Str = strconv.FormatFloat(v.(float64), 'f', 1, 32) + // } + // case telemetry.Empty: + // binding.Transform = telemetry.EmptyTransform + // } + } + + // Set the unset telemetry fields on the updaters as unused fields + for k := range int(telemetry.MaxFields) { + if provider.updaters[k] == nil { + provider.updaters[k] = provider.unused + } + } return provider, nil } func (i *IRacing) isDataAvailable() bool { // Its offline telemetry, data must be available - if i.SDK.File != nil { - return true + if i.SDK.File == nil { + return false } // Check if live telemetry is on - if i.SDK.IsConnected() { - return true + if !i.SDK.IsConnected() { + return false } - return false + return true } func (i *IRacing) stream(ctx context.Context) { @@ -84,17 +127,19 @@ func (i *IRacing) stream(ctx context.Context) { go func() { for { - // Explicitly intercpt cancellation + // Explicitly intercept cancellation select { case <-ctx.Done(): return default: } + // TODO: fix this logic // We start by checking if we do or do not have data available - if !i.isDataAvailable() { - continue - } + // if !i.isDataAvailable() { + // i.logger.Info("isDataAvailable check failed. Cancelling stream...") + // i.streamCancel() + // } select { case <-ctx.Done(): @@ -124,18 +169,14 @@ func (i *IRacing) readData() { return } - // Read 1 to 1 data - for _, b := range i.data.ActiveBinds { - v := i.SDK.Vars.Vars[b.Key].Value - - b.Transform(v, &i.data.Values[b.ID]) + // Read 1 to 1 data using our updaters + for _, bind := range i.data.ActiveBinds { + i.updaters[bind.ID](&i.data.Values[bind.ID]) } // Set up virtual binds i.logger.Debug("Entering virtual binds loop") for _, vBind := range i.data.VirtualBinds { - // NOTE: delete the logs here, they are really bad - i.logger.Debug("Processing virtual binds") vBind.Process(i.data) } @@ -173,7 +214,7 @@ func (i *IRacing) Subscribe(requestFields map[int16]telemetry.FieldID) { // First we must add the virtual fields // we will add their dependencies and the primitives to a slice - pendingBinds := make([]telemetry.FieldID, telemetry.MaxFields) + pendingBinds := make([]telemetry.FieldID, 0, telemetry.MaxFields) for winID, id := range requestFields { i.data.Values[id].IDs = append(i.data.Values[id].IDs, winID) @@ -199,97 +240,8 @@ func (i *IRacing) Subscribe(requestFields map[int16]telemetry.FieldID) { continue } - // Translate the UI FieldIDs to this provider's field names - sdkKey, ok := internalToSDKFieldNames[id] - if !ok { - i.logger.Debug("failed to get internal id") - // Need to find a way to pass a message saying something wasn't right - continue - } - binding := telemetry.BoundField{ - Key: sdkKey, - ID: id, - } - - switch id { - case telemetry.Speed: - binding.Transform = func(v any, out *telemetry.TelemetryField) { - out.Type = telemetry.DataTypeUINT16 - out.Raw = uint64(conv.MsToKph(v.(float32))) - } - case telemetry.Gear: - binding.Transform = func(v any, out *telemetry.TelemetryField) { - out.Type = telemetry.DataTypeCHAR - // out.Raw = uint64(v.(int)) - - gear := 0 - if val, ok := v.(int32); ok { - gear = int(val) - } else if val, ok := v.(int); ok { - gear = val - } - - switch { - case gear == 0: - out.Raw = uint64('N') // ASCII 78 - case gear < 0: - out.Raw = uint64('R') // ASCII 82 - case gear > 0 && gear < 10: - // Quickest way to turn 1 into '1', 2 into '2', etc. - // ASCII '0' is 48, so 48 + 1 = 49 ('1') - out.Raw = uint64('0' + gear) - default: - out.Raw = uint64('?') // Fallback - } - } - case telemetry.RPM: - binding.Transform = func(v any, out *telemetry.TelemetryField) { - out.Type = telemetry.DataTypeUINT16 - out.Raw = uint64(uint16(v.(float32))) - } - case telemetry.FuelLevel: - binding.Transform = telemetry.FloatToStringTransformDEPRECATE - // Engine Data - case telemetry.OilPress: - binding.Transform = telemetry.FloatToStringTransformDEPRECATE - case telemetry.OilTemp: - binding.Transform = telemetry.FloatToStringTransformDEPRECATE - case telemetry.WaterTemp: - binding.Transform = telemetry.FloatToStringTransformDEPRECATE - // Something else - case telemetry.PitSpeedLimiter: - binding.Transform = PitSpeedLimiterTransform - // Adjustements - case telemetry.BrakeBias: - binding.Transform = telemetry.FloatToStringTransformDEPRECATE - case telemetry.ABSSetting: - binding.Transform = telemetry.FloatToUInt8TransformDEPRECATE - case telemetry.TCSetting: - binding.Transform = telemetry.FloatToUInt8TransformDEPRECATE - case telemetry.ThrottleSetting: - binding.Transform = telemetry.FloatToUInt8TransformDEPRECATE - case telemetry.LFtempM: - binding.Transform = func(v any, out *telemetry.TelemetryField) { - out.Type = telemetry.DataTypeSTRING - out.Str = strconv.FormatFloat(float64(v.(float32)), 'f', 1, 32) - } - case telemetry.SessionTime: - binding.Transform = func(v any, out *telemetry.TelemetryField) { - out.Type = telemetry.DataTypeSTRING - out.Str = strconv.FormatFloat(v.(float64), 'f', 1, 32) - } - case telemetry.ReplaySessionTime: - binding.Transform = func(v any, out *telemetry.TelemetryField) { - out.Type = telemetry.DataTypeSTRING - out.Str = strconv.FormatFloat(v.(float64), 'f', 1, 32) - } - case telemetry.Empty: - binding.Transform = telemetry.EmptyTransform - case telemetry.LapLastLapTime: - binding.Transform = LapTimeTransform - case telemetry.LapNumber: - binding.Transform = telemetry.UInt8Transform + ID: id, } i.data.ActiveBinds = append(i.data.ActiveBinds, binding) diff --git a/providers/iracing/transforms.go b/providers/iracing/transforms.go index e3e64a4..5c9b74a 100644 --- a/providers/iracing/transforms.go +++ b/providers/iracing/transforms.go @@ -3,15 +3,124 @@ package iracing import ( "time" + conv "esdi/conversions" "esdi/telemetry" + + "github.com/ESilva15/goirsdk" ) const ( LapTimeFormatStr = "04:05.000" ) -func LapTimeTransform(v any, out *telemetry.TelemetryField) { - lapTimeInSeconds := v.(float32) +func getVar[T any](vars map[string]goirsdk.Var, key string, fallback T) T { + raw, ok := vars[key] + if !ok { + return fallback + } + + val, ok := raw.Value.(T) + if !ok { + return fallback + } + + return val +} + +func (i *IRacing) unused(out *telemetry.TelemetryField) { + out.Unused() +} + +func (i *IRacing) speed(out *telemetry.TelemetryField) { + speed := getVar(i.SDK.Vars.Vars, "Speed", float32(0)) + + out.Type = telemetry.DataTypeUINT16 + out.Raw = uint64(conv.MsToKph(speed)) +} + +func (i *IRacing) gear(out *telemetry.TelemetryField) { + gear := getVar(i.SDK.Vars.Vars, "Gear", int32(0)) + + out.Type = telemetry.DataTypeCHAR + + switch { + case gear == 0: + out.Raw = uint64('N') + case gear < 0: + out.Raw = uint64('R') + case gear > 0 && gear < 10: + out.Raw = uint64('0' + gear) + default: + out.Raw = uint64('?') // Fallback + } +} + +func (i *IRacing) rpm(out *telemetry.TelemetryField) { + rpm := getVar(i.SDK.Vars.Vars, "RPM", float32(0)) + + out.Type = telemetry.DataTypeUINT16 + out.Raw = uint64(uint16(rpm)) +} + +func (i *IRacing) fuelLevel(out *telemetry.TelemetryField) { + fuelLevel := getVar(i.SDK.Vars.Vars, "FuelLevel", float32(-1.0)) + telemetry.FloatToStringTransform(fuelLevel, out) +} + +// EngineWarnings +func (i *IRacing) pitSpeedLimiter(out *telemetry.TelemetryField) { + out.Type = telemetry.DataTypeSTRING + out.Str = " " + + if i.SDK.PitSpeedLimiter() { + out.Str = "PIT" + } +} + +// EngineData ↓ +func (i *IRacing) oilPress(out *telemetry.TelemetryField) { + oilPress := getVar(i.SDK.Vars.Vars, "OilPress", float32(-1.0)) + telemetry.FloatToStringTransform(oilPress, out) +} + +func (i *IRacing) oilTemp(out *telemetry.TelemetryField) { + oilTemp := getVar(i.SDK.Vars.Vars, "OilTemp", float32(-1.0)) + telemetry.FloatToStringTransform(oilTemp, out) +} + +func (i *IRacing) waterTemp(out *telemetry.TelemetryField) { + waterTemp := getVar(i.SDK.Vars.Vars, "WaterTemp", float32(-1.0)) + telemetry.FloatToStringTransform(waterTemp, out) +} + +// EngineData ↑ + +// Adjustments ↓ +func (i *IRacing) brakeBias(out *telemetry.TelemetryField) { + bb := getVar(i.SDK.Vars.Vars, "dcBrakeBias", float32(-1.0)) + telemetry.FloatToStringTransform(bb, out) +} + +func (i *IRacing) absSetting(out *telemetry.TelemetryField) { + abs := getVar(i.SDK.Vars.Vars, "dcABS", float32(-1.0)) + telemetry.FloatToUInt8Transform(abs, out) +} + +func (i *IRacing) tcSetting(out *telemetry.TelemetryField) { + tc := getVar(i.SDK.Vars.Vars, "dcTractionControl", float32(-1.0)) + telemetry.FloatToUInt8Transform(tc, out) +} + +func (i *IRacing) throttleSetting(out *telemetry.TelemetryField) { + throttle := getVar(i.SDK.Vars.Vars, "dcThrottleShape", float32(-1.0)) + telemetry.FloatToUInt8Transform(throttle, out) +} + +// Adjustments ↑ + +// Laps ↓ +func (i *IRacing) lapTime(out *telemetry.TelemetryField) { + lapTimeInSeconds := getVar(i.SDK.Vars.Vars, "LapLastLapTime", float32(0)) if lapTimeInSeconds < 0 { lapTimeInSeconds = 0 @@ -24,11 +133,9 @@ func LapTimeTransform(v any, out *telemetry.TelemetryField) { out.Str = lapTime.Format(LapTimeFormatStr) } -func PitSpeedLimiterTransform(v any, out *telemetry.TelemetryField) { - out.Type = telemetry.DataTypeSTRING - if v.(bool) { - out.Str = "PIT" - } else { - out.Str = " " - } +func (i *IRacing) lapNumber(out *telemetry.TelemetryField) { + lapNumber := getVar(i.SDK.Vars.Vars, "Lap", int(0)) + telemetry.UInt8Transform(lapNumber, out) } + +// Laps ↑ diff --git a/providers/iracing/utils.go b/providers/iracing/utils.go index b7b1d19..ac9d2df 100644 --- a/providers/iracing/utils.go +++ b/providers/iracing/utils.go @@ -1,16 +1,39 @@ package iracing -import "github.com/ESilva15/goirsdk" +import ( + "github.com/ESilva15/goirsdk" +) func IsRunning() bool { sdk, err := goirsdk.Init(goirsdk.Options{ SourceType: goirsdk.SharedMemoryFile, }) + defer sdk.Close() if err != nil { return false } - sdk.Close() + // if sdk.SessionStateInvalid() { + // slog.Debug("state is invalid") + // sessionState, ok := sdk.Vars.Vars["SessionState"] + // if !ok { + // slog.Debug("SessionState not ok") + // return false + // } + // + // state, ok := sessionState.Value.(int) + // if !ok { + // slog.Debug(fmt.Sprintf("can't typecast: %+v", sessionState)) + // return false + // } + // + // slog.Debug(fmt.Sprintf("%d", state)) + // return false + // } return true } + +func (i *IRacing) Stalled() bool { + return i.Stalled() +} diff --git a/providers/providers.go b/providers/providers.go index e635f39..f38c1b1 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -16,7 +16,7 @@ import ( type Provider struct { Name string NewProvider func(*slog.Logger) telemetry.TelemetryProvider - IsRunning func() bool + IsRunning func() bool // To check if this provider is up and running } var Providers = map[string]Provider{ @@ -32,15 +32,9 @@ var Providers = map[string]Provider{ }, } -// func NewIRacingProvider(logger *slog.Logger, opts goirsdk.Options, -// ) telemetry.TelemetryProvider { -// provider, _ := iracing.NewIRacingProvider(logger, opts) -// -// return provider -// } - func NewLiveIRacingProvider(logger *slog.Logger) telemetry.TelemetryProvider { provider, _ := iracing.NewIRacingProvider(logger, goirsdk.Options{ + Logger: logger, SourceType: goirsdk.SharedMemoryFile, })