So, we got the telemetry service going and it polls back to the UI

This commit is contained in:
2026-03-03 23:06:19 +00:00
parent f8e65a03d2
commit da315b1c7b
4 changed files with 258 additions and 191 deletions
+22 -183
View File
@@ -1,59 +1,32 @@
package controllers
import (
esdi "esdi/oldEsdi"
"esdi/tui/internal/services"
"esdi/tui/internal/views"
"fmt"
"log"
"os"
"strings"
"sync"
"time"
"github.com/ESilva15/goirsdk"
"github.com/gdamore/tcell/v2"
)
// Car data lengths
const (
SpeedLen = 5
GearLen = 3
RpmLen = 6
BrakeBiasLen = 6
)
type StreamingCtrl struct {
*Controller
Service *services.CDashService
LastMessageTime time.Time
Data *esdi.SimulationData
DataView *esdi.DataPacket
InitialTime time.Time
LastTime time.Time
Mut sync.Mutex
Irsdk *goirsdk.IBT
StreamView *views.StreamView
Messages chan string
Internal chan string
Run bool
OnExit func()
isRunning bool
Service *services.CDashService
StreamView *views.StreamView
Messages chan string
Internal chan string
Run bool
OnExit func()
isRunning bool
}
func NewStreamingCtrl(base *Controller, ser *services.CDashService) *StreamingCtrl {
ctrl := &StreamingCtrl{
Controller: base,
Service: ser,
Data: &esdi.SimulationData{},
DataView: &esdi.DataPacket{},
InitialTime: time.Now(),
LastTime: time.Now(),
Messages: make(chan string, 10),
Internal: make(chan string, 10),
Run: false,
StreamView: views.NewStreamView(),
isRunning: false,
Controller: base,
Service: ser,
Messages: make(chan string, 10),
Internal: make(chan string, 10),
Run: false,
StreamView: views.NewStreamView(),
isRunning: false,
}
ctrl.registerHooks()
@@ -82,156 +55,22 @@ func (sc *StreamingCtrl) registerHooks() {
}
func (sc *StreamingCtrl) Stop() {
sc.Run = false
sc.Service.StopStream()
}
func (sc *StreamingCtrl) Start() {
// Open the telemetry file
file, err := os.Open("/home/esilva/Desktop/projetos/simracing_peripherals/testTelemetry/supercars_indianapolis.ibt")
if err != nil {
log.Fatalf("Failed to open IBT file: %v", err)
}
sc.Service.StartStream()
sc.Messages <- "started stream\n"
sc.Irsdk, err = goirsdk.Init(file, "./out.ibt", "./out.yaml")
if err != nil {
log.Fatalf("Failed to load iRacing data")
}
stream := sc.Service.GetStream()
sc.Messages <- "got stream\n"
startTime := time.Now()
go sc.ReadData()
sc.Run = true
// global irsdk is now here
go func() {
ticker := time.NewTicker(time.Second / 60)
defer ticker.Stop()
counter := 0
for t := range ticker.C {
if !sc.Run {
break
}
// whatever
currentTime := time.Now()
delta := currentTime.Sub(startTime)
str := sc.Stringified() + fmt.Sprintf("%v - %7d\n%5f - %5f",
t.UTC(), counter, delta.Seconds(), delta.Seconds()/float64(counter))
for msg := range stream {
sc.Messages <- "received message\n"
sc.App.QueueUpdateDraw(func() {
sc.StreamView.Update(str)
sc.StreamView.Update(msg)
})
counter++
}
}()
}
func (sc *StreamingCtrl) ReadData() {
dataReaderTicker := time.NewTicker(time.Second / 60)
defer dataReaderTicker.Stop()
sc.InitialTime = time.Now()
for _ = range dataReaderTicker.C {
var err error
sc.Mut.Lock()
_, err = sc.Irsdk.Update(time.Millisecond * 100)
if err != nil {
fmt.Printf("could not update data: %v", err)
continue
}
sc.Mut.Unlock()
sc.getVehicleData()
// Test the actual dataPacket we are sending over the wire
copyBytes(sc.DataView.Speed[:], SpeedLen, fmt.Sprintf("%3d", sc.Data.Speed))
copyBytes(sc.DataView.Gear[:], GearLen, fmt.Sprintf("%2d", sc.Data.Gear))
copyBytes(sc.DataView.RPM[:], RpmLen, fmt.Sprintf("%3d", sc.Data.RPM))
sc.LastMessageTime = time.Now()
}
}
func (sc *StreamingCtrl) getVehicleData() {
sc.Mut.Lock()
curGear := sc.Irsdk.Vars.Vars["Gear"].Value
curRPM := sc.Irsdk.Vars.Vars["RPM"].Value
curSpeed := sc.Irsdk.Vars.Vars["Speed"].Value
sc.Data.Gear = int32(curGear.(int))
sc.Data.RPM = int32(curRPM.(float32))
sc.Data.Speed = int32(msToKph(curSpeed.(float32)))
sc.Mut.Unlock()
}
func copyBytes(dest []byte, destSize int, src string) {
copy(dest[:], []byte(src))
dest[min(destSize-1, len(src))] = '\x00'
}
func msToKph(v float32) int {
return int((3600 * v) / 1000)
}
// Move this function to the views
func (sc *StreamingCtrl) Stringified() string {
var buffer strings.Builder
sc.Mut.Lock()
sessionTimeR := sc.Irsdk.Vars.Vars["SessionTime"].Value
sessionTime := float64(sessionTimeR.(float64))
currTime := time.Now()
delta := currTime.Sub(sc.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))
sc.LastTime = currTime
elapsed := currTime.Sub(sc.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",
sc.Data.Gear, sc.Data.RPM, sc.Data.Speed))
// 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))
sc.Mut.Unlock()
return buffer.String()
}
@@ -9,18 +9,21 @@ import (
)
type CDashService struct {
Logger *slog.Logger
CDash *cdashdisplay.CDashDisplay
DevClerk *peripheral.PeripheralDeviceClerk
Messages chan string
Logger *slog.Logger
CDash *cdashdisplay.CDashDisplay
iRacingTelemetry *IRacingService
DevClerk *peripheral.PeripheralDeviceClerk
Messages chan string
}
func NewCDashService(logger *slog.Logger) *CDashService {
sharedChannel := make(chan string, 10)
return &CDashService{
Logger: logger,
CDash: nil,
DevClerk: peripheral.NewPeripheralDeviceClerk(),
Messages: make(chan string, 10),
Logger: logger,
CDash: nil,
DevClerk: peripheral.NewPeripheralDeviceClerk(),
Messages: sharedChannel,
iRacingTelemetry: NewIRacingService(sharedChannel),
}
}
@@ -90,3 +93,15 @@ func (cds *CDashService) MoveWindow(win *models.UIWindow, vec *helper.Vector) er
return nil
}
func (cds *CDashService) StartStream() {
cds.iRacingTelemetry.StartStream()
}
func (cds *CDashService) GetStream() <-chan string {
return cds.iRacingTelemetry.GetStream()
}
func (cds *CDashService) StopStream() {
cds.iRacingTelemetry.StopStream()
}
+211
View File
@@ -0,0 +1,211 @@
package services
// NOTE: I have to think about how to implement this, for now I'm bruteforcing
// iracing support only. But I should have a generic service that can gather
// telemetry from multiples sims and just publish it in an internal format
import (
"context"
esdi "esdi/oldEsdi"
"fmt"
"log"
"os"
"strings"
"sync"
"time"
"github.com/ESilva15/goirsdk"
)
// Car data lengths
const (
SpeedLen = 5
GearLen = 3
RpmLen = 6
BrakeBiasLen = 6
)
type StreamState int
const (
StreamStatePaused StreamState = 0
StreamStateRunning StreamState = 1
StreamStateOff StreamState = 2
)
type IRacingService struct {
Message chan string
LastMessageTime time.Time
Data *esdi.SimulationData
DataView *esdi.DataPacket
InitialTime time.Time
LastTime time.Time
Mut sync.Mutex
ticker *time.Ticker
Irsdk *goirsdk.IBT
//
isRunning bool
Stream chan string
StreamCancel context.CancelFunc
}
func NewIRacingService(msg chan string) *IRacingService {
// Open the telemetry file
file, err := os.Open("/home/esilva/Desktop/projetos/simracing_peripherals/testTelemetry/supercars_indianapolis.ibt")
if err != nil {
log.Fatalf("Failed to open IBT file: %v", err)
}
irsdk, err := goirsdk.Init(file, "./out.ibt", "./out.yaml")
if err != nil {
log.Fatalf("Failed to load iRacing data")
}
return &IRacingService{
Message: msg,
ticker: time.NewTicker(time.Second / 60),
Irsdk: irsdk,
Stream: make(chan string, 10),
Data: &esdi.SimulationData{},
DataView: &esdi.DataPacket{},
isRunning: false,
}
}
func (irs *IRacingService) GetStream() <-chan string {
return irs.Stream
}
func (irs *IRacingService) StartStream() {
if irs.isRunning {
return
}
var ctx context.Context
ctx, irs.StreamCancel = context.WithCancel(context.Background())
irs.startStream(ctx)
irs.isRunning = true
}
func (irs *IRacingService) StopStream() {
if irs.StreamCancel != nil {
irs.StreamCancel()
}
}
func (irs *IRacingService) startStream(ctx context.Context) {
irs.Message <- "STARTING THIS\n"
irs.InitialTime = time.Now()
go func() {
for {
select {
case <-ctx.Done():
irs.Message <- "CONTEXT SAID WE ARE DONE\n"
return
case <-irs.ticker.C:
irs.Message <- "GOT A TICK\n"
irs.ReadData(ctx)
irs.Stream <- irs.Stringified()
}
irs.Message <- "we are we going???\n"
}
}()
}
func (irs *IRacingService) ReadData(ctx context.Context) {
irs.Mut.Lock()
defer irs.Mut.Unlock()
var err error
_, err = irs.Irsdk.Update(time.Millisecond * 100)
if err != nil {
return
}
irs.getVehicleData()
// Test the actual dataPacket we are sending over the wire
copyBytes(irs.DataView.Speed[:], SpeedLen, fmt.Sprintf("%3d", irs.Data.Speed))
copyBytes(irs.DataView.Gear[:], GearLen, fmt.Sprintf("%2d", irs.Data.Gear))
copyBytes(irs.DataView.RPM[:], RpmLen, fmt.Sprintf("%3d", irs.Data.RPM))
irs.LastMessageTime = time.Now()
}
func (irs *IRacingService) getVehicleData() {
curGear := irs.Irsdk.Vars.Vars["Gear"].Value
curRPM := irs.Irsdk.Vars.Vars["RPM"].Value
curSpeed := irs.Irsdk.Vars.Vars["Speed"].Value
irs.Data.Gear = int32(curGear.(int))
irs.Data.RPM = int32(curRPM.(float32))
irs.Data.Speed = int32(msToKph(curSpeed.(float32)))
}
func copyBytes(dest []byte, destSize int, src string) {
copy(dest[:], []byte(src))
dest[min(destSize-1, len(src))] = '\x00'
}
func msToKph(v float32) int {
return int((3600 * v) / 1000)
}
func (irs *IRacingService) Stringified() string {
var buffer strings.Builder
irs.Mut.Lock()
sessionTimeR := irs.Irsdk.Vars.Vars["SessionTime"].Value
sessionTime := float64(sessionTimeR.(float64))
currTime := time.Now()
delta := currTime.Sub(irs.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))
irs.LastTime = currTime
elapsed := currTime.Sub(irs.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",
irs.Data.Gear, irs.Data.RPM, irs.Data.Speed))
// 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))
irs.Mut.Unlock()
return buffer.String()
}
+2
View File
@@ -0,0 +1,2 @@
// Package services interacts with the other libraries required for this UI
package services