diff --git a/providers/beamng/utils.go b/providers/beamng/utils.go new file mode 100644 index 0000000..2854b74 --- /dev/null +++ b/providers/beamng/utils.go @@ -0,0 +1,33 @@ +package beamng + +import ( + "log/slog" + "net" + "time" +) + +func IsRunning() bool { + // The address should be loaded from some type of configuration + addr, err := net.ResolveUDPAddr("udp", "127.0.0.1:4443") + if err != nil { + return false + } + + conn, err := net.ListenUDP("udp", addr) + defer conn.Close() + if err != nil { + slog.Debug("failed to listen to udp socket: " + err.Error()) + return false + } + + buf := make([]byte, 512) + conn.SetReadDeadline(time.Now().Add(50 * time.Millisecond)) + + n, _, err := conn.ReadFromUDP(buf) + if err != nil { + return false + } + + // Think of a better number or something + return n >= 80 +} diff --git a/providers/iracing/utils.go b/providers/iracing/utils.go new file mode 100644 index 0000000..b7b1d19 --- /dev/null +++ b/providers/iracing/utils.go @@ -0,0 +1,16 @@ +package iracing + +import "github.com/ESilva15/goirsdk" + +func IsRunning() bool { + sdk, err := goirsdk.Init(goirsdk.Options{ + SourceType: goirsdk.SharedMemoryFile, + }) + if err != nil { + return false + } + + sdk.Close() + + return true +} diff --git a/providers/providers.go b/providers/providers.go index e95e079..e635f39 100644 --- a/providers/providers.go +++ b/providers/providers.go @@ -14,28 +14,42 @@ import ( // Make this be some kind of struct where we can access a function that returns // the selected provider by its name type Provider struct { - Name string - Provider telemetry.TelemetryProvider + Name string + NewProvider func(*slog.Logger) telemetry.TelemetryProvider + IsRunning func() bool } var Providers = map[string]Provider{ beamng.NAME: { - Name: beamng.NAME, + Name: beamng.NAME, + NewProvider: NewBeamNGProvider, + IsRunning: beamng.IsRunning, }, iracing.NAME: { - Name: iracing.NAME, + Name: iracing.NAME, + NewProvider: NewLiveIRacingProvider, + IsRunning: iracing.IsRunning, }, } -func NewIRacingProvider(logger *slog.Logger, opts goirsdk.Options, -) telemetry.TelemetryProvider { - provider, _ := iracing.NewIRacingProvider(logger, opts) +// func NewIRacingProvider(logger *slog.Logger, opts goirsdk.Options, +// ) telemetry.TelemetryProvider { +// provider, _ := iracing.NewIRacingProvider(logger, opts) +// +// return provider +// } + +func NewLiveIRacingProvider(logger *slog.Logger) telemetry.TelemetryProvider { + provider, _ := iracing.NewIRacingProvider(logger, goirsdk.Options{ + SourceType: goirsdk.SharedMemoryFile, + }) return provider } -func NewBeamNGProvider(ip string, port int) telemetry.TelemetryProvider { - provider, _ := beamng.NewBeamNGProvider(ip, port) +func NewBeamNGProvider(logger *slog.Logger) telemetry.TelemetryProvider { + // Get from some kind of config or whatever + provider, _ := beamng.NewBeamNGProvider("127.0.0.1", 4443) return provider } diff --git a/services/cdashdisplay.go b/services/cdashdisplay.go deleted file mode 100644 index 111b2dc..0000000 --- a/services/cdashdisplay.go +++ /dev/null @@ -1,131 +0,0 @@ -package services - -// type CDashService struct { -// Logger *slog.Logger -// CDash *cdashdisplay.CDashDisplay -// // DevClerk *peripheral.PeripheralDeviceClerk -// // Messages chan string -// // Telemetry Channel -// // streamCancel context.CancelFunc -// // TelemCh <-chan telemetry.TelemetryData -// } - -// func NewCDashService(logger *slog.Logger) *CDashService { -// // sharedChannel := make(chan string, 10) -// return &CDashService{ -// Logger: logger, -// CDash: nil, -// // DevClerk: peripheral.NewPeripheralDeviceClerk(), -// // Messages: sharedChannel, -// } -// } - -// func (ds *CDashService) FindDevice() { -// ds.Messages <- "looking for cdash display...\n" -// ds.Logger.Info("Looking for CDashDisplay") - -// cdashdisplay.SetLogger(ds.Logger.With("[device]", "cdashdisplay")) - -// display, err := cdashdisplay.Discover() -// if err != nil { -// ds.Logger.Info("didn't find cdashdisplay") -// ds.Messages <- "didn't find cdash display\n" -// return -// } -// -// ds.CDash = display -// ds.Logger.Info("found cdashdisplay on: " + display.WT.Cfg.Name) -// ds.Messages <- "found cdashdisplay on: " + display.WT.Cfg.Name + "\n" -// } - -// func (ds *CDashService) CreateWindow( -// win *cdashdisplay.DesktopUIWindow, -// ) (*cdashdisplay.DesktopUIWindow, error) { -// updatedWindow, err := ds.CDash.CreateWindow(win) -// if err != nil { -// return nil, err -// } -// -// return updatedWindow, nil -// } -// -// func (ds *CDashService) LoadLayout(layoutPath string) error { -// return ds.CDash.LoadLayout(layoutPath) -// } -// -// func (ds *CDashService) SaveLayout(layoutPath string) error { -// return ds.CDash.SaveLayout(layoutPath) -// } -// -// func (ds *CDashService) UnloadLayout() error { -// return ds.CDash.UnloadLayout() -// } -// -// func (ds *CDashService) UpdateWindow(win *cdashdisplay.DesktopUIWindow) error { -// ds.Messages <- fmt.Sprintf("Updating a window:\n%+v\n", win) -// return ds.CDash.UpdateWindow(win) -// } -// -// func (ds *CDashService) DeleteWindow(idx int16) error { -// return ds.CDash.DestroyWindow(idx) -// } -// -// func (ds *CDashService) ResizeWindow(idx int16, vec *helper.Vector) error { -// err := ds.CDash.ResizeWindow(idx, vec) -// if err != nil { -// return err -// } -// -// return nil -// } -// -// func (ds *CDashService) MoveWindow(idx int16, vec *helper.Vector) error { -// return ds.CDash.MoveWindow(idx, vec) -// } - -// func (ds *CDashService) SetTelemetryChannel(ch <-chan telemetry.TelemetryData) { -// ds.TelemCh = ch -// } - -// func (cds *CDashService) StartStream() { -// // NOTE: i'm using this pattern a whole lot. Maybe I can create a struct to handle this -// var ctx context.Context -// ctx, cds.streamCancel = context.WithCancel(context.Background()) -// -// go cds.transmit(ctx) -// } -// -// func (cds *CDashService) StopStream() { -// if cds.streamCancel == nil { -// return -// } -// -// cds.streamCancel() -// cds.streamCancel = nil -// } - -// INTERNAL - -// func (ds *CDashService) transmit(ctx context.Context) { -// var isSending atomic.Bool -// -// for { -// select { -// case <-ctx.Done(): -// return -// case data, ok := <-ds.TelemCh: -// if !ok { -// return -// } -// -// if isSending.Load() { -// continue -// } -// -// isSending.Store(true) -// -// ds.CDash.SendData(&data) -// isSending.Store(false) -// } -// } -// } diff --git a/services/telemetry.go b/services/telemetry.go index f50b532..b68d32c 100644 --- a/services/telemetry.go +++ b/services/telemetry.go @@ -4,11 +4,10 @@ import ( "context" "log/slog" "sync" + "time" "esdi/providers" telem "esdi/telemetry" - - "github.com/ESilva15/goirsdk" ) // TelemetryService will be our base struct to handle telemetry data @@ -19,33 +18,61 @@ type TelemetryService struct { // Concurrency protection mut sync.RWMutex ativeProvider telem.TelemetryProvider + isConnected bool // Channel for the UI listeners map[string]chan telem.TelemetryData cancelForward context.CancelFunc + // Output window + Messages chan string + // Cancel looking for providers + CtxMonitor context.Context + cancelMonitor context.CancelFunc } func NewTelemetryService(logger *slog.Logger, devServo *DeviceService) *TelemetryService { + sharedChannel := make(chan string, 10) newService := &TelemetryService{ - logger: logger, - devService: devServo, - listeners: make(map[string]chan telem.TelemetryData), + logger: logger, + isConnected: false, + devService: devServo, + listeners: make(map[string]chan telem.TelemetryData), + Messages: sharedChannel, } - - // TODO: do not create and set a provider here, a background job should be - // detecting providers instead - - // Need to instantiate a default provider here - // source := "/home/esilva/Desktop/projetos/simracing_peripherals/testTelemetry/gt3_mustang_bathurst.ibt" - firstProvider := providers.NewIRacingProvider(slog.Default(), goirsdk.Options{ - SourceType: goirsdk.SharedMemoryFile, - }) - // firstProvider := providers.NewBeamNGProvider("127.0.0.1", 4443) - - newService.SwitchProvider(firstProvider) + newService.CtxMonitor, newService.cancelMonitor = context.WithCancel(context.Background()) return newService } +func (t *TelemetryService) OnFindProvider(prov providers.Provider) { + t.SwitchProvider(prov.NewProvider(t.logger)) + t.logger.Debug("Found provider for " + prov.Name) +} + +// TODO: add some way of retriggering this. Currently it should: +// start monitoring on startup -> find provider -> stop monitoring +func (t *TelemetryService) FindProvider(ctx context.Context, callback func(providers.Provider), +) { + ticker := time.NewTicker(2 * time.Second) + defer ticker.Stop() + + t.logger.Debug("monitoring for providers") + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + for _, prov := range providers.Providers { + t.logger.Debug("checking provider: " + prov.Name) + if prov.IsRunning() { + callback(prov) + return + } + t.logger.Debug(" wasn't read") + } + } + } +} + func (t *TelemetryService) SwitchProvider(newProvider telem.TelemetryProvider) error { t.mut.Lock() defer t.mut.Unlock() diff --git a/tui/tui.go b/tui/tui.go index 747b9d7..68feb50 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -31,6 +31,8 @@ func NewControlPanel(logger *slog.Logger) *ControlPanel { panic("failed to create the telemetry service") } + go telemService.FindProvider(telemService.CtxMonitor, telemService.OnFindProvider) + return &ControlPanel{ Controller: baseController, DeviceController: controllers.NewDeviceController(baseController, devService, telemService),