More work associated with the new TUI - must recover old repl tho
This commit is contained in:
@@ -0,0 +1,181 @@
|
||||
package esdi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (e *ESDI) getVehicleData() {
|
||||
mu.Lock()
|
||||
curGear := e.irsdk.Vars.Vars["Gear"].Value
|
||||
curRPM := e.irsdk.Vars.Vars["RPM"].Value
|
||||
curSpeed := e.irsdk.Vars.Vars["Speed"].Value
|
||||
curBrakeBias, ok := e.irsdk.Vars.Vars["dcBrakeBias"]
|
||||
if ok {
|
||||
copyBytes(e.dataPacket.BrakeBias[:], BrakeBiasLen,
|
||||
fmt.Sprintf("%.1f", curBrakeBias.Value.(float32)))
|
||||
}
|
||||
|
||||
e.data.Gear = int32(curGear.(int))
|
||||
e.data.RPM = int32(curRPM.(float32))
|
||||
e.data.Speed = int32(msToKph(curSpeed.(float32)))
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
func (e *ESDI) fuelData() {
|
||||
mu.Lock()
|
||||
fLiters := e.irsdk.Vars.Vars["FuelLevel"].Value
|
||||
fPct := e.irsdk.Vars.Vars["FuelLevelPct"].Value
|
||||
|
||||
curLap := e.irsdk.Vars.Vars["Lap"].Value.(int)
|
||||
fuelLevels[curLap] = fLiters.(float32)
|
||||
|
||||
if curLap-2 < 0 {
|
||||
e.data.FuelPerLap = 0.0
|
||||
} else {
|
||||
e.data.FuelPerLap = fuelLevels[curLap-2] - fuelLevels[curLap-1]
|
||||
}
|
||||
|
||||
e.data.FuelPct = float32(fPct.(float32)) * 100
|
||||
e.data.FuelLiters = float32(fLiters.(float32))
|
||||
e.data.FuelTotal = (100 * e.data.FuelLiters) / e.data.FuelPct
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
func (e *ESDI) lapData() {
|
||||
mu.Lock()
|
||||
currentLap := e.irsdk.Vars.Vars["Lap"].Value
|
||||
lapDistPct := e.irsdk.Vars.Vars["LapDistPct"].Value
|
||||
currentLapTime := e.irsdk.Vars.Vars["LapCurrentLapTime"].Value
|
||||
lapBestLapTime := e.irsdk.Vars.Vars["LapBestLapTime"].Value
|
||||
lapLastLapTime := e.irsdk.Vars.Vars["LapLastLapTime"].Value
|
||||
lapDeltaToBestLap := e.irsdk.Vars.Vars["LapDeltaToBestLap"].Value
|
||||
|
||||
e.data.LapDeltaFloat = lapDeltaToBestLap.(float32)
|
||||
e.data.LapCount = int32(currentLap.(int))
|
||||
e.data.LapDistPct = float32(lapDistPct.(float32)) * 100
|
||||
|
||||
// TODO
|
||||
// Don't create the strings here, should be creating them later one only
|
||||
// Get the best lap data from the session info - I guess
|
||||
copy(e.data.CurrLapTime[:], string(lapTimeRepresentation(currentLapTime.(float32),
|
||||
LapTimeFormatStr)))
|
||||
copy(e.data.LastLapTime[:], string(lapTimeRepresentation(lapLastLapTime.(float32),
|
||||
LapTimeFormatStr)))
|
||||
copy(e.data.BestLapTime[:], string(lapTimeRepresentation(lapBestLapTime.(float32),
|
||||
LapTimeFormatStr)))
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
// This function doesnt feel very pretty - NEED TO REFACTOR IT
|
||||
func (e *ESDI) positionData() {
|
||||
// Only create a table with more people if we have more players
|
||||
mu.Lock()
|
||||
standings := createStandingsTable(e.irsdk)
|
||||
if len(standings) <= 0 {
|
||||
for range 5 {
|
||||
standings = append(standings, paddingStandingsLine)
|
||||
}
|
||||
} else {
|
||||
relativeStandings(e.irsdk, standings, e.irsdk.SessionInfo.DriverInfo.DriverCarIdx)
|
||||
p := findEntry(standings, func(l StandingsLine) bool {
|
||||
return l.CarIdx == int32(e.irsdk.SessionInfo.DriverInfo.DriverCarIdx)
|
||||
})
|
||||
|
||||
lowerLim := p - 2
|
||||
upperLim := p + 3
|
||||
|
||||
var lowerPadding []StandingsLine
|
||||
var upperPadding []StandingsLine
|
||||
|
||||
if lowerLim < 0 {
|
||||
lowerPadding = make([]StandingsLine, abs(lowerLim))
|
||||
for k := range abs(lowerLim) {
|
||||
lowerPadding[k] = paddingStandingsLine
|
||||
}
|
||||
lowerLim = 0
|
||||
}
|
||||
if upperLim >= len(standings) {
|
||||
upperPadding = make([]StandingsLine, upperLim-len(standings))
|
||||
for k := range upperLim - len(standings) {
|
||||
upperPadding[k] = paddingStandingsLine
|
||||
}
|
||||
upperLim = len(standings)
|
||||
}
|
||||
|
||||
standings = append(lowerPadding, standings[lowerLim:upperLim]...)
|
||||
standings = append(standings, upperPadding...)
|
||||
|
||||
e.data.Position = int32(p)
|
||||
}
|
||||
|
||||
copy(e.data.Standings[:], standings[0:5])
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
func packageStandingsLineDataPacket(data [5]StandingsLine) [5]StandingsLineDataPacket {
|
||||
var sl [5]StandingsLineDataPacket
|
||||
|
||||
for k := range data {
|
||||
copyBytes(sl[k].Lap[:], LapStringLen, fmt.Sprintf("%-2d", data[k].Lap))
|
||||
copyBytes(sl[k].DriverName[:], DriverNameLen,
|
||||
fmt.Sprintf("%-16s", data[k].DriverName[0:DriverNameLen]))
|
||||
copyBytes(sl[k].TimeBehindString[:], TimeBehindStringLen,
|
||||
fmt.Sprintf("%-16s", data[k].TimeBehindString[0:TimeBehindStringLen]))
|
||||
}
|
||||
|
||||
return sl
|
||||
}
|
||||
|
||||
func readData(e *ESDI, done <-chan string) {
|
||||
// dataReaderTicker := time.NewTicker(time.Second / 60)
|
||||
dataReaderTicker := time.NewTicker(time.Second / 240)
|
||||
defer dataReaderTicker.Stop()
|
||||
|
||||
initialTime = time.Now()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-dataReaderTicker.C:
|
||||
var err error
|
||||
|
||||
mu.Lock()
|
||||
_, err = e.irsdk.Update(time.Millisecond * 100)
|
||||
if err != nil {
|
||||
fmt.Printf("could not update data: %v", err)
|
||||
continue
|
||||
}
|
||||
mu.Unlock()
|
||||
|
||||
e.getVehicleData()
|
||||
e.fuelData()
|
||||
e.lapData()
|
||||
e.positionData()
|
||||
|
||||
// Test the actual dataPacket we are sending over the wire
|
||||
copyBytes(e.dataPacket.Speed[:], SpeedLen, fmt.Sprintf("%3d", e.data.Speed))
|
||||
copyBytes(e.dataPacket.Gear[:], GearLen, fmt.Sprintf("%2d", e.data.Gear))
|
||||
copyBytes(e.dataPacket.RPM[:], RpmLen, fmt.Sprintf("%3d", e.data.RPM))
|
||||
|
||||
e.dataPacket.Standings = packageStandingsLineDataPacket(e.data.Standings)
|
||||
|
||||
copyBytes(e.dataPacket.LapNumber[:], LapNumberLen, fmt.Sprintf("%-3d", e.data.LapCount))
|
||||
copyBytes(e.dataPacket.DeltaToBestLap[:], DeltaToBestLapLen,
|
||||
fmt.Sprintf("%s", lapTimeDeltaRepresentation(e.data.LapDeltaFloat)))
|
||||
copyBytes(e.dataPacket.BestLapTime[:], BestLapTimeLen,
|
||||
fmt.Sprintf("%s", e.data.BestLapTime[:8]))
|
||||
copyBytes(e.dataPacket.CurrLapTime[:], CurrLapTimeLen,
|
||||
fmt.Sprintf("%s", e.data.CurrLapTime[:8]))
|
||||
copyBytes(e.dataPacket.LastLapTime[:], LastLapTimeLen,
|
||||
fmt.Sprintf("%s", e.data.LastLapTime[:8]))
|
||||
copyBytes(e.dataPacket.FuelEst[:], FuelEstLen,
|
||||
fmt.Sprintf("%.1f - %.1f", e.data.FuelPerLap, e.data.FuelLiters/e.data.FuelPerLap))
|
||||
|
||||
e.dataPacket.StartMarker = 0x02
|
||||
e.dataPacket.EndMarker = 0x03
|
||||
|
||||
lastMessageTime = time.Now()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
// Package esdi will have the domain logic for our desktop interface
|
||||
package esdi
|
||||
|
||||
import (
|
||||
"esdi/sources/iracing"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ESilva15/goirsdk"
|
||||
"github.com/tarm/serial"
|
||||
)
|
||||
|
||||
type ESDI struct {
|
||||
SerialConfig *serial.Config
|
||||
SerialConn *serial.Port
|
||||
irsdk *goirsdk.IBT
|
||||
data SimulationData
|
||||
dataPacket DataPacket
|
||||
// Source GameSource
|
||||
}
|
||||
|
||||
func (e *ESDI) Close() {
|
||||
err := e.SerialConn.Close()
|
||||
if err != nil {
|
||||
log.Fatalf("couldn't close serial connection: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func ESDIInit(port string, baud int) (ESDI, error) {
|
||||
sConfig := &serial.Config{
|
||||
Name: port,
|
||||
Baud: baud,
|
||||
ReadTimeout: time.Millisecond * 1000,
|
||||
}
|
||||
|
||||
// Open the serial port
|
||||
sPort, err := serial.OpenPort(sConfig)
|
||||
if err != nil {
|
||||
return ESDI{}, err
|
||||
}
|
||||
|
||||
// return ESDI{sConfig, sPort, nil}, nil
|
||||
return ESDI{sConfig, sPort, nil, SimulationData{}, DataPacket{}}, nil
|
||||
// return ESDI{nil, nil, nil, SimulationData{}, DataPacket{}}, nil
|
||||
}
|
||||
|
||||
func RunLiveTelemetry(port string, output string, session string) {
|
||||
esdi, err := ESDIInit(port, 115200)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get Desktop Interface: %v", err)
|
||||
}
|
||||
|
||||
// irsdk, err := iracing.Init(nil, outputFile, sessionFile)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Failed to create iRacing interface: %v", err)
|
||||
// }
|
||||
|
||||
irsdk, err := goirsdk.Init(nil, output, session)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create irsdk instance: %v\n", err)
|
||||
}
|
||||
|
||||
esdi.irsdk = irsdk
|
||||
|
||||
esdi.telemetry()
|
||||
}
|
||||
|
||||
func RunOfflineTelemetry(port string, input string, output string, session string) {
|
||||
esdi, err := ESDIInit(port, 115200)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to get Desktop Interface: %v", err)
|
||||
}
|
||||
|
||||
file, err := os.Open(input)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open IBT file: %v", err)
|
||||
}
|
||||
|
||||
irsdk, err := iracing.Init(file, output, session)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to create iRacing interface: %v", err)
|
||||
}
|
||||
// irsdk, err := goirsdk.Init(file, outFile, sessionFile)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Failed to create irsdk instance: %v\n", err)
|
||||
// }
|
||||
|
||||
esdi.irsdk = irsdk.SDK
|
||||
|
||||
esdi.telemetry()
|
||||
}
|
||||
@@ -0,0 +1,266 @@
|
||||
package esdi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"io"
|
||||
"log"
|
||||
|
||||
// "encoding/binary"
|
||||
"fmt"
|
||||
// "log"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
// "github.com/tarm/serial"
|
||||
)
|
||||
|
||||
var (
|
||||
initialTime = time.Now()
|
||||
lastTime = time.Now()
|
||||
paddingStandingsLine = StandingsLine{
|
||||
DriverName: createDriverName("---"),
|
||||
Lap: 0,
|
||||
CarIdx: 0,
|
||||
LapPct: 0,
|
||||
EstTime: 0,
|
||||
TimeBehind: 0,
|
||||
}
|
||||
iniFuelLvl float32
|
||||
fuelLevels map[int]float32
|
||||
lastMessageTime time.Time
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
const (
|
||||
LapTimeFormatStr = "04:05.000"
|
||||
RelativeDeltaFormatStr = "04:05.0"
|
||||
)
|
||||
|
||||
func msToKph(v float32) int {
|
||||
return int((3600 * v) / 1000)
|
||||
}
|
||||
|
||||
func lapTimeRepresentation(t float32, f string) string {
|
||||
if t < 0 {
|
||||
t = 0
|
||||
}
|
||||
|
||||
wholeSeconds := int64(t)
|
||||
lapTime := time.Unix(wholeSeconds, int64((t-float32(wholeSeconds))*1e9))
|
||||
|
||||
return lapTime.Format(f)
|
||||
}
|
||||
|
||||
func lapTimeDeltaRepresentation(t float32) string {
|
||||
sign := '-'
|
||||
if t < 0 {
|
||||
sign = '+'
|
||||
t = -t
|
||||
}
|
||||
|
||||
// Cap to 99.9 max
|
||||
if t > 99.9 {
|
||||
t = 99.9
|
||||
}
|
||||
|
||||
if t >= 1 {
|
||||
// Round to nearest tenth
|
||||
rounded := float32(int(t*10+0.5)) / 10
|
||||
return fmt.Sprintf("%c%.1f", sign, rounded)
|
||||
}
|
||||
|
||||
// t < 1: round to nearest tenth and remove leading zero (e.g., "0.1" → ".1")
|
||||
rounded := float32(int(t*10+0.5)) / 10
|
||||
s := fmt.Sprintf("%.1f", rounded)
|
||||
if strings.HasPrefix(s, "0") {
|
||||
s = s[1:]
|
||||
}
|
||||
return fmt.Sprintf("%c%s", sign, s)
|
||||
}
|
||||
|
||||
func resetTerminal() {
|
||||
// Show cursor and clear screen
|
||||
fmt.Print("\033[?25h\033[2J\033[H")
|
||||
}
|
||||
|
||||
func (e *ESDI) setupSignalHandlers() chan string {
|
||||
sigc := make(chan os.Signal, 1)
|
||||
signal.Notify(sigc,
|
||||
syscall.SIGHUP,
|
||||
syscall.SIGINT,
|
||||
syscall.SIGTERM,
|
||||
syscall.SIGQUIT,
|
||||
)
|
||||
done := make(chan string)
|
||||
|
||||
go func() {
|
||||
s := <-sigc
|
||||
switch s {
|
||||
case syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGHUP:
|
||||
resetTerminal()
|
||||
fmt.Print("Closing ESDI")
|
||||
}
|
||||
close(done)
|
||||
e.Close()
|
||||
}()
|
||||
|
||||
return done
|
||||
}
|
||||
|
||||
func printData(e *ESDI, done <-chan string) {
|
||||
ticker := time.NewTicker(time.Millisecond * 25)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
case <-ticker.C:
|
||||
var buffer strings.Builder
|
||||
buffer.WriteString("\033[?25l\033[2J\033[H")
|
||||
|
||||
mu.Lock()
|
||||
sessionTimeR := e.irsdk.Vars.Vars["SessionTime"].Value
|
||||
sessionTime := float64(sessionTimeR.(float64))
|
||||
|
||||
currTime := time.Now()
|
||||
delta := currTime.Sub(lastTime)
|
||||
buffer.WriteString(fmt.Sprintf("[%s]\n", currTime.Format("2006/01/02 15:04:05.000")))
|
||||
buffer.WriteString(fmt.Sprintf("Delta: %d [%f]\n\n", delta.Milliseconds(), 1000.0/60.0))
|
||||
lastTime = currTime
|
||||
|
||||
elapsed := currTime.Sub(initialTime)
|
||||
softwareElapsed := time.Unix(0, 0).Add(elapsed).Format("04:05.000")
|
||||
sessionElapsed := time.Unix(0, 0).
|
||||
Add(time.Duration(sessionTime * float64(time.Second))).
|
||||
Format("04:05.000")
|
||||
|
||||
buffer.WriteString(fmt.Sprintf("Elapsed (software): %s\n",
|
||||
softwareElapsed))
|
||||
buffer.WriteString(fmt.Sprintf("Elapsed (session): %s\n\n",
|
||||
sessionElapsed))
|
||||
|
||||
// buffer.WriteString("Car data:\n")
|
||||
buffer.WriteString(fmt.Sprintf("Gear: %d, RPM: %d, Speed: %d\n\n",
|
||||
e.dataPacket.Gear, e.dataPacket.RPM, e.dataPacket.Speed))
|
||||
buffer.WriteString(fmt.Sprintf("BB: %s\n\n", e.dataPacket.BrakeBias))
|
||||
|
||||
buffer.WriteString("Fuel data:\n")
|
||||
buffer.WriteString(fmt.Sprintf("Fuel Est: %s\n\n", e.dataPacket.FuelEst))
|
||||
|
||||
buffer.WriteString("Lap data:\n")
|
||||
buffer.WriteString(fmt.Sprintf("Delta: [%s] [%f] [%s]\n", e.dataPacket.DeltaToBestLap,
|
||||
e.data.LapDeltaFloat, lapTimeDeltaRepresentation(e.data.LapDeltaFloat)))
|
||||
buffer.WriteString(fmt.Sprintf("LapTime: %s\n", e.dataPacket.CurrLapTime))
|
||||
buffer.WriteString(fmt.Sprintf("Best Lap Time: %s\n", e.dataPacket.BestLapTime))
|
||||
buffer.WriteString(fmt.Sprintf("Last Lap Time: %s\n", e.dataPacket.LastLapTime))
|
||||
// buffer.WriteString(fmt.Sprintf("LapBestNLapTi: %f\n\n", e.data.LapBestNLapTime))
|
||||
|
||||
// buffer.WriteString("Position data:\n")
|
||||
// buffer.WriteString(fmt.Sprintf("Pos: %d\n", e.dataPacket.Position))
|
||||
|
||||
for p, v := range e.dataPacket.Standings {
|
||||
s := fmt.Sprintf("[%2d] %s %-16s %-16s\n",
|
||||
p+1, v.Lap, string(bytes.Trim(v.DriverName[:], "\x00")), v.TimeBehindString)
|
||||
buffer.WriteString(s)
|
||||
}
|
||||
|
||||
buffer.WriteString(fmt.Sprintf("Size: %v\n", binary.Size(DataPacket{})))
|
||||
buffer.WriteString(fmt.Sprintf("Recv: %d\n", e.data.Recv))
|
||||
buffer.WriteString(fmt.Sprintf("Recv Err: %v\n", e.data.ReadError))
|
||||
|
||||
mu.Unlock()
|
||||
|
||||
// buffer.WriteString("\n" + message)
|
||||
fmt.Print(buffer.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type DataReq struct {
|
||||
Req int8
|
||||
}
|
||||
|
||||
func (e *ESDI) telemetry() {
|
||||
// Set the handlers
|
||||
done := e.setupSignalHandlers()
|
||||
dataError := make(chan string)
|
||||
|
||||
// Display the data on the terminal periodically
|
||||
go printData(e, done)
|
||||
|
||||
// Maybe create a struct made to calculate the fuel levels
|
||||
// -> struct FuelLvlCalculator
|
||||
fuelLevels = make(map[int]float32, 256)
|
||||
|
||||
// We need to add another goroutine here that continuously updates
|
||||
// the data
|
||||
go readData(e, done)
|
||||
|
||||
// Add another goroutine that is actually waiting for data requests
|
||||
// And gives the signal or sends the data
|
||||
// go waitForReq(e, done)
|
||||
|
||||
// TODO:
|
||||
// Add start and end markers to the data frames
|
||||
|
||||
// This loop will wait for the display to request the data
|
||||
total := time.Microsecond * 0
|
||||
previousRequest := time.Now()
|
||||
nRequests := 0
|
||||
for {
|
||||
isRunning := true
|
||||
select {
|
||||
case s := <-done:
|
||||
resetTerminal()
|
||||
fmt.Println(s)
|
||||
isRunning = false
|
||||
break
|
||||
case s := <-dataError:
|
||||
done <- s
|
||||
default:
|
||||
// Wait for the display to request some data
|
||||
var r DataReq
|
||||
err := binary.Read(e.SerialConn, binary.LittleEndian, &r)
|
||||
e.data.ReadError = err
|
||||
if err != nil && err != io.EOF {
|
||||
log.Println(err)
|
||||
fmt.Println("->", r)
|
||||
}
|
||||
e.data.Recv = r.Req
|
||||
|
||||
if r.Req == 5 {
|
||||
e.SerialConn.Flush()
|
||||
currTime := time.Now()
|
||||
if nRequests != 0 {
|
||||
total += currTime.Sub(previousRequest)
|
||||
}
|
||||
previousRequest = currTime
|
||||
nRequests += 1
|
||||
|
||||
var buf bytes.Buffer
|
||||
err = binary.Write(&buf, binary.LittleEndian, e.dataPacket)
|
||||
|
||||
_, err = e.SerialConn.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Printf("Unable to write data: %v", err)
|
||||
break
|
||||
}
|
||||
e.SerialConn.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
if !isRunning {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
log.Println("Statistics:")
|
||||
log.Printf(" Reqs: %d\n", nRequests)
|
||||
log.Printf(" Total time: %d (ms)\n", total.Milliseconds())
|
||||
log.Printf(" Average req time: %d (ms / request)\n", total.Milliseconds()/int64(nRequests))
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package esdi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestLapTimeDeltaRepresentationPositiveLT1(t *testing.T) {
|
||||
// Arrange
|
||||
var time float32 = 0.123
|
||||
expect := "-.1"
|
||||
|
||||
// Act
|
||||
res := lapTimeDeltaRepresentation(time)
|
||||
|
||||
// Test
|
||||
if res != expect {
|
||||
t.Fatalf("Expected `%s`, got `%s`", expect, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLapTimeDeltaRepresentationPositiveGE1(t *testing.T) {
|
||||
// Arrange
|
||||
var time float32 = 1.123
|
||||
expect := "-1.1"
|
||||
|
||||
// Act
|
||||
res := lapTimeDeltaRepresentation(time)
|
||||
|
||||
// Test
|
||||
if res != expect {
|
||||
t.Fatalf("Expected `%s`, got `%s`", expect, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLapTimeDeltaRepresentationNegativeLT1(t *testing.T) {
|
||||
// Arrange
|
||||
var time float32 = -0.123
|
||||
expect := "+.1"
|
||||
|
||||
// Act
|
||||
res := lapTimeDeltaRepresentation(time)
|
||||
|
||||
// Test
|
||||
if res != expect {
|
||||
t.Fatalf("Expected `%s`, got `%s`", expect, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLapTimeDeltaRepresentationNegativeGE1(t *testing.T) {
|
||||
// Arrange
|
||||
var time float32 = -1.123
|
||||
expect := "+1.1"
|
||||
|
||||
// Act
|
||||
res := lapTimeDeltaRepresentation(time)
|
||||
|
||||
// Test
|
||||
if res != expect {
|
||||
t.Fatalf("Expected `%s`, got `%s`", expect, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLapTimeDeltaRepresentationGreaterThan100(t *testing.T) {
|
||||
// Arrange
|
||||
var time float32 = -102.123
|
||||
expect := "+99.9"
|
||||
|
||||
// Act
|
||||
res := lapTimeDeltaRepresentation(time)
|
||||
|
||||
// Test
|
||||
if res != expect {
|
||||
t.Fatalf("Expected `%s`, got `%s`", expect, res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLapTimeDeltaRepresentationGreaterNew(t *testing.T) {
|
||||
// Arrange
|
||||
var time float32 = -0.012
|
||||
expect := "+.0"
|
||||
|
||||
// Act
|
||||
res := lapTimeDeltaRepresentation(time)
|
||||
|
||||
// Test
|
||||
if res != expect {
|
||||
t.Fatalf("Expected `%s`, got `%s`", expect, res)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package esdi
|
||||
|
||||
// "esdi/sources/beamng"
|
||||
// "fmt"
|
||||
// "log"
|
||||
// "strings"
|
||||
// "time"
|
||||
|
||||
func beamngExample(esdi ESDI) {
|
||||
// bIF, err := beamng.Init("127.0.0.1", 4444)
|
||||
// if err != nil {
|
||||
// log.Fatalf("Failed to create BeamNG interface: %v", err)
|
||||
// }
|
||||
//
|
||||
// esdi.Source = &bIF
|
||||
//
|
||||
// lastTime := time.Now().UnixMilli()
|
||||
// lastDataSent := time.Now().UnixMilli()
|
||||
// for {
|
||||
// var err error
|
||||
// var buffer strings.Builder
|
||||
// buffer.WriteString("\033[?25l\033[2J\033[H")
|
||||
//
|
||||
// err = esdi.Source.UpdateData()
|
||||
// if err != nil {
|
||||
// fmt.Printf("could not update data: %v", err)
|
||||
// continue
|
||||
// }
|
||||
//
|
||||
// curGear, err := esdi.Source.GetData("Gear")
|
||||
// if err != nil {
|
||||
// log.Fatalf("could not get field `Gear`: %v", err)
|
||||
// }
|
||||
//
|
||||
// curRPM, err := esdi.Source.GetData("RPM")
|
||||
// if err != nil {
|
||||
// log.Fatalf("could not get field `RPM`: %v", err)
|
||||
// }
|
||||
//
|
||||
// gear := int(curGear.(int8))
|
||||
// rpm := int(curRPM.(float32))
|
||||
//
|
||||
// buffer.WriteString(fmt.Sprintf("Gear: %d, RPM: %d", gear, rpm))
|
||||
//
|
||||
// curTime := time.Now().UnixMilli()
|
||||
// message := fmt.Sprintf("%d,%d\n", gear-1, rpm)
|
||||
// buffer.WriteString("\n" + message)
|
||||
//
|
||||
// messageWasSentMark := "N"
|
||||
// if curTime-lastDataSent > 75 {
|
||||
// _, err = esdi.SerialConn.Write([]byte(message))
|
||||
// if err != nil {
|
||||
// log.Printf("Unable to write data: %v", err)
|
||||
// break
|
||||
// }
|
||||
//
|
||||
// messageWasSentMark = "Y"
|
||||
// lastDataSent = curTime
|
||||
// }
|
||||
//
|
||||
// buffer.WriteString(" -> " + messageWasSentMark)
|
||||
// if curTime-lastTime > 100 {
|
||||
// fmt.Print(buffer.String())
|
||||
// lastTime = curTime
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
package esdi
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/ESilva15/goirsdk"
|
||||
)
|
||||
|
||||
func createDriverName(s string) [DriverNameLen]byte {
|
||||
var arr [DriverNameLen]byte
|
||||
|
||||
if len(s) > 32 {
|
||||
s = s[:32]
|
||||
}
|
||||
|
||||
copy(arr[:], s)
|
||||
return arr
|
||||
}
|
||||
|
||||
// type standingsFilter func([]StandingsLine, []float32, int)
|
||||
|
||||
func generalStandings(i *goirsdk.IBT, s []StandingsLine, id int) {
|
||||
// We have to sort the racists by their lap count and position on the track
|
||||
// on the current lap
|
||||
sort.Slice(s, func(i int, j int) bool {
|
||||
if s[i].Lap == int32(s[j].Lap) {
|
||||
return s[i].LapPct > s[j].LapPct
|
||||
}
|
||||
return s[i].Lap > s[j].Lap
|
||||
})
|
||||
|
||||
estTime := i.Vars.Vars["CarIdxEstTime"].Value.([]float32)
|
||||
|
||||
// This will give the delta to the guy ahead
|
||||
// P1 0:00:000
|
||||
// P2 0:01:000 -> 1s from P1
|
||||
// P3 0:01:000 -> 1s from P2
|
||||
for p := range s {
|
||||
theirEstimate := estTime[s[p].CarIdx]
|
||||
var theThing float32 = 0.0
|
||||
if p != id {
|
||||
theThing = estTime[s[p-1].CarIdx] - theirEstimate
|
||||
}
|
||||
|
||||
s[p].TimeBehind = theThing
|
||||
copy(s[p].TimeBehindString[:], string(lapTimeRepresentation(theThing,
|
||||
RelativeDeltaFormatStr)))
|
||||
}
|
||||
}
|
||||
|
||||
func relativeStandings(i *goirsdk.IBT, s []StandingsLine, id int) {
|
||||
sort.Slice(s, func(i int, j int) bool {
|
||||
if s[i].Lap == int32(s[j].Lap) {
|
||||
return s[i].LapPct > s[j].LapPct
|
||||
}
|
||||
return s[i].Lap > s[j].Lap
|
||||
})
|
||||
|
||||
estTime := i.Vars.Vars["CarIdxEstTime"].Value.([]float32)
|
||||
|
||||
// Get the delta to a given carId
|
||||
// TODO: fix the delta when the car behind is still in the previous lap.
|
||||
for p := range s {
|
||||
curCarEstimate := estTime[s[p].CarIdx]
|
||||
|
||||
var delta float32 = 0.0
|
||||
delta = abs(estTime[id] - curCarEstimate)
|
||||
|
||||
s[p].TimeBehind = delta
|
||||
copy(s[p].TimeBehindString[:], string(lapTimeRepresentation(delta,
|
||||
RelativeDeltaFormatStr)))
|
||||
}
|
||||
}
|
||||
|
||||
// createStandingsTable will create a table with the standings data
|
||||
// still working on this
|
||||
// If the filter applied is generalStandings, the carId has to be 0
|
||||
// We can do some dynamicProgramming on this thing I guess, I still haven't
|
||||
// though about it much yet tbh
|
||||
func createStandingsTable(i *goirsdk.IBT) []StandingsLine {
|
||||
driversLapDistPctRaw := i.Vars.Vars["CarIdxLapDistPct"].Value
|
||||
if driversLapDistPctRaw == nil {
|
||||
return []StandingsLine{}
|
||||
}
|
||||
|
||||
driversLapDistPct := driversLapDistPctRaw.([]float32)
|
||||
driversEstTime := i.Vars.Vars["CarIdxEstTime"].Value.([]float32)
|
||||
driversLap := i.Vars.Vars["CarIdxLap"].Value.([]int32)
|
||||
drivers := i.SessionInfo.DriverInfo.Drivers
|
||||
|
||||
standings := make([]StandingsLine, len(drivers))
|
||||
|
||||
for k := range len(drivers) {
|
||||
if drivers[k].CarIsPaceCar == 1 || drivers[k].IsSpectator == 1 || drivers[k].UserName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if driversLap[k] == -1 || drivers[k].UserName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
standings[k] = StandingsLine{
|
||||
CarIdx: int32(k),
|
||||
LapPct: driversLapDistPct[k],
|
||||
DriverName: createDriverName(drivers[k].UserName),
|
||||
EstTime: driversEstTime[k],
|
||||
Lap: driversLap[k],
|
||||
TimeBehind: 0,
|
||||
}
|
||||
}
|
||||
|
||||
return standings
|
||||
}
|
||||
|
||||
func abs[V int32 | float32 | int](value V) V {
|
||||
if value < 0 {
|
||||
value = value * -1
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package esdi
|
||||
|
||||
func findEntry[T any](s []T, predicate func(T) bool) int {
|
||||
for i, elem := range s {
|
||||
if predicate(elem) {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func copyBytes(dest []byte, destSize int, src string) {
|
||||
copy(dest[:], []byte(src))
|
||||
dest[min(destSize-1, len(src))] = '\x00'
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package esdi
|
||||
|
||||
// Car data lengths
|
||||
const (
|
||||
SpeedLen = 5
|
||||
GearLen = 3
|
||||
RpmLen = 6
|
||||
BrakeBiasLen = 6
|
||||
)
|
||||
|
||||
// DataPacket Lens
|
||||
const (
|
||||
LapNumberLen = 5
|
||||
DeltaToBestLapLen = 6
|
||||
BestLapTimeLen = 10
|
||||
CurrLapTimeLen = 10
|
||||
LastLapTimeLen = 10
|
||||
FuelTankLen = 15 // 101.6 / 123.4L
|
||||
FuelEstLen = 15
|
||||
)
|
||||
|
||||
// StandingsLineDataPacket Lens
|
||||
const (
|
||||
LapStringLen = 4
|
||||
DriverNameLen = 24
|
||||
TimeBehindStringLen = 8
|
||||
)
|
||||
|
||||
type GameSource interface {
|
||||
GetData(string) (any, error)
|
||||
UpdateData() error
|
||||
GetSessionInfo() (any, error)
|
||||
}
|
||||
|
||||
type StandingsLine struct {
|
||||
CarIdx int32
|
||||
LapPct float32
|
||||
Lap int32
|
||||
DriverName [DriverNameLen]byte
|
||||
EstTime float32
|
||||
TimeBehind float32
|
||||
TimeBehindString [TimeBehindStringLen]byte
|
||||
}
|
||||
|
||||
type SimulationData struct {
|
||||
ReadError error
|
||||
Recv int8
|
||||
Speed int32
|
||||
Gear int32
|
||||
RPM int32
|
||||
LapCount int32
|
||||
LapDistPct float32
|
||||
CurrLapTime [16]byte // Current lap time
|
||||
LapDeltaFloat float32
|
||||
BestLapTime [16]byte // Best lap in session
|
||||
LastLapTime [16]byte // Last lap time
|
||||
FuelUsageCurLap float32
|
||||
FuelPerLap float32
|
||||
FuelPct float32
|
||||
FuelLiters float32
|
||||
FuelTotal float32 // This will be calculated and passed in Liters
|
||||
Position int32
|
||||
LapBestNLapTime float32
|
||||
Standings [5]StandingsLine
|
||||
}
|
||||
|
||||
type StandingsLineDataPacket struct {
|
||||
Lap [LapStringLen]byte `binary:"little"`
|
||||
DriverName [DriverNameLen]byte `binary:"little"`
|
||||
TimeBehindString [TimeBehindStringLen]byte `binary:"little"`
|
||||
}
|
||||
|
||||
type DataPacket struct {
|
||||
StartMarker uint8 `binary:"little"`
|
||||
Speed [SpeedLen]byte `binary:"little"`
|
||||
Gear [GearLen]byte `binary:"little"`
|
||||
RPM [RpmLen]byte `binary:"little"`
|
||||
BrakeBias [BrakeBiasLen]byte `binary:"little"`
|
||||
LapNumber [LapNumberLen]byte `binary:"little"`
|
||||
DeltaToBestLap [DeltaToBestLapLen]byte `binary:"little"`
|
||||
BestLapTime [BestLapTimeLen]byte `binary:"little"`
|
||||
CurrLapTime [CurrLapTimeLen]byte `binary:"little"`
|
||||
LastLapTime [LastLapTimeLen]byte `binary:"little"`
|
||||
FuelEst [FuelEstLen]byte `binary:"little"`
|
||||
Standings [5]StandingsLineDataPacket `binary:"little"`
|
||||
EndMarker uint8 `binary:"little"`
|
||||
}
|
||||
Reference in New Issue
Block a user