telemetry service and telemetry agents interface
This commit is contained in:
@@ -0,0 +1,6 @@
|
|||||||
|
// Package conversions will host all of our unit conversion functions
|
||||||
|
package conversions
|
||||||
|
|
||||||
|
func MsToKph(v float32) int {
|
||||||
|
return int((3600 * v) / 1000)
|
||||||
|
}
|
||||||
+22
-3
@@ -11,9 +11,9 @@ type Vector struct {
|
|||||||
DY uint16
|
DY uint16
|
||||||
}
|
}
|
||||||
|
|
||||||
type MultiError struct {
|
// type MultiError struct {
|
||||||
Errors []error
|
// Errors []error
|
||||||
}
|
// }
|
||||||
|
|
||||||
func B32(s string) [32]byte {
|
func B32(s string) [32]byte {
|
||||||
var b [32]byte
|
var b [32]byte
|
||||||
@@ -30,3 +30,22 @@ func StructToBytes(s any) ([]byte, error) {
|
|||||||
|
|
||||||
return buf.Bytes(), nil
|
return buf.Bytes(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// func CopyBytes(dest []byte, destSize int, src string) {
|
||||||
|
// copy(dest[:], []byte(src))
|
||||||
|
// dest[min(destSize-1, len(src))] = '\x00'
|
||||||
|
// }
|
||||||
|
|
||||||
|
func CopyBytes(dest []byte, src string) {
|
||||||
|
// 1. Clear the destination (optional but safer for fixed-width telemetry)
|
||||||
|
for i := range dest {
|
||||||
|
dest[i] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Copy as much as fits, leaving at least 1 byte for a null terminator
|
||||||
|
// We limit the copy to len(dest) - 1
|
||||||
|
n := copy(dest[:len(dest)-1], src)
|
||||||
|
|
||||||
|
// 3. Explicitly null terminate after the last written byte
|
||||||
|
dest[n] = '\x00'
|
||||||
|
}
|
||||||
|
|||||||
+11
-22
@@ -2,9 +2,7 @@
|
|||||||
package esdi
|
package esdi
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"esdi/sources/iracing"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ESilva15/goirsdk"
|
"github.com/ESilva15/goirsdk"
|
||||||
@@ -67,26 +65,17 @@ func RunLiveTelemetry(port string, output string, session string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func RunOfflineTelemetry(port string, input string, output string, session string) {
|
func RunOfflineTelemetry(port string, input string, output string, session string) {
|
||||||
esdi, err := ESDIInit(port, 115200)
|
// 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 {
|
// if err != nil {
|
||||||
// log.Fatalf("Failed to create irsdk instance: %v\n", err)
|
// log.Fatalf("Failed to get Desktop Interface: %v", err)
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
esdi.irsdk = irsdk.SDK
|
// irsdk, err := iracing.Init(input, output, session)
|
||||||
|
// if err != nil {
|
||||||
esdi.telemetry()
|
// log.Fatalf("Failed to create iRacing interface: %v", err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// esdi.irsdk = irsdk.SDK
|
||||||
|
//
|
||||||
|
// esdi.telemetry()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
package iracing
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
conv "esdi/conversions"
|
||||||
|
helper "esdi/helpers"
|
||||||
|
telem "esdi/telemetry"
|
||||||
|
|
||||||
|
"github.com/ESilva15/goirsdk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IRacing is our iRacing telemetry data provider - its a TelemetryProvider interface
|
||||||
|
type IRacing struct {
|
||||||
|
SDK *goirsdk.IBT
|
||||||
|
|
||||||
|
// Data Handling
|
||||||
|
mut sync.Mutex
|
||||||
|
data *telem.TelemetryData
|
||||||
|
|
||||||
|
// Timing information
|
||||||
|
initialTime time.Time
|
||||||
|
lastMessageTime time.Time
|
||||||
|
ticker time.Ticker // ticker will keep polling intervals constant
|
||||||
|
|
||||||
|
// Stream
|
||||||
|
streamCh chan telem.TelemetryData
|
||||||
|
streamCancel context.CancelFunc
|
||||||
|
// isRunning bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewIRacingProvider(source string, telemOut string, yamlOut string) (*IRacing, error) {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
// Open the input file if provided
|
||||||
|
var file *os.File = nil
|
||||||
|
if source != "" {
|
||||||
|
file, err = os.Open(source)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to open IBT file: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sdk, err := goirsdk.Init(file, telemOut, yamlOut)
|
||||||
|
if err != nil {
|
||||||
|
return &IRacing{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &IRacing{
|
||||||
|
SDK: sdk,
|
||||||
|
data: telem.NewTelemetryData(),
|
||||||
|
streamCh: make(chan telem.TelemetryData),
|
||||||
|
ticker: *time.NewTicker(time.Second / 60),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IRacing) stream(ctx context.Context) {
|
||||||
|
i.initialTime = time.Now()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-i.ticker.C:
|
||||||
|
i.ReadData()
|
||||||
|
|
||||||
|
// Publish data
|
||||||
|
i.streamCh <- *i.data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IRacing) ReadData() {
|
||||||
|
i.mut.Lock()
|
||||||
|
defer i.mut.Unlock()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
|
||||||
|
_, err = i.SDK.Update(time.Millisecond * 100)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i.readVehicleData()
|
||||||
|
|
||||||
|
i.lastMessageTime = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IRacing) readVehicleData() {
|
||||||
|
curGear := i.SDK.Vars.Vars["Gear"].Value
|
||||||
|
curRPM := i.SDK.Vars.Vars["RPM"].Value
|
||||||
|
curSpeed := i.SDK.Vars.Vars["Speed"].Value
|
||||||
|
|
||||||
|
speed := fmt.Sprintf("%3d", int32(conv.MsToKph(curSpeed.(float32))))
|
||||||
|
gear := fmt.Sprintf("%2d", int32(curGear.(int)))
|
||||||
|
rpm := fmt.Sprintf("%3d", int32(curRPM.(float32)))
|
||||||
|
|
||||||
|
speedArray := i.data.Values["Speed"]
|
||||||
|
helper.CopyBytes(speedArray[:], speed)
|
||||||
|
i.data.Values["Speed"] = speedArray
|
||||||
|
|
||||||
|
gearArray := i.data.Values["Gear"]
|
||||||
|
helper.CopyBytes(gearArray[:], gear)
|
||||||
|
i.data.Values["Gear"] = gearArray
|
||||||
|
|
||||||
|
rpmArray := i.data.Values["RPM"]
|
||||||
|
helper.CopyBytes(rpmArray[:], rpm)
|
||||||
|
i.data.Values["RPM"] = rpmArray
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IRacing) Stream() (<-chan telem.TelemetryData, error) {
|
||||||
|
var ctx context.Context
|
||||||
|
ctx, i.streamCancel = context.WithCancel(context.Background())
|
||||||
|
|
||||||
|
// Start the stream
|
||||||
|
i.stream(ctx)
|
||||||
|
|
||||||
|
return i.streamCh, nil
|
||||||
|
}
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
package iracing
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ESilva15/goirsdk"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This will implement the GameSink interface from the main package
|
|
||||||
type IRacing struct {
|
|
||||||
SDK *goirsdk.IBT
|
|
||||||
}
|
|
||||||
|
|
||||||
const (
|
|
||||||
NAME = "iRacing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Init(f goirsdk.Reader, telemOut string, yamlOut string) (IRacing, error) {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
sdk, err := goirsdk.Init(f, telemOut, yamlOut)
|
|
||||||
if err != nil {
|
|
||||||
return IRacing{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return IRacing{SDK: sdk}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IRacing) GetData(fieldName string) (interface{}, error) {
|
|
||||||
if val, ok := i.SDK.Vars.Vars[fieldName]; ok {
|
|
||||||
return val.Value, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("key `%s` doesn't exist", fieldName)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IRacing) UpdateData() error {
|
|
||||||
_, err := i.SDK.Update(100 * time.Millisecond)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *IRacing) GetSessionInfo() (interface{}, error) {
|
|
||||||
return i.SDK.SessionInfo, nil
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package telemetry
|
||||||
|
|
||||||
|
type TelemetryField struct {
|
||||||
|
Parser func()
|
||||||
|
Value any
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: Replace values with a more appropriate custom field approach where
|
||||||
|
// every custom field only takes as many bytes as required
|
||||||
|
// NOTE: Add the timing fields here to count frames of data gathering and whatnot
|
||||||
|
// remember to do the same to whoever is sending data
|
||||||
|
|
||||||
|
type TelemetryData struct {
|
||||||
|
// Values map[string]*TelemetryField
|
||||||
|
Values map[string][32]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTelemetryData() *TelemetryData {
|
||||||
|
return &TelemetryData{
|
||||||
|
Values: make(map[string][32]byte),
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
// Package telemetry is our interface with our data sources
|
||||||
|
package telemetry
|
||||||
|
|
||||||
|
type TelemetryProvider interface {
|
||||||
|
Stream() (<-chan TelemetryData, error)
|
||||||
|
}
|
||||||
@@ -17,22 +17,18 @@ type DeviceController struct {
|
|||||||
DevService *serv.CDashService
|
DevService *serv.CDashService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeviceController(base *Controller, devService *serv.CDashService) *DeviceController {
|
func NewDeviceController(
|
||||||
|
base *Controller,
|
||||||
|
devService *serv.CDashService,
|
||||||
|
telemService *serv.TelemetryService,
|
||||||
|
) *DeviceController {
|
||||||
mc := &DeviceController{
|
mc := &DeviceController{
|
||||||
Controller: base,
|
Controller: base,
|
||||||
LayoutCtrl: NewLayoutController(base, devService),
|
LayoutCtrl: NewLayoutController(base, devService),
|
||||||
DevService: devService,
|
DevService: devService,
|
||||||
StreamCtrl: NewStreamingCtrl(base, devService),
|
StreamCtrl: NewStreamingCtrl(base, devService, telemService),
|
||||||
}
|
}
|
||||||
|
|
||||||
// mc.Bus.On(ui.StartStreamingReqEv{}, func(e any) {
|
|
||||||
// mc.StreamStrl.Start(mc.Bus)
|
|
||||||
// })
|
|
||||||
|
|
||||||
// mc.Bus.On(ui.StopStreamingReqEv{}, func(e any) {
|
|
||||||
// mc.StreamStrl.Stop(mc.Bus)
|
|
||||||
// })
|
|
||||||
|
|
||||||
return mc
|
return mc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,12 +16,18 @@ type StreamingCtrl struct {
|
|||||||
Run bool
|
Run bool
|
||||||
OnExit func()
|
OnExit func()
|
||||||
isRunning bool
|
isRunning bool
|
||||||
|
TelemServ *services.TelemetryService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStreamingCtrl(base *Controller, ser *services.CDashService) *StreamingCtrl {
|
func NewStreamingCtrl(
|
||||||
|
base *Controller,
|
||||||
|
serCDash *services.CDashService,
|
||||||
|
serTelem *services.TelemetryService,
|
||||||
|
) *StreamingCtrl {
|
||||||
ctrl := &StreamingCtrl{
|
ctrl := &StreamingCtrl{
|
||||||
Controller: base,
|
Controller: base,
|
||||||
Service: ser,
|
Service: serCDash,
|
||||||
|
TelemServ: serTelem,
|
||||||
Messages: make(chan string, 10),
|
Messages: make(chan string, 10),
|
||||||
Internal: make(chan string, 10),
|
Internal: make(chan string, 10),
|
||||||
Run: false,
|
Run: false,
|
||||||
@@ -47,27 +53,18 @@ func (sc *StreamingCtrl) registerHooks() {
|
|||||||
sc.Start()
|
sc.Start()
|
||||||
case 'p':
|
case 'p':
|
||||||
// Pause
|
// Pause
|
||||||
sc.Stop()
|
// sc.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
return ev
|
return ev
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sc *StreamingCtrl) Stop() {
|
|
||||||
sc.Service.StopStream()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sc *StreamingCtrl) Start() {
|
func (sc *StreamingCtrl) Start() {
|
||||||
sc.Service.StartStream()
|
stream := sc.TelemServ.StartStream()
|
||||||
sc.Messages <- "started stream\n"
|
|
||||||
|
|
||||||
stream := sc.Service.GetStream()
|
|
||||||
sc.Messages <- "got stream\n"
|
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
for msg := range stream {
|
for msg := range stream {
|
||||||
sc.Messages <- "received message\n"
|
|
||||||
sc.App.QueueUpdateDraw(func() {
|
sc.App.QueueUpdateDraw(func() {
|
||||||
sc.StreamView.Update(msg)
|
sc.StreamView.Update(msg)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,21 +9,21 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type CDashService struct {
|
type CDashService struct {
|
||||||
Logger *slog.Logger
|
Logger *slog.Logger
|
||||||
CDash *cdashdisplay.CDashDisplay
|
CDash *cdashdisplay.CDashDisplay
|
||||||
iRacingTelemetry *IRacingService
|
// iRacingTelemetry *IRacingService
|
||||||
DevClerk *peripheral.PeripheralDeviceClerk
|
DevClerk *peripheral.PeripheralDeviceClerk
|
||||||
Messages chan string
|
Messages chan string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCDashService(logger *slog.Logger) *CDashService {
|
func NewCDashService(logger *slog.Logger) *CDashService {
|
||||||
sharedChannel := make(chan string, 10)
|
sharedChannel := make(chan string, 10)
|
||||||
return &CDashService{
|
return &CDashService{
|
||||||
Logger: logger,
|
Logger: logger,
|
||||||
CDash: nil,
|
CDash: nil,
|
||||||
DevClerk: peripheral.NewPeripheralDeviceClerk(),
|
DevClerk: peripheral.NewPeripheralDeviceClerk(),
|
||||||
Messages: sharedChannel,
|
Messages: sharedChannel,
|
||||||
iRacingTelemetry: NewIRacingService(sharedChannel),
|
// iRacingTelemetry: NewIRacingService(sharedChannel),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,14 +94,34 @@ func (cds *CDashService) MoveWindow(win *models.UIWindow, vec *helper.Vector) er
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cds *CDashService) StartStream() {
|
// func (cds *CDashService) StartStream() {
|
||||||
cds.iRacingTelemetry.StartStream()
|
// // We need to get a channel from the telemetry agent to listen to and
|
||||||
}
|
// // propogate the data to the clients
|
||||||
|
// dataStream := cds.iRacingTelemetry.StartStream()
|
||||||
|
//
|
||||||
|
// // Custom types to be able to actually send data to the device
|
||||||
|
// type CDashDisplayUIWindowStreamData struct {
|
||||||
|
// IDX int16
|
||||||
|
// Data [32]byte
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// type CDashDisplayStreamData struct {
|
||||||
|
// Data []CDashDisplayUIWindowStreamData
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // We will need a mechanism to kill this channel too I reckon
|
||||||
|
// go func() {
|
||||||
|
// for data := range dataStream {
|
||||||
|
// // We have to send this data to the device
|
||||||
|
// cds.Messages <- fmt.Sprintf("%d %d %d\n", data.Speed[:], data.Gear[:], data.RPM[:])
|
||||||
|
// }
|
||||||
|
// }()
|
||||||
|
// }
|
||||||
|
|
||||||
func (cds *CDashService) GetStream() <-chan string {
|
// func (cds *CDashService) GetStream() <-chan string {
|
||||||
return cds.iRacingTelemetry.GetStream()
|
// return cds.iRacingTelemetry.GetStream()
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
func (cds *CDashService) StopStream() {
|
// func (cds *CDashService) StopStream() {
|
||||||
cds.iRacingTelemetry.StopStream()
|
// cds.iRacingTelemetry.StopStream()
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
// DeviceService will handle sending the data from the telemetry service to the
|
||||||
|
// actual devices
|
||||||
|
// NOTE: create a virtual device and make it be the output window or something so
|
||||||
|
// we can just add it as a device or whatever instead of being a custom made thing
|
||||||
|
// that would be pretty cool I think
|
||||||
|
type DeviceService struct {
|
||||||
|
}
|
||||||
@@ -4,19 +4,6 @@ package services
|
|||||||
// iracing support only. But I should have a generic service that can gather
|
// 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
|
// 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
|
// Car data lengths
|
||||||
const (
|
const (
|
||||||
SpeedLen = 5
|
SpeedLen = 5
|
||||||
@@ -33,183 +20,71 @@ const (
|
|||||||
StreamStateOff StreamState = 2
|
StreamStateOff StreamState = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
type IRacingService struct {
|
// type IRacingService struct {
|
||||||
Message chan string
|
// Message chan string
|
||||||
// Timers
|
// // Timers
|
||||||
LastMessageTime time.Time
|
// LastMessageTime time.Time
|
||||||
InitialTime time.Time
|
// InitialTime time.Time
|
||||||
LastTime time.Time
|
// LastTime time.Time
|
||||||
ticker *time.Ticker
|
// ticker *time.Ticker
|
||||||
// Data vessels
|
// // Data vessels
|
||||||
data *esdi.SimulationData
|
// data *esdi.SimulationData
|
||||||
dataView *esdi.DataPacket
|
// dataView *esdi.DataPacket
|
||||||
// Data access control
|
// // Data access control
|
||||||
Mut sync.Mutex
|
// Mut sync.Mutex
|
||||||
// Source
|
// // Source
|
||||||
Irsdk *goirsdk.IBT
|
// Irsdk *goirsdk.IBT
|
||||||
// Stream control
|
// // Stream control
|
||||||
isRunning bool
|
// isRunning bool
|
||||||
Stream chan string
|
// UIStream chan string
|
||||||
StreamCancel context.CancelFunc
|
// DataStream chan *esdi.DataPacket
|
||||||
}
|
// StreamCancel context.CancelFunc
|
||||||
|
// }
|
||||||
|
|
||||||
func NewIRacingService(msg chan string) *IRacingService {
|
// func NewIRacingService(msg chan string) *IRacingService {
|
||||||
// Open the telemetry file
|
// // Open the telemetry file
|
||||||
file, err := os.Open("/home/esilva/Desktop/projetos/simracing_peripherals/testTelemetry/supercars_indianapolis.ibt")
|
// file, err := os.Open("/home/esilva/Desktop/projetos/simracing_peripherals/testTelemetry/supercars_indianapolis.ibt")
|
||||||
if err != nil {
|
// if err != nil {
|
||||||
log.Fatalf("Failed to open IBT file: %v", err)
|
// 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,
|
||||||
|
// UIStream: make(chan string, 10),
|
||||||
|
// DataStream: make(chan *esdi.DataPacket),
|
||||||
|
// data: &esdi.SimulationData{},
|
||||||
|
// dataView: &esdi.DataPacket{},
|
||||||
|
// isRunning: false,
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
irsdk, err := goirsdk.Init(file, "./out.ibt", "./out.yaml")
|
// func (irs *IRacingService) GetStream() <-chan string {
|
||||||
if err != nil {
|
// return irs.UIStream
|
||||||
log.Fatalf("Failed to load iRacing data")
|
// }
|
||||||
}
|
//
|
||||||
|
// func (irs *IRacingService) StartStream() <-chan *esdi.DataPacket {
|
||||||
|
// if irs.isRunning {
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var ctx context.Context
|
||||||
|
// ctx, irs.StreamCancel = context.WithCancel(context.Background())
|
||||||
|
//
|
||||||
|
// irs.startStream(ctx)
|
||||||
|
// irs.isRunning = true
|
||||||
|
//
|
||||||
|
// return irs.DataStream
|
||||||
|
// }
|
||||||
|
|
||||||
return &IRacingService{
|
// func (irs *IRacingService) StopStream() {
|
||||||
Message: msg,
|
// if irs.StreamCancel != nil {
|
||||||
ticker: time.NewTicker(time.Second / 60),
|
// irs.StreamCancel()
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,6 +1,45 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
// Telemetry will be our base struct to handle telemetry data
|
import (
|
||||||
|
providerir "esdi/providers/iracing"
|
||||||
|
|
||||||
|
telemetry "esdi/telemetry"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TelemetryService will be our base struct to handle telemetry data
|
||||||
// It should hook to a data sink and handle it like iRacing, BeamNG, AC and so on
|
// It should hook to a data sink and handle it like iRacing, BeamNG, AC and so on
|
||||||
type Telemetry struct {
|
type TelemetryService struct {
|
||||||
|
ActiveProvider telemetry.TelemetryProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTelemetryService() *TelemetryService {
|
||||||
|
return &TelemetryService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TelemetryService) setIRacingProvider() {
|
||||||
|
path := "/home/esilva/Desktop/projetos/simracing_peripherals/testTelemetry/supercars_indianapolis.ibt"
|
||||||
|
provider, _ := providerir.NewIRacingProvider(path, "", "")
|
||||||
|
|
||||||
|
t.ActiveProvider = provider
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TelemetryService) SetProvider(provider string) *TelemetryService {
|
||||||
|
if t.ActiveProvider != nil {
|
||||||
|
// Gotta do something here to clean up before switching
|
||||||
|
}
|
||||||
|
|
||||||
|
switch provider {
|
||||||
|
case "iRacing":
|
||||||
|
// Set up iRacing
|
||||||
|
t.setIRacingProvider()
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *TelemetryService) StartStream() <-chan telemetry.TelemetryData {
|
||||||
|
stream, _ := t.ActiveProvider.Stream()
|
||||||
|
return stream
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
package views
|
package views
|
||||||
|
|
||||||
import "github.com/rivo/tview"
|
import (
|
||||||
|
"esdi/telemetry"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/rivo/tview"
|
||||||
|
)
|
||||||
|
|
||||||
// Car data lengths
|
// Car data lengths
|
||||||
const (
|
const (
|
||||||
@@ -10,10 +16,6 @@ const (
|
|||||||
BrakeBiasLen = 6
|
BrakeBiasLen = 6
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
streamingBoxID = "streaming-box"
|
|
||||||
)
|
|
||||||
|
|
||||||
type StreamView struct {
|
type StreamView struct {
|
||||||
TextView *tview.TextView
|
TextView *tview.TextView
|
||||||
}
|
}
|
||||||
@@ -27,6 +29,15 @@ func NewStreamView() *StreamView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sv *StreamView) Update(str string) {
|
func (sv *StreamView) Update(data telemetry.TelemetryData) {
|
||||||
sv.TextView.SetText(str)
|
sv.TextView.SetText(stringify(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringify(data telemetry.TelemetryData) string {
|
||||||
|
var buffer strings.Builder
|
||||||
|
|
||||||
|
buffer.WriteString(fmt.Sprintf("Gear: %d, RPM: %d, Speed: %d\n\n",
|
||||||
|
data.Values["Gear"], data.Values["RPM"], data.Values["Speed"]))
|
||||||
|
|
||||||
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-2
@@ -21,11 +21,15 @@ func NewControlPanel(logger *slog.Logger) *ControlPanel {
|
|||||||
App: tview.NewApplication(),
|
App: tview.NewApplication(),
|
||||||
}
|
}
|
||||||
|
|
||||||
deviceService := services.NewCDashService(logger)
|
devService := services.NewCDashService(logger)
|
||||||
|
telemService := services.NewTelemetryService().SetProvider("iRacing")
|
||||||
|
if telemService == nil {
|
||||||
|
panic("failed to create the telemetry service")
|
||||||
|
}
|
||||||
|
|
||||||
return &ControlPanel{
|
return &ControlPanel{
|
||||||
Controller: baseController,
|
Controller: baseController,
|
||||||
DeviceController: controllers.NewDeviceController(baseController, deviceService),
|
DeviceController: controllers.NewDeviceController(baseController, devService, telemService),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user