296 lines
6.8 KiB
Go
296 lines
6.8 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/signal"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/tarm/serial"
|
|
)
|
|
|
|
type GameSource interface {
|
|
GetData(string) (interface{}, error)
|
|
UpdateData() error
|
|
GetSessionInfo() (interface{}, error)
|
|
}
|
|
|
|
type DataPacket struct {
|
|
Speed int32
|
|
Gear int32
|
|
RPM int32
|
|
LapCount int32
|
|
LapDistPct float32
|
|
LapTime string // Current lap time
|
|
LapDelta string // Delta to selected reference lap
|
|
BestLapTime string // Best lap in session
|
|
LastLapTime string // Last lap time
|
|
FuelPct float32
|
|
FuelLiters float32
|
|
FuelTotal float32 // This will be calculated and passed in Liters
|
|
Position int32
|
|
}
|
|
|
|
var (
|
|
data DataPacket
|
|
lastMessageTime time.Time
|
|
mu sync.Mutex
|
|
)
|
|
|
|
func msToKph(v float32) int {
|
|
return int((3600 * v) / 1000)
|
|
}
|
|
|
|
func lapTimeRepresentation(t float32) string {
|
|
if t < 0 {
|
|
t = 0
|
|
}
|
|
|
|
wholeSeconds := int64(t)
|
|
lapTime := time.Unix(wholeSeconds, int64((t-float32(wholeSeconds))*1e9))
|
|
|
|
return lapTime.Format("04:05.000")
|
|
}
|
|
|
|
func lapTimeDeltaRepresentation(t float32) string {
|
|
sign := '-'
|
|
if t < 0 {
|
|
sign = '+'
|
|
t = -1 * t
|
|
}
|
|
|
|
if t >= 1 {
|
|
wholeSeconds := int64(t)
|
|
lapTime := time.Unix(wholeSeconds, int64((t-float32(wholeSeconds))*1e9))
|
|
return fmt.Sprintf("%c%s", sign, lapTime.Format("5.00"))
|
|
}
|
|
|
|
return fmt.Sprintf("%c.%02d", sign, int64(t*100))
|
|
}
|
|
|
|
func resetTerminal() {
|
|
// Show cursor and clear screen
|
|
fmt.Print("\033[?25h\033[2J\033[H")
|
|
}
|
|
|
|
func (e *ESDI) setupSignalHandlers() chan struct{} {
|
|
sigc := make(chan os.Signal, 1)
|
|
signal.Notify(sigc,
|
|
syscall.SIGHUP,
|
|
syscall.SIGINT,
|
|
syscall.SIGTERM,
|
|
syscall.SIGQUIT,
|
|
)
|
|
done := make(chan struct{})
|
|
|
|
go func() {
|
|
s := <-sigc
|
|
switch s {
|
|
case syscall.SIGTERM, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGHUP:
|
|
resetTerminal()
|
|
}
|
|
close(done)
|
|
e.Close()
|
|
}()
|
|
|
|
return done
|
|
}
|
|
|
|
func sendData(s *serial.Port, done <-chan struct{}) {
|
|
ticker := time.NewTicker(time.Millisecond * 25)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-done:
|
|
return
|
|
case <-ticker.C:
|
|
mu.Lock()
|
|
|
|
var buf bytes.Buffer
|
|
err := binary.Write(&buf, binary.LittleEndian, data)
|
|
|
|
_, err = s.Write(buf.Bytes())
|
|
if err != nil {
|
|
log.Printf("Unable to write data: %v", err)
|
|
break
|
|
}
|
|
|
|
lastMessageTime = time.Now()
|
|
mu.Unlock()
|
|
}
|
|
}
|
|
}
|
|
|
|
func printData(done <-chan struct{}) {
|
|
ticker := time.NewTicker(time.Millisecond * 100)
|
|
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()
|
|
// message := fmt.Sprintf("%d,%d\n", data.Gear-1, data.RPM)
|
|
buffer.WriteString(fmt.Sprintf("Gear: %d, RPM: %d, Speed: %d\n",
|
|
data.Gear, data.RPM, data.Speed))
|
|
buffer.WriteString(fmt.Sprintf("Fuel: %.2fL/%.2fL [%.2f%%]\n", data.FuelLiters,
|
|
data.FuelTotal, data.FuelPct))
|
|
buffer.WriteString(fmt.Sprintf("LapTime: %s [%s]\n", data.LapTime,
|
|
data.LapDelta))
|
|
buffer.WriteString(fmt.Sprintf("Best Lap Time: %s\n", data.BestLapTime))
|
|
buffer.WriteString(fmt.Sprintf("Last Lap Time: %s\n", data.LastLapTime))
|
|
buffer.WriteString(fmt.Sprintf("Lap: %d [%.2f%%]\n", data.LapCount,
|
|
data.LapDistPct))
|
|
buffer.WriteString(fmt.Sprintf("Pos: %d\n", data.Position))
|
|
mu.Unlock()
|
|
|
|
// buffer.WriteString("\n" + message)
|
|
fmt.Print(buffer.String())
|
|
}
|
|
}
|
|
}
|
|
|
|
func (e *ESDI) telemetry() {
|
|
// Set the handlers
|
|
done := e.setupSignalHandlers()
|
|
|
|
// go sendData(e.SerialConn, done)
|
|
go printData(done)
|
|
|
|
mainLoopTicker := time.NewTicker(time.Second / 60)
|
|
defer mainLoopTicker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-done:
|
|
resetTerminal()
|
|
fmt.Println("Quitting ESDI...")
|
|
return
|
|
case <-mainLoopTicker.C:
|
|
time.Sleep(time.Second / 60)
|
|
|
|
var err error
|
|
|
|
err = e.Source.UpdateData()
|
|
if err != nil {
|
|
fmt.Printf("could not update data: %v", err)
|
|
continue
|
|
}
|
|
|
|
// Vehicle Movement data
|
|
curGear, err := e.Source.GetData("Gear")
|
|
if err != nil {
|
|
log.Fatalf("could not get field `Gear`: %v", err)
|
|
}
|
|
|
|
curRPM, err := e.Source.GetData("RPM")
|
|
if err != nil {
|
|
log.Fatalf("could not get field `RPM`: %v", err)
|
|
}
|
|
|
|
curSpeed, err := e.Source.GetData("Speed")
|
|
if err != nil {
|
|
log.Fatalf("could not get field `Speed`: %v", err)
|
|
}
|
|
|
|
gear := int32(curGear.(int))
|
|
rpm := int32(curRPM.(float32))
|
|
speed := int32(msToKph(curSpeed.(float32)))
|
|
|
|
// Fuel data
|
|
fLiters, err := e.Source.GetData("FuelLevel")
|
|
if err != nil {
|
|
log.Fatalf("could not get field `FuelLevel`: %v", err)
|
|
}
|
|
|
|
fPct, err := e.Source.GetData("FuelLevelPct")
|
|
if err != nil {
|
|
log.Fatalf("could not get field `FuelLevelPct`: %v", err)
|
|
}
|
|
|
|
fuelPct := float32(fPct.(float32)) * 100
|
|
fuelLiters := float32(fLiters.(float32))
|
|
totalFuel := (100 * fuelLiters) / fuelPct
|
|
|
|
// Lap data
|
|
currentLap, err := e.Source.GetData("Lap")
|
|
if err != nil {
|
|
log.Fatalf("could not get field `Lap`: %v", err)
|
|
}
|
|
|
|
lapDistPct, err := e.Source.GetData("LapDistPct")
|
|
if err != nil {
|
|
log.Fatalf("could not get field `Lap`: %v", err)
|
|
}
|
|
|
|
currentLapTime, err := e.Source.GetData("LapCurrentLapTime")
|
|
if err != nil {
|
|
log.Fatalf("could not get field `LapCurrentLapTime`: %v", err)
|
|
}
|
|
|
|
lapBestLapTime, err := e.Source.GetData("LapBestLapTime")
|
|
if err != nil {
|
|
log.Fatalf("could not get field `LapBestLapTime`: %v", err)
|
|
}
|
|
|
|
lapLastLapTime, err := e.Source.GetData("LapLastLapTime")
|
|
if err != nil {
|
|
log.Fatalf("could not get field `LapBestLapTime`: %v", err)
|
|
}
|
|
|
|
lapDeltaToBestLap, err := e.Source.GetData("LapDeltaToBestLap")
|
|
if err != nil {
|
|
log.Fatalf("could not get field `LapDeltaToBestLap`: %v", err)
|
|
}
|
|
|
|
lap := int32(currentLap.(int))
|
|
lapPct := float32(lapDistPct.(float32)) * 100
|
|
lapTime := string(lapTimeRepresentation(currentLapTime.(float32)))
|
|
bestLapTime := string(lapTimeRepresentation(lapBestLapTime.(float32)))
|
|
lapDelta := string(lapTimeDeltaRepresentation(lapDeltaToBestLap.(float32)))
|
|
lastLapTime := string(lapTimeRepresentation(lapLastLapTime.(float32)))
|
|
|
|
// Standings
|
|
standings := createStandingsTable(i, 9, relativeStandings)
|
|
positions := i.Vars.Vars["CarIdxPosition"].Value.([]int32)
|
|
p := positions[9]
|
|
standings = standings[p - 3: p + 2]
|
|
|
|
fmt.Printf("\033[?25l\033[2J\033[H")
|
|
for p, v := range standings {
|
|
fmt.Printf("[%2d] [%2d]%-30s %3d %10f %s\n",
|
|
p+1, v.CarIdx, v.DriverName, v.Lap, v.LapPct, lapTimeRepresentation(v.TimeBehind))
|
|
}
|
|
|
|
|
|
mu.Lock()
|
|
data = DataPacket{
|
|
Speed: speed,
|
|
Gear: gear,
|
|
RPM: rpm,
|
|
LapCount: lap,
|
|
LapDistPct: lapPct,
|
|
LapTime: lapTime,
|
|
LastLapTime: lastLapTime,
|
|
BestLapTime: bestLapTime,
|
|
LapDelta: lapDelta,
|
|
FuelLiters: fuelLiters,
|
|
FuelPct: fuelPct,
|
|
FuelTotal: totalFuel,
|
|
}
|
|
mu.Unlock()
|
|
}
|
|
}
|
|
}
|