Provider loop discovery almost finished

Provider lookup will lookup on startup, do the healthcheck until the
stream starts and also restart on stream closure or provider stall
This commit is contained in:
2026-09-17 23:01:50 +01:00
parent c1366b010f
commit dbd4086bf9
8 changed files with 114 additions and 19 deletions
+31
View File
@@ -0,0 +1,31 @@
# Provider Discovery
```
NewControlPanel()
go telemService.FindProvider() - has a callback for onFind
|
|iterates over the known providers
|
onFind
telemService.SwitchProvider() - activates the found provider
|
|-→ Create a background job while the stream hasn't initiated to listened
| for data, otherwise the connection might die before we start streaming
| ↓
| go telemService.ProviderMonitor()
↓ | |
/-→waits--\ if the provider stops we this healthcheck is stopped
\_________/ we clear the provider and once the stream starts
go back to the
FindProvider()
```
The provider discovery routine starts on `tui/tui.go`.
`FindProvider` is called here and it starts a background job.
On successful discovery the background job calls the `SwitchProvider` method
and dies.
## Provider stalls
A provider stalls once there is no new data.
After stalling
+7
View File
@@ -79,6 +79,13 @@ func NewBeamNGProvider(ip string, port int) (*BeamNG, error) {
return provider, nil
}
func (b *BeamNG) Close() {
}
func (b *BeamNG) IsAlive(timeout time.Duration) bool {
return true
}
func (b *BeamNG) StopStream() {
if b.streamCancel == nil {
return
-6
View File
@@ -31,9 +31,3 @@ func IsRunning() bool {
// Think of a better number or something
return n >= 80
}
// Stalled
// TODO: needs to be implemented
func (i *BeamNG) Stalled() bool {
return false
}
+18
View File
@@ -108,6 +108,13 @@ func NewIRacingProvider(
return provider, nil
}
func (i *IRacing) Close() {
// Need to find a way of gracefully closing the channel
// close(i.streamCh)
i.SDK.Close()
i.ticker.Stop()
}
func (i *IRacing) isDataAvailable() bool {
// Its offline telemetry, data must be available
if i.SDK.File == nil {
@@ -126,6 +133,8 @@ func (i *IRacing) stream(ctx context.Context) {
i.data.InitialTime = time.Now()
go func() {
defer close(i.streamCh)
// Put this into the configuration file
consecutiveTimeouts := 0
maxTimeouts := 30
@@ -140,6 +149,7 @@ func (i *IRacing) stream(ctx context.Context) {
if i.SDK.CheckForDataEvent(time.Duration(dataEvTimeout) * time.Millisecond) {
consecutiveTimeouts = 0
i.logger.Debug("sending data", "timeouts", consecutiveTimeouts)
i.readData()
// Publish data
@@ -253,3 +263,11 @@ func (i *IRacing) Subscribe(requestFields map[int16]telemetry.FieldID) {
i.logger.Debug(fmt.Sprintf("Subscribed: %+v\n", i.data.ActiveBinds))
}
func (i *IRacing) IsAlive(timeout time.Duration) bool {
if !i.SDK.CheckForDataEvent(timeout) {
return false
}
return true
}
-4
View File
@@ -31,7 +31,3 @@ func IsRunning() bool {
return true
}
func (i *IRacing) Stalled() bool {
return i.Stalled()
}
+53 -7
View File
@@ -25,8 +25,10 @@ type TelemetryService struct {
// Output window
Messages chan string
// Cancel looking for providers
CtxMonitor context.Context
cancelMonitor context.CancelFunc
CtxMonitor context.Context
cancelMonitor context.CancelFunc
CtxHealthcheck context.Context
healthCheckCancel context.CancelFunc
}
func NewTelemetryService(logger *slog.Logger, devServo *DeviceService) *TelemetryService {
@@ -43,15 +45,51 @@ func NewTelemetryService(logger *slog.Logger, devServo *DeviceService) *Telemetr
return newService
}
func (t *TelemetryService) OnFindProvider(prov providers.Provider) {
func (t *TelemetryService) ProviderMonitor(ctx context.Context) {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
slog.Debug("checking if provider is still running")
if !t.activeProvider.IsAlive(500 * time.Millisecond) {
slog.Debug("provider healthcheck failed")
t.dropActiveProvider()
t.onProviderHealthCheckFailed()
return
}
}
}
}
func (t *TelemetryService) onProviderHealthCheckFailed() {
// Just restart the whole lookup process
go t.FindProvider(t.CtxMonitor)
}
func (t *TelemetryService) onFindProvider(prov providers.Provider) {
// Attach to the provider
t.logger.Info("found provider for " + prov.Name)
t.SwitchProvider(prov.NewProvider(t.logger))
t.logger.Debug("Found provider for " + prov.Name)
// Create a routine to poll this provider while we wait to start the stream or pause it
t.CtxHealthcheck, t.healthCheckCancel = context.WithCancel(context.Background())
go t.ProviderMonitor(t.CtxHealthcheck)
}
func (t *TelemetryService) onProviderStopsMidStream() {
// clear the current provider
t.logger.Info("cleaning dropped provider and restarting lookup service")
t.dropActiveProvider()
go t.FindProvider(t.CtxMonitor)
}
// TODO: add some way of retriggering this. Currently it should:
// start monitoring on startup -> find provider -> stop monitoring (when game closes for example)
func (t *TelemetryService) FindProvider(ctx context.Context, callback func(providers.Provider),
) {
func (t *TelemetryService) FindProvider(ctx context.Context) {
ticker := time.NewTicker(2 * time.Second)
defer ticker.Stop()
@@ -64,7 +102,7 @@ func (t *TelemetryService) FindProvider(ctx context.Context, callback func(provi
for _, prov := range providers.Providers {
t.logger.Debug("checking provider: " + prov.Name)
if prov.IsRunning() {
callback(prov)
t.onFindProvider(prov)
return
}
t.logger.Debug(" wasn't read")
@@ -95,6 +133,8 @@ func (t *TelemetryService) multiplexData(ctx context.Context, dataCh <-chan tele
return
case data, ok := <-dataCh:
if !ok {
t.logger.Debug("something happened on the provider - stream closed")
t.onProviderStopsMidStream()
return
}
@@ -118,6 +158,8 @@ func (t *TelemetryService) dropActiveProvider() {
}
t.activeProvider.StopStream()
t.activeProvider.Close()
t.activeProvider = nil
}
func (t *TelemetryService) SubscribeListener(id string, bufferSize int) <-chan telem.TelemetryData {
@@ -160,6 +202,10 @@ func (t *TelemetryService) StartStream() {
slog.Debug("there's no active provider. not starting the stream")
return
}
// Stop the provider healthcheck
t.healthCheckCancel()
simInCh, _ := t.activeProvider.Stream()
// TODO: the provider needs to be able to tell the data has stopped
// so we can restart the provider lookup routine
+4 -1
View File
@@ -1,9 +1,12 @@
// Package telemetry is our interface with our data sources
package telemetry
import "time"
type TelemetryProvider interface {
StopStream()
Stream() (<-chan TelemetryData, error)
Subscribe(map[int16]FieldID)
Stalled() bool // Return true if no fresh data is coming
IsAlive(time.Duration) bool
Close()
}
+1 -1
View File
@@ -31,7 +31,7 @@ func NewControlPanel(logger *slog.Logger) *ControlPanel {
panic("failed to create the telemetry service")
}
go telemService.FindProvider(telemService.CtxMonitor, telemService.OnFindProvider)
go telemService.FindProvider(telemService.CtxMonitor)
return &ControlPanel{
Controller: baseController,