5 Commits
Author SHA1 Message Date
esilva 52056a7da3 updated it to use the latest and greatest API change on bngsdk
May have introduce some stupid things, will look at them after
2026-07-01 00:20:12 +01:00
esilva 31a2f85407 added a view to the recording utility of BeamNG too 2026-06-29 11:27:27 +01:00
esilva b8d915af4f print the error when it throws an error
it might not even matter because it will just flash too fast
2026-06-29 11:23:12 +01:00
esilva 99b3891c60 Moved the outgauge data prints to a standalone function 2026-06-29 00:19:57 +01:00
esilva 988124ef60 Alignement 2026-06-26 18:00:40 +01:00
8 changed files with 243 additions and 104 deletions
+1
View File
@@ -1 +1,2 @@
*.bin *.bin
*.work*
+10 -1
View File
@@ -1,6 +1,7 @@
package cmd package cmd
import ( import (
"context"
"fmt" "fmt"
"github.com/ESilva15/BeamNGMockOg/mockserver" "github.com/ESilva15/BeamNGMockOg/mockserver"
@@ -13,7 +14,15 @@ func recordAction(cmd *cobra.Command, args []string) {
address, _ := cmd.Flags().GetString("address") address, _ := cmd.Flags().GetString("address")
port, _ := cmd.Flags().GetInt("port") port, _ := cmd.Flags().GetInt("port")
if err := mockserver.Record(address, port, outputFile); err != nil { recorder, err := mockserver.NewRecorder(outputFile, address, port)
if err != nil {
panic(fmt.Sprintf("failed to create new BeamNG recorder: %+v", err))
}
defer recorder.Close()
// NOTE: is this doing anything at all??
ctx := context.Background()
if err := recorder.Record(ctx); err != nil {
fmt.Printf("Something went wrong while recording the file: %v", err) fmt.Printf("Something went wrong while recording the file: %v", err)
} }
} }
+1
View File
@@ -21,6 +21,7 @@ func replayAction(cmd *cobra.Command, args []string) {
return return
} }
// NOTE: is this doing anything at all??
ctx := context.Background() ctx := context.Background()
if err := replayer.Replay(ctx, loop); err != nil { if err := replayer.Replay(ctx, loop); err != nil {
fmt.Printf("Something went wrong while playing the file: %v", err) fmt.Printf("Something went wrong while playing the file: %v", err)
+1 -3
View File
@@ -1,11 +1,9 @@
module github.com/ESilva15/BeamNGMockOg module github.com/ESilva15/BeamNGMockOg
replace github.com/ESilva15/gobngsdk => ../pkg/bngsdk
go 1.23.2 go 1.23.2
require ( require (
github.com/ESilva15/gobngsdk v0.0.0-00010101000000-000000000000 github.com/ESilva15/gobngsdk v1.1.1
github.com/spf13/cobra v1.10.1 github.com/spf13/cobra v1.10.1
) )
+2 -2
View File
@@ -1,5 +1,5 @@
github.com/ESilva15/gobngsdk v0.0.2 h1:N6stNOE14Wg80BAZMFu/Qd9Qa/J0DmExCfdPoDf7lco= github.com/ESilva15/gobngsdk v1.1.1 h1:MyKUjIqG77lYcUY6cnZpnnxPoxS/FdAYwGoi6InXan0=
github.com/ESilva15/gobngsdk v0.0.2/go.mod h1:cKLaZRgM0tGGXDvosaSjHnxeyC5Y6rg7zCLqEUJnOzw= github.com/ESilva15/gobngsdk v1.1.1/go.mod h1:cKLaZRgM0tGGXDvosaSjHnxeyC5Y6rg7zCLqEUJnOzw=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+116 -22
View File
@@ -1,47 +1,141 @@
package mockserver package mockserver
import ( import (
"context"
"encoding/binary" "encoding/binary"
"log" "fmt"
"os" "os"
"strings"
"sync"
"time" "time"
bngsdk "github.com/ESilva15/gobngsdk" bngsdk "github.com/ESilva15/gobngsdk"
) )
// NOTE: add some visual feedback of whats happening. type recorderViewData struct {
// Maybe reuse the replay view function TotalBytes int
SDK *bngsdk.BeamNGSDK
}
type Recorder struct {
SDK bngsdk.BeamNGSDK
OutputFile *os.File
TotalBytes int
// Views
mut sync.RWMutex
viewDataMut sync.RWMutex
viewData recorderViewData
viewCh chan *recorderViewData
recorderCh chan []byte
}
func NewRecorder(fp string, address string, port int) (*Recorder, error) {
var recorder Recorder
var err error
recorder.SDK, err = bngsdk.Init(address, port)
if err != nil {
return &Recorder{}, err
}
recorder.OutputFile, err = os.Create(fp)
if err != nil {
return &Recorder{}, err
}
recorder.viewData = recorderViewData{}
recorder.viewCh = make(chan *recorderViewData, 1)
recorder.recorderCh = make(chan []byte, 1)
return &recorder, nil
}
func (r *Recorder) Close() {
r.SDK.Close()
if r.OutputFile != nil {
r.OutputFile.Close()
}
}
func (r *Recorder) record(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case data := <-r.recorderCh:
r.mut.RLock()
err := binary.Write(r.OutputFile, binary.LittleEndian, data)
r.TotalBytes += len(data)
r.mut.RUnlock()
if err != nil {
// NOTE: find a way of logging this somehow
}
}
}
}
func (r *Recorder) view(ctx context.Context) {
var s strings.Builder
var nBytes int
for {
select {
case <-ctx.Done():
return
case viewData := <-r.viewCh:
s.Reset()
fmt.Fprintf(os.Stdout, "\x1b[2J\x1b[H")
stringifyRecordingProgress(&s, nBytes)
fmt.Fprintf(&s, "\n\n")
r.viewDataMut.RLock()
nBytes = viewData.TotalBytes
stringifyOutgaugeData(&s, &r.SDK)
r.viewDataMut.RUnlock()
fmt.Fprint(os.Stdout, s.String())
}
}
}
// Record records data from the UDP connection created by address and port // Record records data from the UDP connection created by address and port
func Record(address string, port int, filePath string) error { func (r *Recorder) Record(ctx context.Context) error {
ticker := time.NewTicker(time.Second / 60) ticker := time.NewTicker(time.Second / 60)
defer ticker.Stop() defer ticker.Stop()
// Create the output file go r.record(ctx)
bin, err := os.Create(filePath) go r.view(ctx)
if err != nil {
return err
}
defer bin.Close()
// Create the BeamNGSDK instance
beam, err := bngsdk.Init(address, port)
if err != nil {
return err
}
defer beam.Close()
for { for {
err := beam.ReadData() select {
case <-ctx.Done():
return nil
case <-ticker.C:
err := r.SDK.ReadData()
if err != nil { if err != nil {
return err return err
} }
err = binary.Write(bin, binary.LittleEndian, beam.Data) r.viewData.TotalBytes = r.TotalBytes
if err != nil {
log.Fatal(err) // Send the data to the view
select {
case r.viewCh <- &r.viewData:
// Sent the data
default:
// Dropped the frame!
} }
<-ticker.C // Send the data to the UDP socket
select {
case r.recorderCh <- r.SDK.Buffer:
// Sent the data
default:
// Dropped the frame!
}
}
} }
} }
+14 -67
View File
@@ -18,13 +18,14 @@ import (
) )
type ViewData struct { type ViewData struct {
Data []byte SDK *bngsdk.BeamNGSDK
SizeRead int64 SizeRead int64
} }
// Replayer does the replaying // Replayer does the replaying
// Should we make a "player" struct that can record and replay? // Should we make a "player" struct that can record and replay?
type Replayer struct { type Replayer struct {
SDK bngsdk.BeamNGSDK
DataSourcePath string DataSourcePath string
Socket *UDPTransport Socket *UDPTransport
@@ -34,7 +35,6 @@ type Replayer struct {
// Mut // Mut
mut sync.RWMutex mut sync.RWMutex
data []byte
// View // View
viewData ViewData viewData ViewData
@@ -48,8 +48,11 @@ func NewReplayer(address string, port int, fp string) (*Replayer, error) {
replayer := &Replayer{ replayer := &Replayer{
DataSourcePath: fp, DataSourcePath: fp,
SDK: bngsdk.BeamNGSDK{
Data: &bngsdk.Outgauge{},
Buffer: make([]byte, unsafe.Sizeof(bngsdk.Outgauge{})),
},
Socket: udp, Socket: udp,
data: make([]byte, unsafe.Sizeof(bngsdk.Outgauge{})),
viewData: ViewData{}, viewData: ViewData{},
dataViewCh: make(chan ViewData, 1), dataViewCh: make(chan ViewData, 1),
socketCh: make(chan []byte, 1), socketCh: make(chan []byte, 1),
@@ -69,7 +72,6 @@ func (r *Replayer) renderToTerminal(ctx context.Context) {
// NOTE: temporary until I make a better view // NOTE: temporary until I make a better view
var s strings.Builder var s strings.Builder
var bytesReader bytes.Reader var bytesReader bytes.Reader
og := bngsdk.Outgauge{}
for { for {
select { select {
@@ -80,74 +82,18 @@ func (r *Replayer) renderToTerminal(ctx context.Context) {
s.Reset() s.Reset()
fmt.Fprintf(os.Stdout, "\x1b[2J\x1b[H") fmt.Fprintf(os.Stdout, "\x1b[2J\x1b[H")
bytesReader.Reset(data.Data) bytesReader.Reset(data.SDK.Buffer)
err := binary.Read(&bytesReader, binary.LittleEndian, &og) err := binary.Read(&bytesReader, binary.LittleEndian, r.SDK.Data)
if err != nil { if err != nil {
fmt.Fprintf(&s, "FAILED TO PARSE DATA\nError: %+v", err) fmt.Fprintf(&s, "FAILED TO PARSE DATA\nError: %+v", err)
fmt.Fprint(os.Stdout, s.String())
continue continue
} }
percent := int(float64(data.SizeRead) / float64(fileInfo.Size()) * 100) percent := int(float64(data.SizeRead) / float64(fileInfo.Size()) * 100)
fmt.Fprintf(&s, "Replayed: %d%%\n", percent) fmt.Fprintf(&s, "Replayed: %d%%\n", percent)
// NOTE: write a string serialization function on the SDK itself
fmt.Fprint(&s, "Outgauge {\n")
fmt.Fprintf(&s, " Time: %d ms\n", og.Time)
fmt.Fprintf(&s, " Car: %s\n", og.Car)
fmt.Fprintf(&s, " Flags: %b\n", og.Flags)
fmt.Fprintf(&s, " Gear: %d\n", og.Gear)
fmt.Fprintf(&s, " Plid: %d\n", og.Plid)
fmt.Fprintf(&s, " Speed: %f m/s\n", og.Speed)
fmt.Fprintf(&s, " RPM: %f RPM\n", og.RPM)
fmt.Fprintf(&s, " Turbo: %f Bar\n", og.Turbo)
fmt.Fprintf(&s, " EngTemp: %f °C\n", og.EngTemp)
fmt.Fprintf(&s, " Fuel: %f\n", og.Fuel)
fmt.Fprintf(&s, " OilPressure: %f Bar\n", og.OilPressure)
fmt.Fprintf(&s, " OilTemp: %f °C\n", og.OilTemp)
fmt.Fprintf(&s, " DashLights: %b\n", og.DashLights)
fmt.Fprintf(&s, " ShowLights: %b\n", og.ShowLights)
fmt.Fprintf(&s, " Throttle: %f\n", og.Throttle)
fmt.Fprintf(&s, " Brakes: %f\n", og.Brake)
fmt.Fprintf(&s, " Clutch: %f\n", og.Clutch)
fmt.Fprintf(&s, " Display1: %s\n", og.Display1)
fmt.Fprintf(&s, " Display2: %s\n", og.Display2)
fmt.Fprintf(&s, " ID: %d\n", og.Display2)
fmt.Fprint(&s, "}\n\n")
fmt.Fprint(&s, "DashLights {\n") stringifyOutgaugeData(&s, data.SDK)
fmt.Fprintf(&s, " DL_SHIFT: %t\n", og.DashLights&bngsdk.DL_SHIFT != 0)
fmt.Fprintf(&s, " DL_FULLBEAM: %t\n", og.DashLights&bngsdk.DL_FULLBEAM != 0)
fmt.Fprintf(&s, " DL_HANDBRAKE: %t\n", og.DashLights&bngsdk.DL_HANDBRAKE != 0)
fmt.Fprintf(&s, " DL_PITSPEED: %t\n", og.DashLights&bngsdk.DL_PITSPEED != 0)
fmt.Fprintf(&s, " DL_TC: %t\n", og.DashLights&bngsdk.DL_TC != 0)
fmt.Fprintf(&s, " DL_SIGNAL_L: %t\n", og.DashLights&bngsdk.DL_SIGNAL_L != 0)
fmt.Fprintf(&s, " DL_SIGNAL_R: %t\n", og.DashLights&bngsdk.DL_SIGNAL_R != 0)
fmt.Fprintf(&s, " DL_SIGNAL_ANY: %t\n", og.DashLights&bngsdk.DL_SIGNAL_ANY != 0)
fmt.Fprintf(&s, " DL_OILWARN: %t\n", og.DashLights&bngsdk.DL_OILWARN != 0)
fmt.Fprintf(&s, " DL_BATTERY: %t\n", og.DashLights&bngsdk.DL_BATTERY != 0)
fmt.Fprintf(&s, " DL_ABS: %t\n", og.DashLights&bngsdk.DL_ABS != 0)
fmt.Fprintf(&s, " DL_SPARE: %t\n", og.DashLights&bngsdk.DL_SPARE != 0)
fmt.Fprint(&s, "}\n\n")
fmt.Fprint(&s, "ShowLights {\n") // Fixed typo "ShowLigths"
fmt.Fprintf(&s, " DL_SHIFT: %t\n", og.ShowLights&bngsdk.DL_SHIFT != 0)
fmt.Fprintf(&s, " DL_FULLBEAM: %t\n", og.ShowLights&bngsdk.DL_FULLBEAM != 0)
fmt.Fprintf(&s, " DL_HANDBRAKE: %t\n", og.ShowLights&bngsdk.DL_HANDBRAKE != 0)
fmt.Fprintf(&s, " DL_PITSPEED: %t\n", og.ShowLights&bngsdk.DL_PITSPEED != 0)
fmt.Fprintf(&s, " DL_TC: %t\n", og.ShowLights&bngsdk.DL_TC != 0)
fmt.Fprintf(&s, " DL_SIGNAL_L: %t\n", og.ShowLights&bngsdk.DL_SIGNAL_L != 0)
fmt.Fprintf(&s, " DL_SIGNAL_R: %t\n", og.ShowLights&bngsdk.DL_SIGNAL_R != 0)
fmt.Fprintf(&s, " DL_SIGNAL_ANY: %t\n", og.ShowLights&bngsdk.DL_SIGNAL_ANY != 0)
fmt.Fprintf(&s, " DL_OILWARN: %t\n", og.ShowLights&bngsdk.DL_OILWARN != 0)
fmt.Fprintf(&s, " DL_BATTERY: %t\n", og.ShowLights&bngsdk.DL_BATTERY != 0)
fmt.Fprintf(&s, " DL_ABS: %t\n", og.ShowLights&bngsdk.DL_ABS != 0)
fmt.Fprintf(&s, " DL_SPARE: %t\n", og.ShowLights&bngsdk.DL_SPARE != 0)
fmt.Fprint(&s, "}\n\n")
fmt.Fprint(&s, "Flags {\n")
fmt.Fprintf(&s, " OG_TURBO (Has Turbo): %t\n", og.Flags&bngsdk.OG_TURBO != 0)
fmt.Fprintf(&s, " OG_KM (Is Metric): %t\n", og.Flags&bngsdk.OG_KM != 0)
fmt.Fprintf(&s, " OG_BAR (Pressure): %t\n", og.Flags&bngsdk.OG_BAR != 0)
fmt.Fprint(&s, "}")
fmt.Fprint(os.Stdout, s.String()) fmt.Fprint(os.Stdout, s.String())
} }
@@ -195,7 +141,7 @@ func (r *Replayer) Replay(ctx context.Context, loop bool) error {
case <-ticker.C: case <-ticker.C:
r.mut.Lock() r.mut.Lock()
err := reader.Next(r.data) err := reader.Next(r.SDK.Buffer)
r.mut.Unlock() r.mut.Unlock()
if err == io.EOF { if err == io.EOF {
@@ -215,7 +161,8 @@ func (r *Replayer) Replay(ctx context.Context, loop bool) error {
return err return err
} }
r.viewData.Data = r.data // NOTE: Really like this???
r.viewData.SDK = &r.SDK
r.viewData.SizeRead = reader.TotalRead r.viewData.SizeRead = reader.TotalRead
// Send the data to the view // Send the data to the view
@@ -228,7 +175,7 @@ func (r *Replayer) Replay(ctx context.Context, loop bool) error {
// Send the data to the UDP socket // Send the data to the UDP socket
select { select {
case r.socketCh <- r.data: case r.socketCh <- r.SDK.Buffer:
// Sent the data // Sent the data
default: default:
// Dropped the frame! // Dropped the frame!
+89
View File
@@ -0,0 +1,89 @@
package mockserver
import (
"fmt"
"strings"
bngsdk "github.com/ESilva15/gobngsdk"
)
const (
_ = iota
KiB = 1 << (10 * iota)
MiB
GiB
)
func stringifyRecordingProgress(s *strings.Builder, nBytes int) {
if nBytes < KiB {
fmt.Fprintf(s, "%d B", nBytes)
} else if nBytes < MiB {
fmt.Fprintf(s, "%.2f KiB", float64(nBytes)/float64(KiB))
} else if nBytes < GiB {
fmt.Fprintf(s, "%.2f MiB", float64(nBytes)/float64(MiB))
} else {
fmt.Fprintf(s, "%.2f GiB", float64(nBytes)/float64(GiB))
}
}
func stringifyOutgaugeData(s *strings.Builder, sdk *bngsdk.BeamNGSDK) {
// NOTE: write a string serialization function on the SDK itself
fmt.Fprint(s, "Outgauge {\n")
fmt.Fprintf(s, " Time: %d ms\n", sdk.Data.Time)
fmt.Fprintf(s, " Car: %s\n", sdk.Data.Car)
fmt.Fprintf(s, " Flags: %b\n", sdk.Data.Flags)
fmt.Fprintf(s, " Gear: %d\n", sdk.Data.Gear)
fmt.Fprintf(s, " Plid: %d\n", sdk.Data.Plid)
fmt.Fprintf(s, " Speed: %f m/s\n", sdk.Data.Speed)
fmt.Fprintf(s, " RPM: %f RPM\n", sdk.Data.RPM)
fmt.Fprintf(s, " Turbo: %f Bar\n", sdk.Data.Turbo)
fmt.Fprintf(s, " EngTemp: %f °C\n", sdk.Data.EngTemp)
fmt.Fprintf(s, " Fuel: %f\n", sdk.Data.Fuel)
fmt.Fprintf(s, " OilPressure: %f Bar\n", sdk.Data.OilPressure)
fmt.Fprintf(s, " OilTemp: %f °C\n", sdk.Data.OilTemp)
fmt.Fprintf(s, " DashLights: %b\n", sdk.Data.DashLights)
fmt.Fprintf(s, " ShowLights: %b\n", sdk.Data.ShowLights)
fmt.Fprintf(s, " Throttle: %f\n", sdk.Data.Throttle)
fmt.Fprintf(s, " Brakes: %f\n", sdk.Data.Brake)
fmt.Fprintf(s, " Clutch: %f\n", sdk.Data.Clutch)
fmt.Fprintf(s, " Display1: %s\n", sdk.Data.Display1)
fmt.Fprintf(s, " Display2: %s\n", sdk.Data.Display2)
fmt.Fprintf(s, " ID: %d\n", sdk.Data.ID)
fmt.Fprint(s, "}\n\n")
fmt.Fprint(s, "DashLights {\n")
fmt.Fprintf(s, " DL_SHIFT: %t\n", sdk.HasShiftLight())
fmt.Fprintf(s, " DL_FULLBEAM: %t\n", sdk.HasHighBeamLight())
fmt.Fprintf(s, " DL_HANDBRAKE: %t\n", sdk.HasHandbrakeLight())
fmt.Fprintf(s, " DL_PITSPEED: %t\n", sdk.HasPitspeed())
fmt.Fprintf(s, " DL_TC: %t\n", sdk.HasTractionControlLight())
fmt.Fprintf(s, " DL_SIGNAL_L: %t\n", sdk.HasLeftIndicatorLight())
fmt.Fprintf(s, " DL_SIGNAL_R: %t\n", sdk.HasRightIndicatorLight())
fmt.Fprintf(s, " DL_SIGNAL_ANY: %t\n", sdk.HasAnyIndicatorLight())
fmt.Fprintf(s, " DL_OILWARN: %t\n", sdk.HasOilLight())
fmt.Fprintf(s, " DL_BATTERY: %t\n", sdk.HasBatteryLight())
fmt.Fprintf(s, " DL_ABS: %t\n", sdk.HasABSLight())
fmt.Fprintf(s, " DL_SPARE: %t\n", sdk.Data.DashLights&bngsdk.DL_SPARE != 0)
fmt.Fprint(s, "}\n\n")
fmt.Fprint(s, "ShowLights {\n") // Fixed typo "ShowLigths"
fmt.Fprintf(s, " DL_SHIFT: %t\n", sdk.ShiftLight())
fmt.Fprintf(s, " DL_FULLBEAM: %t\n", sdk.HighBeam())
fmt.Fprintf(s, " DL_HANDBRAKE: %t\n", sdk.Handbrake())
fmt.Fprintf(s, " DL_PITSPEED: %t\n", sdk.Pitspeed())
fmt.Fprintf(s, " DL_TC: %t\n", sdk.TractionControl())
fmt.Fprintf(s, " DL_SIGNAL_L: %t\n", sdk.LeftIndicator())
fmt.Fprintf(s, " DL_SIGNAL_R: %t\n", sdk.RightIndicator())
fmt.Fprintf(s, " DL_SIGNAL_ANY: %t\n", sdk.AnyIndicator())
fmt.Fprintf(s, " DL_OILWARN: %t\n", sdk.OilLight())
fmt.Fprintf(s, " DL_BATTERY: %t\n", sdk.BatteryLight())
fmt.Fprintf(s, " DL_ABS: %t\n", sdk.ABS())
fmt.Fprintf(s, " DL_SPARE: %t\n", sdk.Data.ShowLights&bngsdk.DL_SPARE != 0)
fmt.Fprint(s, "}\n\n")
fmt.Fprint(s, "Flags {\n")
fmt.Fprintf(s, " OG_TURBO (Has Turbo): %t\n", sdk.HasTurbo())
fmt.Fprintf(s, " OG_KM (Is Metric): %t\n", sdk.PrefersKm())
fmt.Fprintf(s, " OG_BAR (Pressure): %t\n", sdk.PrefersBAR())
fmt.Fprint(s, "}")
}