Support for BeamNG as a data provider
This commit is contained in:
@@ -29,22 +29,46 @@ Games to be implemented:
|
||||
|
||||
|
||||
## Roadmap
|
||||
- [ ] Implement the interface for a data source
|
||||
- [X] Implement the interface for a data source
|
||||
- [ ] Finish implementing BeamNG
|
||||
- [ ] Configuration of the peripherals via ESDI
|
||||
- [ ] Detection of the display
|
||||
- [ ] Fuel Calculator
|
||||
- [X] Fuel Calculator
|
||||
- [ ] LapTime Calculator
|
||||
- [ ] A very long list useful stuff like flags, position, more info about
|
||||
other drivers, track conditions and so on so forth
|
||||
- [ ] Dynamic data packets
|
||||
- [X] Dynamic data packets
|
||||
- [ ] More roadmap entries
|
||||
- [ ] Telemetry analysis tool
|
||||
- [ ] Better user interface
|
||||
|
||||
|
||||
## Debugging
|
||||
### Freezes:
|
||||
## Todos
|
||||
- [ ] Remove all the `slog.logger` passing and struct members and just configure
|
||||
`slog.Logger` once and use the default. Or think of something better
|
||||
- [ ] Do all the `NOTE`s and `BUG`s
|
||||
- [ ] Whats common among the providers? Should abstract whats common
|
||||
- [ ] Whats common with all the "streams" I have? Should abstract whats common
|
||||
|
||||
|
||||
## Development
|
||||
### Mockservers
|
||||
To mock [BeamNG.drive](https://www.beamng.com/game/), I built
|
||||
[BeaMNGMockOg](https://github.com/ESilva15/BeamNGMockOg) (should have though longer
|
||||
about the name).
|
||||
You create a recording by launching the BeamNG and then launching the mockserver with:
|
||||
`BeamNGMockOg record -a 127.0.0.1 -p 4443 -o output.bin`.
|
||||
|
||||
To replay the recording:
|
||||
`BeamNGMockOg replay [--loop] -a 127.0.0.1 -p 4443 -i input.bin`
|
||||
|
||||
### Troubleshooting
|
||||
#### Can't see BeamNG data coming through
|
||||
Use, for example, `tcpdump -i any udp port <port> -X` to check if any data is available.
|
||||
|
||||
|
||||
### Debugging
|
||||
#### Freezes:
|
||||
Using delve:
|
||||
- Launch Terminal1 with `dlv debug . --headless --listen=:2345 -- tui`
|
||||
- Launch Terminal2 and connect to with with `dlv connect :2345`
|
||||
@@ -59,7 +83,7 @@ pprof:
|
||||
- type `web` to view a graph version
|
||||
|
||||
|
||||
### Shameless begging
|
||||
## Shameless begging
|
||||
Hey, doesn't hurt to try, its free either way:
|
||||
[Buy me a coffee!](buymeacoffee.com/ESilva_15)
|
||||
|
||||
|
||||
+221
-15
@@ -1,43 +1,249 @@
|
||||
// Package beamng is the BeamNG.drive data provider
|
||||
package beamng
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
conv "esdi/conversions"
|
||||
"esdi/telemetry"
|
||||
|
||||
bngsdk "github.com/ESilva15/gobngsdk"
|
||||
)
|
||||
|
||||
type BeamNG struct {
|
||||
SDK *bngsdk.BNGSDK
|
||||
|
||||
// data handling
|
||||
mut sync.Mutex
|
||||
data *telemetry.TelemetryData
|
||||
|
||||
// stream control
|
||||
streamCh chan telemetry.TelemetryData
|
||||
streamCancel context.CancelFunc
|
||||
|
||||
// timing
|
||||
ticker *time.Ticker
|
||||
}
|
||||
|
||||
const (
|
||||
NAME = "BeamNG.drive"
|
||||
)
|
||||
|
||||
func Init(ip string, port int) (BeamNG, error) {
|
||||
var err error
|
||||
|
||||
sdk, err := bngsdk.Init(ip, port)
|
||||
func NewBeamNGProvider(ip string, port int) (*BeamNG, error) {
|
||||
beam, err := bngsdk.Init(ip, port)
|
||||
if err != nil {
|
||||
return BeamNG{}, err
|
||||
return &BeamNG{}, err
|
||||
}
|
||||
|
||||
return BeamNG{SDK: &sdk}, nil
|
||||
return &BeamNG{
|
||||
streamCh: make(chan telemetry.TelemetryData, 1),
|
||||
data: telemetry.NewTelemetryData(),
|
||||
SDK: &beam,
|
||||
ticker: time.NewTicker(time.Second / 60),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetData will retrieve a given field by its name from the OutGauge data
|
||||
func (b *BeamNG) GetData(fieldName string) (interface{}, error) {
|
||||
if val, ok := b.SDK.DataDict[fieldName]; ok {
|
||||
return val, nil
|
||||
func (b *BeamNG) StopStream() {
|
||||
if b.streamCancel == nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("key `%s` doesn't exist", fieldName)
|
||||
b.streamCancel()
|
||||
}
|
||||
|
||||
func (b *BeamNG) UpdateData() error {
|
||||
return b.SDK.ReadData()
|
||||
func (b *BeamNG) Stream() (<-chan telemetry.TelemetryData, error) {
|
||||
var ctx context.Context
|
||||
ctx, b.streamCancel = context.WithCancel(context.Background())
|
||||
|
||||
// Start the stream
|
||||
b.stream(ctx)
|
||||
|
||||
return b.streamCh, nil
|
||||
}
|
||||
|
||||
func (b *BeamNG) GetSessionInfo() (interface{}, error) {
|
||||
return nil, fmt.Errorf("Not implemented")
|
||||
func (b *BeamNG) Subscribe(requestFields map[int16]telemetry.FieldID) {
|
||||
slog.Debug(fmt.Sprintf("Len Req: %d\n", len(requestFields)))
|
||||
|
||||
b.data.ActiveBinds = make([]telemetry.BoundField, 0, len(requestFields))
|
||||
|
||||
// First we must add the virtual fields
|
||||
// we will add their dependencies and the primitives to a slice
|
||||
pendingBinds := make([]telemetry.FieldID, telemetry.MaxFields)
|
||||
|
||||
for winID, id := range requestFields {
|
||||
b.data.Values[id].IDs = append(b.data.Values[id].IDs, winID)
|
||||
|
||||
switch id {
|
||||
case telemetry.RPMStateColour:
|
||||
b.data.VirtualBinds = append(b.data.VirtualBinds, telemetry.NewRPMLights())
|
||||
case telemetry.FCCurrentLap:
|
||||
b.data.VirtualBinds = append(b.data.VirtualBinds,
|
||||
telemetry.NewFuelCalculator(slog.Default().WithGroup("FUEL CALC")))
|
||||
default:
|
||||
// primitive telemetry field
|
||||
pendingBinds = append(pendingBinds, id)
|
||||
}
|
||||
}
|
||||
|
||||
boundCheck := make(map[telemetry.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
|
||||
}
|
||||
|
||||
// Translate the UI FieldIDs to this provider's field names
|
||||
sdkKey, ok := internalToSDKFieldNames[id]
|
||||
if !ok {
|
||||
slog.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 = GearTransform
|
||||
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.FloatToStringTransform
|
||||
// Engine Data
|
||||
case telemetry.OilPress:
|
||||
binding.Transform = telemetry.FloatToStringTransform
|
||||
case telemetry.OilTemp:
|
||||
binding.Transform = telemetry.FloatToStringTransform
|
||||
case telemetry.WaterTemp:
|
||||
binding.Transform = telemetry.FloatToStringTransform
|
||||
// Something else
|
||||
case telemetry.PitSpeedLimiter:
|
||||
binding.Transform = PitSpeedLimiterTransform
|
||||
// Adjustements
|
||||
case telemetry.BrakeBias:
|
||||
binding.Transform = telemetry.FloatToStringTransform
|
||||
case telemetry.ABSSetting:
|
||||
binding.Transform = telemetry.FloatToUInt8Transform
|
||||
case telemetry.TCSetting:
|
||||
binding.Transform = telemetry.FloatToUInt8Transform
|
||||
case telemetry.ThrottleSetting:
|
||||
binding.Transform = telemetry.FloatToUInt8Transform
|
||||
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
|
||||
}
|
||||
|
||||
b.data.ActiveBinds = append(b.data.ActiveBinds, binding)
|
||||
boundCheck[id] = true
|
||||
}
|
||||
|
||||
slog.Debug(fmt.Sprintf("Subscribed: %+v\n", b.data.ActiveBinds))
|
||||
}
|
||||
|
||||
// Internal
|
||||
|
||||
func (b *BeamNG) readData() {
|
||||
slog.Debug("READING THIS DATA")
|
||||
// BUG: getting stuck in here
|
||||
err := b.SDK.ReadData()
|
||||
slog.Debug("THE DATA WAS READ")
|
||||
if err != nil {
|
||||
slog.Error("Error getting data", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
b.mut.Lock()
|
||||
defer b.mut.Unlock()
|
||||
|
||||
// Read 1 to 1 data
|
||||
slog.Debug("Reading normal data binds")
|
||||
for _, bind := range b.data.ActiveBinds {
|
||||
v := b.SDK.DataDict[bind.Key]
|
||||
|
||||
bind.Transform(v, &b.data.Values[bind.ID])
|
||||
}
|
||||
|
||||
// Set up virtual binds
|
||||
slog.Debug("Entering virtual binds loop")
|
||||
for _, vBind := range b.data.VirtualBinds {
|
||||
// NOTE: delete the logs here, they are really bad
|
||||
slog.Debug("Processing virtual binds")
|
||||
vBind.Process(b.data)
|
||||
}
|
||||
|
||||
b.data.PenultimateDataPoll = b.data.LastDataPoll
|
||||
b.data.LastDataPoll = time.Now()
|
||||
}
|
||||
|
||||
func (b *BeamNG) stream(ctx context.Context) {
|
||||
b.data.InitialTime = time.Now()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
// Explicitly intercept cancellation
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// We start by checking if we do or do not have data available
|
||||
// if !b.isDataAvailable() {
|
||||
// continue
|
||||
// }
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-b.ticker.C:
|
||||
slog.Debug("READING DATA")
|
||||
b.readData()
|
||||
slog.Debug("READ DATA")
|
||||
|
||||
// Publish data
|
||||
select {
|
||||
case b.streamCh <- *b.data:
|
||||
slog.Debug("PUBLISHED DATA")
|
||||
default:
|
||||
// skip this data, don't allow publishers to lag behind
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package beamng
|
||||
|
||||
// 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: "Fuel",
|
||||
// Engine Data
|
||||
telemetry.OilPress: "OilPressure",
|
||||
telemetry.OilTemp: "OilTemp",
|
||||
telemetry.WaterTemp: "EngTemp",
|
||||
// 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",
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package beamng
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"esdi/telemetry"
|
||||
)
|
||||
|
||||
const (
|
||||
LapTimeFormatStr = "04:05.000"
|
||||
)
|
||||
|
||||
// NOTE: this is a copy from iracing provider. Change it however necessary
|
||||
func LapTimeTransform(v any, out *telemetry.TelemetryField) {
|
||||
lapTimeInSeconds := v.(float32)
|
||||
|
||||
if lapTimeInSeconds < 0 {
|
||||
lapTimeInSeconds = 0
|
||||
}
|
||||
|
||||
wholeSeconds := int64(lapTimeInSeconds)
|
||||
lapTime := time.Unix(wholeSeconds, int64((lapTimeInSeconds-float32(wholeSeconds))*1e9))
|
||||
|
||||
out.Type = telemetry.DataTypeSTRING
|
||||
out.Str = lapTime.Format(LapTimeFormatStr)
|
||||
}
|
||||
|
||||
// NOTE: this is a copy from iracing provider. Change this however necessary in the future
|
||||
// for beamng.
|
||||
func PitSpeedLimiterTransform(v any, out *telemetry.TelemetryField) {
|
||||
out.Type = telemetry.DataTypeSTRING
|
||||
if v.(bool) {
|
||||
out.Str = "PIT"
|
||||
} else {
|
||||
out.Str = " "
|
||||
}
|
||||
}
|
||||
|
||||
func GearTransform(v any, out *telemetry.TelemetryField) {
|
||||
out.Type = telemetry.DataTypeCHAR
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
package iracing
|
||||
|
||||
import (
|
||||
telem "esdi/telemetry"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
LapTimeFormatStr = "04:05.000"
|
||||
)
|
||||
|
||||
func LapTimeTransform(v any, out *telem.TelemetryField) {
|
||||
lapTimeInSeconds := v.(float32)
|
||||
|
||||
if lapTimeInSeconds < 0 {
|
||||
lapTimeInSeconds = 0
|
||||
}
|
||||
|
||||
wholeSeconds := int64(lapTimeInSeconds)
|
||||
lapTime := time.Unix(wholeSeconds, int64((lapTimeInSeconds-float32(wholeSeconds))*1e9))
|
||||
|
||||
out.Type = telem.DataTypeSTRING
|
||||
out.Str = lapTime.Format(LapTimeFormatStr)
|
||||
}
|
||||
|
||||
func EmptyTransform(v any, out *telem.TelemetryField) {
|
||||
out.Type = telem.DataTypeCHAR
|
||||
out.Raw = uint64('-')
|
||||
}
|
||||
|
||||
func PitSpeedLimiterTransform(v any, out *telem.TelemetryField) {
|
||||
out.Type = telem.DataTypeSTRING
|
||||
if v.(bool) {
|
||||
out.Str = "PIT"
|
||||
} else {
|
||||
out.Str = " "
|
||||
}
|
||||
}
|
||||
|
||||
func UInt8Transform(v any, out *telem.TelemetryField) {
|
||||
out.Type = telem.DataTypeUINT8
|
||||
|
||||
if v == nil {
|
||||
out.Raw = 0
|
||||
return
|
||||
}
|
||||
|
||||
out.Raw = uint64(v.(int))
|
||||
}
|
||||
|
||||
func FloatToStringTransform(v any, out *telem.TelemetryField) {
|
||||
out.Type = telem.DataTypeSTRING
|
||||
|
||||
if v == nil {
|
||||
out.Str = "inv"
|
||||
return
|
||||
}
|
||||
|
||||
out.Str = strconv.FormatFloat(float64(v.(float32)), 'f', 1, 32)
|
||||
}
|
||||
|
||||
func FloatToUInt8Transform(v any, out *telem.TelemetryField) {
|
||||
out.Type = telem.DataTypeUINT8
|
||||
|
||||
if v == nil {
|
||||
out.Raw = uint64(0)
|
||||
return
|
||||
}
|
||||
|
||||
out.Raw = uint64(v.(float32))
|
||||
}
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"time"
|
||||
|
||||
conv "esdi/conversions"
|
||||
telem "esdi/telemetry"
|
||||
"esdi/telemetry"
|
||||
|
||||
"github.com/ESilva15/goirsdk"
|
||||
)
|
||||
@@ -29,13 +29,13 @@ type IRacing struct {
|
||||
|
||||
// Data Handling
|
||||
mut sync.Mutex
|
||||
data *telem.TelemetryData
|
||||
data *telemetry.TelemetryData
|
||||
|
||||
// Timing information
|
||||
ticker *time.Ticker // ticker will keep polling intervals constant
|
||||
|
||||
// Stream
|
||||
streamCh chan telem.TelemetryData
|
||||
streamCh chan telemetry.TelemetryData
|
||||
streamCancel context.CancelFunc
|
||||
}
|
||||
|
||||
@@ -68,8 +68,8 @@ func NewIRacingProvider(
|
||||
return &IRacing{
|
||||
logger: logger,
|
||||
SDK: sdk,
|
||||
data: telem.NewTelemetryData(),
|
||||
streamCh: make(chan telem.TelemetryData, 1),
|
||||
data: telemetry.NewTelemetryData(),
|
||||
streamCh: make(chan telemetry.TelemetryData, 1),
|
||||
// NOTE: This is because I stupidly recorded a test IBT file in 240
|
||||
ticker: time.NewTicker(time.Second / 240),
|
||||
}, nil
|
||||
@@ -157,7 +157,7 @@ func (i *IRacing) readData() {
|
||||
|
||||
// Stream returns a channel that we will use to funnel the telemetry data back to the
|
||||
// UI, which then should broadcast it to the devices
|
||||
func (i *IRacing) Stream() (<-chan telem.TelemetryData, error) {
|
||||
func (i *IRacing) Stream() (<-chan telemetry.TelemetryData, error) {
|
||||
var ctx context.Context
|
||||
ctx, i.streamCancel = context.WithCancel(context.Background())
|
||||
|
||||
@@ -176,31 +176,31 @@ func (i *IRacing) StopStream() {
|
||||
i.streamCancel = nil
|
||||
}
|
||||
|
||||
func (i *IRacing) Subscribe(requestFields map[int16]telem.FieldID) {
|
||||
func (i *IRacing) Subscribe(requestFields map[int16]telemetry.FieldID) {
|
||||
i.logger.Debug(fmt.Sprintf("Len Req: %d\n", len(requestFields)))
|
||||
|
||||
i.data.ActiveBinds = make([]telem.BoundField, 0, len(requestFields))
|
||||
i.data.ActiveBinds = make([]telemetry.BoundField, 0, len(requestFields))
|
||||
|
||||
// First we must add the virtual fields
|
||||
// we will add their dependencies and the primitives to a slice
|
||||
pendingBinds := make([]telem.FieldID, telem.MaxFields)
|
||||
pendingBinds := make([]telemetry.FieldID, telemetry.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.FCCurrentLap:
|
||||
case telemetry.RPMStateColour:
|
||||
i.data.VirtualBinds = append(i.data.VirtualBinds, telemetry.NewRPMLights())
|
||||
case telemetry.FCCurrentLap:
|
||||
i.data.VirtualBinds = append(i.data.VirtualBinds,
|
||||
telem.NewFuelCalculator(i.logger.WithGroup("FUEL CALC")))
|
||||
telemetry.NewFuelCalculator(i.logger.WithGroup("FUEL CALC")))
|
||||
default:
|
||||
// primitive telemetry field
|
||||
pendingBinds = append(pendingBinds, id)
|
||||
}
|
||||
}
|
||||
|
||||
boundCheck := make(map[telem.FieldID]bool)
|
||||
boundCheck := make(map[telemetry.FieldID]bool)
|
||||
|
||||
// Now that we know all the fields we need to bind we follow the binding procedure
|
||||
for _, id := range pendingBinds {
|
||||
@@ -217,20 +217,20 @@ func (i *IRacing) Subscribe(requestFields map[int16]telem.FieldID) {
|
||||
continue
|
||||
}
|
||||
|
||||
binding := telem.BoundField{
|
||||
binding := telemetry.BoundField{
|
||||
Key: sdkKey,
|
||||
ID: id,
|
||||
}
|
||||
|
||||
switch id {
|
||||
case telem.Speed:
|
||||
binding.Transform = func(v any, out *telem.TelemetryField) {
|
||||
out.Type = telem.DataTypeUINT16
|
||||
case telemetry.Speed:
|
||||
binding.Transform = func(v any, out *telemetry.TelemetryField) {
|
||||
out.Type = telemetry.DataTypeUINT16
|
||||
out.Raw = uint64(conv.MsToKph(v.(float32)))
|
||||
}
|
||||
case telem.Gear:
|
||||
binding.Transform = func(v any, out *telem.TelemetryField) {
|
||||
out.Type = telem.DataTypeCHAR
|
||||
case telemetry.Gear:
|
||||
binding.Transform = func(v any, out *telemetry.TelemetryField) {
|
||||
out.Type = telemetry.DataTypeCHAR
|
||||
// out.Raw = uint64(v.(int))
|
||||
|
||||
gear := 0
|
||||
@@ -253,53 +253,53 @@ func (i *IRacing) Subscribe(requestFields map[int16]telem.FieldID) {
|
||||
out.Raw = uint64('?') // Fallback
|
||||
}
|
||||
}
|
||||
case telem.RPM:
|
||||
binding.Transform = func(v any, out *telem.TelemetryField) {
|
||||
out.Type = telem.DataTypeUINT16
|
||||
case telemetry.RPM:
|
||||
binding.Transform = func(v any, out *telemetry.TelemetryField) {
|
||||
out.Type = telemetry.DataTypeUINT16
|
||||
out.Raw = uint64(uint16(v.(float32)))
|
||||
}
|
||||
case telem.FuelLevel:
|
||||
binding.Transform = FloatToStringTransform
|
||||
case telemetry.FuelLevel:
|
||||
binding.Transform = telemetry.FloatToStringTransform
|
||||
// Engine Data
|
||||
case telem.OilPress:
|
||||
binding.Transform = FloatToStringTransform
|
||||
case telem.OilTemp:
|
||||
binding.Transform = FloatToStringTransform
|
||||
case telem.WaterTemp:
|
||||
binding.Transform = FloatToStringTransform
|
||||
case telemetry.OilPress:
|
||||
binding.Transform = telemetry.FloatToStringTransform
|
||||
case telemetry.OilTemp:
|
||||
binding.Transform = telemetry.FloatToStringTransform
|
||||
case telemetry.WaterTemp:
|
||||
binding.Transform = telemetry.FloatToStringTransform
|
||||
// Something else
|
||||
case telem.PitSpeedLimiter:
|
||||
case telemetry.PitSpeedLimiter:
|
||||
binding.Transform = PitSpeedLimiterTransform
|
||||
// Adjustements
|
||||
case telem.BrakeBias:
|
||||
binding.Transform = FloatToStringTransform
|
||||
case telem.ABSSetting:
|
||||
binding.Transform = FloatToUInt8Transform
|
||||
case telem.TCSetting:
|
||||
binding.Transform = FloatToUInt8Transform
|
||||
case telem.ThrottleSetting:
|
||||
binding.Transform = FloatToUInt8Transform
|
||||
case telem.LFtempM:
|
||||
binding.Transform = func(v any, out *telem.TelemetryField) {
|
||||
out.Type = telem.DataTypeSTRING
|
||||
case telemetry.BrakeBias:
|
||||
binding.Transform = telemetry.FloatToStringTransform
|
||||
case telemetry.ABSSetting:
|
||||
binding.Transform = telemetry.FloatToUInt8Transform
|
||||
case telemetry.TCSetting:
|
||||
binding.Transform = telemetry.FloatToUInt8Transform
|
||||
case telemetry.ThrottleSetting:
|
||||
binding.Transform = telemetry.FloatToUInt8Transform
|
||||
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 telem.SessionTime:
|
||||
binding.Transform = func(v any, out *telem.TelemetryField) {
|
||||
out.Type = telem.DataTypeSTRING
|
||||
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 telem.ReplaySessionTime:
|
||||
binding.Transform = func(v any, out *telem.TelemetryField) {
|
||||
out.Type = telem.DataTypeSTRING
|
||||
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 telem.Empty:
|
||||
binding.Transform = EmptyTransform
|
||||
case telem.LapLastLapTime:
|
||||
case telemetry.Empty:
|
||||
binding.Transform = telemetry.EmptyTransform
|
||||
case telemetry.LapLastLapTime:
|
||||
binding.Transform = LapTimeTransform
|
||||
case telem.LapNumber:
|
||||
binding.Transform = UInt8Transform
|
||||
case telemetry.LapNumber:
|
||||
binding.Transform = telemetry.UInt8Transform
|
||||
}
|
||||
|
||||
i.data.ActiveBinds = append(i.data.ActiveBinds, binding)
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package iracing
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"esdi/telemetry"
|
||||
)
|
||||
|
||||
const (
|
||||
LapTimeFormatStr = "04:05.000"
|
||||
)
|
||||
|
||||
func LapTimeTransform(v any, out *telemetry.TelemetryField) {
|
||||
lapTimeInSeconds := v.(float32)
|
||||
|
||||
if lapTimeInSeconds < 0 {
|
||||
lapTimeInSeconds = 0
|
||||
}
|
||||
|
||||
wholeSeconds := int64(lapTimeInSeconds)
|
||||
lapTime := time.Unix(wholeSeconds, int64((lapTimeInSeconds-float32(wholeSeconds))*1e9))
|
||||
|
||||
out.Type = telemetry.DataTypeSTRING
|
||||
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 = " "
|
||||
}
|
||||
}
|
||||
@@ -32,3 +32,8 @@ func NewIRacingProvider(logger *slog.Logger, source string,
|
||||
|
||||
return provider
|
||||
}
|
||||
|
||||
func NewBeamNGProvider(ip string, port int) telemetry.TelemetryProvider {
|
||||
provider, _ := beamng.NewBeamNGProvider(ip, port)
|
||||
return provider
|
||||
}
|
||||
|
||||
@@ -19,7 +19,6 @@ type TelemetryService struct {
|
||||
ativeProvider telem.TelemetryProvider
|
||||
// Channel for the UI
|
||||
listeners map[string]chan telem.TelemetryData
|
||||
uiOutCh chan telem.TelemetryData
|
||||
cancelForward context.CancelFunc
|
||||
}
|
||||
|
||||
@@ -27,22 +26,19 @@ func NewTelemetryService(logger *slog.Logger, cdash *CDashService) *TelemetrySer
|
||||
newService := &TelemetryService{
|
||||
logger: logger,
|
||||
cdash: cdash,
|
||||
uiOutCh: make(chan telem.TelemetryData, 5),
|
||||
listeners: make(map[string]chan telem.TelemetryData),
|
||||
}
|
||||
|
||||
// Need to instantiate a default provider here
|
||||
source := "/home/esilva/Desktop/projetos/simracing_peripherals/testTelemetry/gt3_mustang_bathurst.ibt"
|
||||
firstProvider := providers.NewIRacingProvider(slog.Default(), source, "", "")
|
||||
// source := "/home/esilva/Desktop/projetos/simracing_peripherals/testTelemetry/gt3_mustang_bathurst.ibt"
|
||||
// firstProvider := providers.NewIRacingProvider(slog.Default(), source, "", "")
|
||||
firstProvider := providers.NewBeamNGProvider("127.0.0.1", 4443)
|
||||
|
||||
newService.SwitchProvider(firstProvider)
|
||||
|
||||
return newService
|
||||
}
|
||||
|
||||
// func (t *TelemetryService) GetUIStream() <-chan telem.TelemetryData {
|
||||
// return t.uiOutCh
|
||||
// }
|
||||
|
||||
func (t *TelemetryService) SwitchProvider(newProvider telem.TelemetryProvider) error {
|
||||
t.mut.Lock()
|
||||
defer t.mut.Unlock()
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package telemetry
|
||||
|
||||
import "strconv"
|
||||
|
||||
func EmptyTransform(v any, out *TelemetryField) {
|
||||
out.Type = DataTypeCHAR
|
||||
out.Raw = uint64('-')
|
||||
}
|
||||
|
||||
func UInt8Transform(v any, out *TelemetryField) {
|
||||
out.Type = DataTypeUINT8
|
||||
|
||||
if v == nil {
|
||||
out.Raw = 0
|
||||
return
|
||||
}
|
||||
|
||||
out.Raw = uint64(v.(int))
|
||||
}
|
||||
|
||||
func FloatToStringTransform(v any, out *TelemetryField) {
|
||||
out.Type = DataTypeSTRING
|
||||
|
||||
if v == nil {
|
||||
out.Str = "inv"
|
||||
return
|
||||
}
|
||||
|
||||
out.Str = strconv.FormatFloat(float64(v.(float32)), 'f', 1, 32)
|
||||
}
|
||||
|
||||
func FloatToUInt8Transform(v any, out *TelemetryField) {
|
||||
out.Type = DataTypeUINT8
|
||||
|
||||
if v == nil {
|
||||
out.Raw = uint64(0)
|
||||
return
|
||||
}
|
||||
|
||||
out.Raw = uint64(v.(float32))
|
||||
}
|
||||
@@ -31,7 +31,7 @@ func NewLayoutController(base *Controller, service *services.CDashService) *Layo
|
||||
Messages: make(chan string, 10),
|
||||
DevService: service,
|
||||
MoveToolState: &windowManipState{Mode: moveMode},
|
||||
SelectedLayout: "layout.yaml",
|
||||
SelectedLayout: "beamng.yaml",
|
||||
}
|
||||
|
||||
lc.registerHooks()
|
||||
|
||||
@@ -35,7 +35,7 @@ func NewStreamingCtrl(
|
||||
serCDash *services.CDashService,
|
||||
serTelem *services.TelemetryService,
|
||||
) *StreamingCtrl {
|
||||
// NOTE: looks sus
|
||||
// NOTE: looks sus, put this somewhere also. Not very good in here
|
||||
providerList := []providers.Provider{}
|
||||
for _, item := range providers.Providers {
|
||||
providerList = append(providerList, item)
|
||||
|
||||
Reference in New Issue
Block a user