Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
047deb65e7 | ||
|
|
f1728c11da | ||
|
|
245d6ac0e1 | ||
|
|
c3ee9c0f44 | ||
|
|
31140b5df4 | ||
|
|
3857f5983f | ||
|
|
88f50218a4 | ||
|
|
4251348c15 | ||
|
|
502bf03835 | ||
|
|
ee03eab4c1 | ||
|
|
bfc9882bb2 | ||
|
|
9a9d486f9f |
@@ -1,4 +1,32 @@
|
||||
# BeamNG SDK
|
||||
Simple SDK to interact with BeamNG.drive OutGauge data.
|
||||
There's some features missing listed in a comment in the
|
||||
`outgauge.go` file. I will implement them as necessary.
|
||||
|
||||
## Development
|
||||
### Performance
|
||||
`go test -bench=BenchmarkReadData -benchmem -memprofile=mem.pprof`
|
||||
replace the function to be tested
|
||||
|
||||
Use `go tool pprof` to analyze the results
|
||||
|
||||
#### Results
|
||||
```bash
|
||||
# Previous footprint
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/ESilva15/gobngsdk
|
||||
cpu: AMD Ryzen 7 5800X3D 8-Core Processor
|
||||
BenchmarkReadData-16 320931 4058 ns/op 100 B/op 2 allocs/op
|
||||
PASS
|
||||
ok github.com/ESilva15/gobngsdk 1.345s
|
||||
|
||||
# New footprint
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: github.com/ESilva15/gobngsdk
|
||||
cpu: AMD Ryzen 7 5800X3D 8-Core Processor
|
||||
BenchmarkReadData-16 362514 3188 ns/op 4 B/op 1 allocs/op
|
||||
PASS
|
||||
ok github.com/ESilva15/gobngsdk 1.194s
|
||||
|
||||
# Pretty good enough. I can finally go be productive instead of "procrastinating" here
|
||||
```
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
// Package bngsdk defines an API to interact with the BeamNG outgauge data in Go
|
||||
package bngsdk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
)
|
||||
|
||||
const (
|
||||
UDP_IP = "127.0.0.1"
|
||||
UDP_PORT = 4444
|
||||
// DefaultUDPIP is the IP of the UDP outgauge server
|
||||
DefaultUDPIP = "127.0.0.1"
|
||||
// DefaultUDPPort is the port of the UDP outgauge server
|
||||
DefaultUDPPort = 4444
|
||||
)
|
||||
|
||||
type BNGSDK struct {
|
||||
Addr *net.UDPAddr
|
||||
Conn *net.UDPConn
|
||||
Buffer []byte
|
||||
Data *Outgauge
|
||||
DataDict map[string]interface{}
|
||||
type BeamNGSDK struct {
|
||||
Addr *net.UDPAddr
|
||||
Conn *net.UDPConn
|
||||
Buffer []byte
|
||||
Data Outgauge
|
||||
}
|
||||
|
||||
func createUDPConnection(ip string, port int) (*net.UDPConn, *net.UDPAddr, error) {
|
||||
@@ -36,8 +38,34 @@ func createUDPConnection(ip string, port int) (*net.UDPConn, *net.UDPAddr, error
|
||||
return conn, addr, nil
|
||||
}
|
||||
|
||||
// parseData will parse the bytes read from the socket into the Outgauge struct
|
||||
func (sdk *BeamNGSDK) parseData() error {
|
||||
sdk.Data.Time = binary.LittleEndian.Uint32(sdk.Buffer[0:4])
|
||||
copy(sdk.Data.Car[:], sdk.Buffer[4:8])
|
||||
sdk.Data.Flags = binary.LittleEndian.Uint16(sdk.Buffer[8:10])
|
||||
sdk.Data.Gear = int8(sdk.Buffer[10])
|
||||
sdk.Data.Plid = int8(sdk.Buffer[11])
|
||||
sdk.Data.Speed = math.Float32frombits(binary.LittleEndian.Uint32(sdk.Buffer[12:16]))
|
||||
sdk.Data.RPM = math.Float32frombits(binary.LittleEndian.Uint32(sdk.Buffer[16:20]))
|
||||
sdk.Data.Turbo = math.Float32frombits(binary.LittleEndian.Uint32(sdk.Buffer[20:24]))
|
||||
sdk.Data.EngTemp = math.Float32frombits(binary.LittleEndian.Uint32(sdk.Buffer[24:28]))
|
||||
sdk.Data.Fuel = math.Float32frombits(binary.LittleEndian.Uint32(sdk.Buffer[28:32]))
|
||||
sdk.Data.OilPressure = math.Float32frombits(binary.LittleEndian.Uint32(sdk.Buffer[32:36]))
|
||||
sdk.Data.OilTemp = math.Float32frombits(binary.LittleEndian.Uint32(sdk.Buffer[36:40]))
|
||||
sdk.Data.DashLights = binary.LittleEndian.Uint32(sdk.Buffer[40:44])
|
||||
sdk.Data.ShowLights = binary.LittleEndian.Uint32(sdk.Buffer[44:48])
|
||||
sdk.Data.Throttle = math.Float32frombits(binary.LittleEndian.Uint32(sdk.Buffer[48:52]))
|
||||
sdk.Data.Brake = math.Float32frombits(binary.LittleEndian.Uint32(sdk.Buffer[52:56]))
|
||||
sdk.Data.Throttle = math.Float32frombits(binary.LittleEndian.Uint32(sdk.Buffer[56:60]))
|
||||
copy(sdk.Data.Display1[:], sdk.Buffer[60:76])
|
||||
copy(sdk.Data.Display2[:], sdk.Buffer[76:92])
|
||||
sdk.Data.ID = int32(binary.LittleEndian.Uint32(sdk.Buffer[92:96]))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReadData will read new data from the UDP server
|
||||
func (sdk *BNGSDK) ReadData() error {
|
||||
func (sdk *BeamNGSDK) ReadData() error {
|
||||
// Receive data from the socket
|
||||
n, _, err := sdk.Conn.ReadFromUDP(sdk.Buffer)
|
||||
if err != nil {
|
||||
@@ -46,44 +74,216 @@ func (sdk *BNGSDK) ReadData() error {
|
||||
}
|
||||
|
||||
// Check if enough data was received to fill our struct
|
||||
if n < binary.Size(Outgauge{}) {
|
||||
if n < outgaugeSize {
|
||||
fmt.Println("Received packet too small for Outgauge struct")
|
||||
return err
|
||||
}
|
||||
|
||||
// Read the binary data into the struct
|
||||
reader := bytes.NewReader(sdk.Buffer[:n])
|
||||
if err := binary.Read(reader, binary.LittleEndian, sdk.Data); err != nil {
|
||||
err = sdk.parseData()
|
||||
if err != nil {
|
||||
fmt.Println("Error decoding UDP packet:", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the local map
|
||||
sdk.DataDict = sdk.Data.ToMap()
|
||||
|
||||
// this means there's new data
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sdk *BNGSDK) Close() {
|
||||
sdk.Conn.Close()
|
||||
func (sdk *BeamNGSDK) Close() error {
|
||||
return sdk.Conn.Close()
|
||||
}
|
||||
|
||||
// Init initializes a BeamNG SDK struct
|
||||
func Init(ip string, port int) (BNGSDK, error) {
|
||||
// NOTE: Change this to output a *BeamNGSDK
|
||||
func Init(ip string, port int) (BeamNGSDK, error) {
|
||||
var err error
|
||||
sdk := BNGSDK{}
|
||||
sdk := BeamNGSDK{}
|
||||
|
||||
// Create the connection to the OutGauge server
|
||||
sdk.Conn, sdk.Addr, err = createUDPConnection(ip, port)
|
||||
if err != nil {
|
||||
return BNGSDK{}, err
|
||||
return BeamNGSDK{}, err
|
||||
}
|
||||
|
||||
// Initiate the data variables
|
||||
sdk.Buffer = make([]byte, 1024)
|
||||
sdk.Data = &Outgauge{}
|
||||
sdk.DataDict = sdk.Data.ToMap()
|
||||
|
||||
return sdk, nil
|
||||
}
|
||||
|
||||
// SDK utilities
|
||||
|
||||
// ShowLights - functions to check if a given dash light is on [START]
|
||||
|
||||
// ShiftLight reports whether the shift light is on
|
||||
func (sdk *BeamNGSDK) ShiftLight() bool {
|
||||
return sdk.Data.ShowLights&DL_SHIFT != 0
|
||||
}
|
||||
|
||||
// HighBeam reports whether high beams are on
|
||||
func (sdk *BeamNGSDK) HighBeam() bool {
|
||||
return sdk.Data.ShowLights&DL_FULLBEAM != 0
|
||||
}
|
||||
|
||||
// Handbrake reports whether the handbrake is pulled
|
||||
func (sdk *BeamNGSDK) Handbrake() bool {
|
||||
return sdk.Data.ShowLights&DL_HANDBRAKE != 0
|
||||
}
|
||||
|
||||
// Pitspeed reports whether the pit speed limiter is engaged
|
||||
//
|
||||
// NOTE: this may not be used in BeamNG.drive, haven't checked yet
|
||||
func (sdk *BeamNGSDK) Pitspeed() bool {
|
||||
return sdk.Data.ShowLights&DL_PITSPEED != 0
|
||||
}
|
||||
|
||||
// TractionControl reports wheter TC is engaged
|
||||
func (sdk *BeamNGSDK) TractionControl() bool {
|
||||
return sdk.Data.ShowLights&DL_TC != 0
|
||||
}
|
||||
|
||||
// LeftIndicator reports whether the left indicator is on
|
||||
func (sdk *BeamNGSDK) LeftIndicator() bool {
|
||||
return sdk.Data.ShowLights&DL_SIGNAL_L != 0
|
||||
}
|
||||
|
||||
// RightIndicator reports wheter the right indicator is on
|
||||
func (sdk *BeamNGSDK) RightIndicator() bool {
|
||||
return sdk.Data.ShowLights&DL_SIGNAL_R != 0
|
||||
}
|
||||
|
||||
// AnyIndicator reports whether any indicator is on
|
||||
func (sdk *BeamNGSDK) AnyIndicator() bool {
|
||||
return sdk.Data.ShowLights&DL_SIGNAL_ANY != 0
|
||||
}
|
||||
|
||||
// OilLight reports whether the oil warning light is on
|
||||
func (sdk *BeamNGSDK) OilLight() bool {
|
||||
return sdk.Data.ShowLights&DL_OILWARN != 0
|
||||
}
|
||||
|
||||
// BatteryLight reports whether the battery light is on
|
||||
func (sdk *BeamNGSDK) BatteryLight() bool {
|
||||
return sdk.Data.ShowLights&DL_BATTERY != 0
|
||||
}
|
||||
|
||||
// ABS reports whether the ABS light is on
|
||||
func (sdk *BeamNGSDK) ABS() bool {
|
||||
return sdk.Data.ShowLights&DL_ABS != 0
|
||||
}
|
||||
|
||||
// ShowLights - functions to check if a given dash light is on [END]
|
||||
|
||||
// DashLights - functions to check if a given dash light is provided [START]
|
||||
|
||||
// HasShiftLight reports whether a shift light is available
|
||||
func (sdk *BeamNGSDK) HasShiftLight() bool {
|
||||
return sdk.Data.DashLights&DL_SHIFT != 0
|
||||
}
|
||||
|
||||
// HasHighBeamLight reports whether a high beam light is available
|
||||
func (sdk *BeamNGSDK) HasHighBeamLight() bool {
|
||||
return sdk.Data.DashLights&DL_FULLBEAM != 0
|
||||
}
|
||||
|
||||
// HasHandbrakeLight reports wheter a handbrake light is available
|
||||
func (sdk *BeamNGSDK) HasHandbrakeLight() bool {
|
||||
return sdk.Data.DashLights&DL_HANDBRAKE != 0
|
||||
}
|
||||
|
||||
// HasPitspeed reports whether a pit speed limitr is available
|
||||
// NOTE: this may not be used in BeamNG.drive, haven't checked yet
|
||||
func (sdk *BeamNGSDK) HasPitspeed() bool {
|
||||
return sdk.Data.DashLights&DL_HANDBRAKE != 0
|
||||
}
|
||||
|
||||
// HasTractionControlLight reports whether a traction control light is available
|
||||
func (sdk *BeamNGSDK) HasTractionControlLight() bool {
|
||||
return sdk.Data.DashLights&DL_TC != 0
|
||||
}
|
||||
|
||||
// HasLeftIndicatorLight reports whether a left indicator is available
|
||||
func (sdk *BeamNGSDK) HasLeftIndicatorLight() bool {
|
||||
return sdk.Data.DashLights&DL_SIGNAL_L != 0
|
||||
}
|
||||
|
||||
// HasRightIndicatorLight reports whether a right indicator is available
|
||||
func (sdk *BeamNGSDK) HasRightIndicatorLight() bool {
|
||||
return sdk.Data.DashLights&DL_SIGNAL_R != 0
|
||||
}
|
||||
|
||||
// HasAnyIndicatorLight reports whether an any indicator light is available
|
||||
func (sdk *BeamNGSDK) HasAnyIndicatorLight() bool {
|
||||
return sdk.Data.DashLights&DL_SIGNAL_ANY != 0
|
||||
}
|
||||
|
||||
// HasOilLight reports whether a oil light is available
|
||||
func (sdk *BeamNGSDK) HasOilLight() bool {
|
||||
return sdk.Data.DashLights&DL_OILWARN != 0
|
||||
}
|
||||
|
||||
// HasBatteryLight reports whether a battery light is available
|
||||
func (sdk *BeamNGSDK) HasBatteryLight() bool {
|
||||
return sdk.Data.DashLights&DL_BATTERY != 0
|
||||
}
|
||||
|
||||
// HasABSLight reports whether an ABS light is available
|
||||
func (sdk *BeamNGSDK) HasABSLight() bool {
|
||||
return sdk.Data.DashLights&DL_ABS != 0
|
||||
}
|
||||
|
||||
// DashLights - functions to check if a given dash light is provided [END]
|
||||
|
||||
// Flags - functions to check if a given flag is ON [START]
|
||||
|
||||
// HasTurbo reports whether there's a turbo
|
||||
func (sdk *BeamNGSDK) HasTurbo() bool {
|
||||
return sdk.Data.Flags&OG_TURBO != 0
|
||||
}
|
||||
|
||||
// PrefersKm reports whether the user prefers kilometers:
|
||||
// - true is prefers Km
|
||||
// - false is prefers Mi
|
||||
func (sdk *BeamNGSDK) PrefersKm() bool {
|
||||
return sdk.Data.Flags&OG_KM != 0
|
||||
}
|
||||
|
||||
// PrefersBAR reports whether the user prefers BAR:
|
||||
// - true is prefers BAR
|
||||
// - false is prefers PSI
|
||||
func (sdk *BeamNGSDK) PrefersBAR() bool {
|
||||
return sdk.Data.Flags&OG_BAR != 0
|
||||
}
|
||||
|
||||
// Flags - functions to check if a given flag is ON [END]
|
||||
|
||||
// Data Retrieval [START]
|
||||
|
||||
// ToMap creates a map with the data in the Outgauge struct
|
||||
func (sdk *BeamNGSDK) ToMap() map[string]any {
|
||||
return map[string]any{
|
||||
"Time": sdk.Data.Time, // time in milliseconds (to check order)
|
||||
"Car": sdk.Data.Car, // Car name
|
||||
"Flags": sdk.Data.Flags, // Info (see OG_x below)
|
||||
"Gear": sdk.Data.Gear, // Reverse:0, Neutral:1, First:2...
|
||||
"Plid": sdk.Data.Plid, // Unique ID of viewed player (0 = none)
|
||||
"Speed": sdk.Data.Speed, // M/S
|
||||
"RPM": sdk.Data.RPM, // RPM
|
||||
"Turbo": sdk.Data.Turbo, // BAR
|
||||
"EngTemp": sdk.Data.EngTemp, // C
|
||||
"Fuel": sdk.Data.Fuel, // 0 to 1
|
||||
"OilPressure": sdk.Data.OilPressure, // BAR
|
||||
"OilTemp": sdk.Data.OilTemp, // C
|
||||
"DashLights": sdk.Data.DashLights, // Dash lights available (see DL_x below)
|
||||
"ShowLights": sdk.Data.ShowLights, // Dash lights currently switched on
|
||||
"Throttle": sdk.Data.Throttle, // 0 to 1
|
||||
"Brake": sdk.Data.Brake, // 0 to 1
|
||||
"Clutch": sdk.Data.Clutch, // 0 to 1
|
||||
"Display1": sdk.Data.Display1, // Usually Fuel
|
||||
"Display2": sdk.Data.Display2, // Usually Settings
|
||||
"ID": sdk.Data.ID, // optional - only if OutGauge ID is specified
|
||||
}
|
||||
}
|
||||
|
||||
// Data Retrieval [END]
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package bngsdk
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkReadData(b *testing.B) {
|
||||
// Spin up an UDP server
|
||||
sdk, err := Init("127.0.0.1", 0)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to initialize SDK: %v", err)
|
||||
}
|
||||
defer sdk.Close()
|
||||
|
||||
// Retrieve the actual assigned UDP address
|
||||
localAddr := sdk.Conn.LocalAddr().(*net.UDPAddr)
|
||||
|
||||
// Start a client to stream data
|
||||
clientConn, err := net.DialUDP("udp", nil, localAddr)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to dial local UDP socket: %v", err)
|
||||
}
|
||||
defer clientConn.Close()
|
||||
|
||||
// Pre serialize some data
|
||||
dummyOutgauge := Outgauge{
|
||||
Time: 424242,
|
||||
Car: [4]byte{'P', 'E', 'R', 'F'},
|
||||
Speed: 45.2,
|
||||
RPM: 3500.0,
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
if err := binary.Write(&buf, binary.LittleEndian, dummyOutgauge); err != nil {
|
||||
b.Fatalf("Failed to serialize dummy struct: %v", err)
|
||||
}
|
||||
packetBytes := buf.Bytes()
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
// Feed a packet into the network buffer right before reading it
|
||||
_, err := clientConn.Write(packetBytes)
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to write to UDP socket: %v", err)
|
||||
}
|
||||
|
||||
// Execute the target function
|
||||
err = sdk.ReadData()
|
||||
if err != nil {
|
||||
b.Fatalf("ReadData failed at iteration %d: %v", i, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
-30
@@ -1,11 +1,37 @@
|
||||
package bngsdk
|
||||
|
||||
/* TODO
|
||||
- OG_x Flags
|
||||
- DL_x Flags
|
||||
*/
|
||||
import "encoding/binary"
|
||||
|
||||
// More documentation at https://go.beamng.com/protocols
|
||||
var outgaugeSize = binary.Size(Outgauge{})
|
||||
|
||||
// More documentation at https://go.beamng.com/protocols.
|
||||
// Or at BeamNG/lua/vehicle/protocols/outgauge.lua
|
||||
// OG_x buts for flags
|
||||
const (
|
||||
OG_SHIFT = 1 // key // N/A
|
||||
OG_CTRL = 2 // key // N/A
|
||||
OG_TURBO = 8192 // show turbo gauge
|
||||
OG_KM = 16384 // if not set - user prefers MILES
|
||||
OG_BAR = 32768 // if not set - user prefers PSI
|
||||
)
|
||||
|
||||
// DL_x Flags
|
||||
const (
|
||||
DL_SHIFT = 1 << 0 // shift light
|
||||
DL_FULLBEAM = 1 << 1 // full beam
|
||||
DL_HANDBRAKE = 1 << 2 // handbrake
|
||||
DL_PITSPEED = 1 << 3 // pit speed limiter // N/A
|
||||
DL_TC = 1 << 4 // tc active or switched off
|
||||
DL_SIGNAL_L = 1 << 5 // left turn signal
|
||||
DL_SIGNAL_R = 1 << 6 // right turn signal
|
||||
DL_SIGNAL_ANY = 1 << 7 // shared turn signal // N/A
|
||||
DL_OILWARN = 1 << 8 // oil pressure warning
|
||||
DL_BATTERY = 1 << 9 // battery warning
|
||||
DL_ABS = 1 << 10 // abs active or switched off
|
||||
DL_SPARE = 1 << 11 // N/A
|
||||
)
|
||||
|
||||
// Outgauge describes the data served by the BeamNG outgauge server
|
||||
type Outgauge struct {
|
||||
Time uint32 // time in milliseconds (to check order)
|
||||
Car [4]byte // Car name
|
||||
@@ -28,28 +54,3 @@ type Outgauge struct {
|
||||
Display2 [16]byte // Usually Settings
|
||||
ID int32 // optional - only if OutGauge ID is specified
|
||||
}
|
||||
|
||||
func (o *Outgauge) ToMap() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"Time": o.Time, // time in milliseconds (to check order)
|
||||
"Car": o.Car, // Car name
|
||||
"Flags": o.Flags, // Info (see OG_x below)
|
||||
"Gear": o.Gear, // Reverse:0, Neutral:1, First:2...
|
||||
"Plid": o.Plid, // Unique ID of viewed player (0 = none)
|
||||
"Speed": o.Speed, // M/S
|
||||
"RPM": o.RPM, // RPM
|
||||
"Turbo": o.Turbo, // BAR
|
||||
"EngTemp": o.EngTemp, // C
|
||||
"Fuel": o.Fuel, // 0 to 1
|
||||
"OilPressure": o.OilPressure, // BAR
|
||||
"OilTemp": o.OilTemp, // C
|
||||
"DashLights": o.DashLights, // Dash lights available (see DL_x below)
|
||||
"ShowLights": o.ShowLights, // Dash lights currently switched on
|
||||
"Throttle": o.Throttle, // 0 to 1
|
||||
"Brake": o.Brake, // 0 to 1
|
||||
"Clutch": o.Clutch, // 0 to 1
|
||||
"Display1": o.Display1, // Usually Fuel
|
||||
"Display2": o.Display2, // Usually Settings
|
||||
"ID": o.ID, // optional - only if OutGauge ID is specified
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user