Mid stream peripheral connection #16
@@ -1,6 +1,13 @@
|
|||||||
package constants
|
package constants
|
||||||
|
|
||||||
|
// Provider Names
|
||||||
const (
|
const (
|
||||||
IRacingProviderName = "iRacing"
|
IRacingProviderName = "iRacing"
|
||||||
BeamNGProviderName = "BeamNG.drive"
|
BeamNGProviderName = "BeamNG.drive"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Virtual Field Names
|
||||||
|
const (
|
||||||
|
FuelCalculatorName = "FuelCalculator"
|
||||||
|
RPMLightsName = "RPMLights"
|
||||||
|
)
|
||||||
|
|||||||
+58
-32
@@ -28,6 +28,9 @@ type BeamNG struct {
|
|||||||
data *telemetry.TelemetryData
|
data *telemetry.TelemetryData
|
||||||
updaters [telemetry.MaxFields]func(*telemetry.TelemetryField)
|
updaters [telemetry.MaxFields]func(*telemetry.TelemetryField)
|
||||||
|
|
||||||
|
// Field Subscription management
|
||||||
|
boundFields map[telemetry.FieldID]bool
|
||||||
|
|
||||||
// stream control
|
// stream control
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
streamCancel context.CancelFunc
|
streamCancel context.CancelFunc
|
||||||
@@ -51,6 +54,8 @@ func NewBeamNGProvider(logger *slog.Logger, opts *bngsdk.Options) (*BeamNG, erro
|
|||||||
data: telemetry.NewTelemetryData(),
|
data: telemetry.NewTelemetryData(),
|
||||||
SDK: beam,
|
SDK: beam,
|
||||||
og: &bngsdk.Outgauge{},
|
og: &bngsdk.Outgauge{},
|
||||||
|
// Field subscription management
|
||||||
|
boundFields: make(map[telemetry.FieldID]bool, telemetry.MaxFields),
|
||||||
ticker: time.NewTicker(time.Second / 60),
|
ticker: time.NewTicker(time.Second / 60),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,47 +120,68 @@ func (b *BeamNG) Stream() (<-chan telemetry.TelemetryData, error) {
|
|||||||
return ch, nil
|
return ch, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *BeamNG) Subscribe(requestFields []telemetry.FieldID) {
|
// TODO: this function is exactly the same in BeamNG drive now, and I reckon it will be the same
|
||||||
// NOTE: document how the Subscribe funtion works
|
// In plenty other things. I should make it TelemetryData method
|
||||||
slog.Debug(fmt.Sprintf("Len Req: %d\n", len(requestFields)))
|
func (i *BeamNG) subscribe(fields []telemetry.FieldID) []string {
|
||||||
|
newSubscriptions := make([]string, 0, telemetry.MaxFields)
|
||||||
|
|
||||||
b.data.ActiveBinds = make([]telemetry.BoundField, 0, len(requestFields))
|
i.mut.Lock()
|
||||||
|
defer i.mut.Unlock()
|
||||||
|
|
||||||
// First we must add the virtual fields
|
for _, id := range fields {
|
||||||
// we will add their dependencies and the primitives to a slice
|
if i.boundFields[id] {
|
||||||
pendingBinds := make([]telemetry.FieldID, telemetry.MaxFields)
|
|
||||||
|
|
||||||
for _, id := range requestFields {
|
|
||||||
switch id {
|
|
||||||
case telemetry.RPMStateColour:
|
|
||||||
b.data.VirtualBinds = append(b.data.VirtualBinds, telemetry.NewRPMLights())
|
|
||||||
case telemetry.FCCurrentLap:
|
|
||||||
b.data.VirtualBinds = append(b.data.VirtualBinds,
|
|
||||||
telemetry.NewFuelCalculator(slog.Default().WithGroup("FUEL CALC")))
|
|
||||||
default:
|
|
||||||
// primitive telemetry field
|
|
||||||
pendingBinds = append(pendingBinds, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boundCheck := make(map[telemetry.FieldID]bool)
|
|
||||||
|
|
||||||
// Now that we know all the fields we need to bind we follow the binding procedure
|
|
||||||
for _, id := range pendingBinds {
|
|
||||||
// Check if we already bound this FieldID
|
|
||||||
if boundCheck[id] {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
binding := telemetry.BoundField{
|
i.data.ActiveBinds[id] = telemetry.BoundField{
|
||||||
ID: id,
|
ID: id,
|
||||||
}
|
}
|
||||||
|
i.boundFields[id] = true
|
||||||
b.data.ActiveBinds = append(b.data.ActiveBinds, binding)
|
newSubscriptions = append(newSubscriptions, telemetry.FieldNames[id])
|
||||||
boundCheck[id] = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
slog.Debug(fmt.Sprintf("Subscribed: %+v\n", b.data.ActiveBinds))
|
// unsubscribe from fields we many not need anymore
|
||||||
|
for key, bound := range i.boundFields {
|
||||||
|
if _, ok := i.data.ActiveBinds[key]; bound && !ok {
|
||||||
|
delete(i.data.ActiveBinds, key)
|
||||||
|
i.boundFields[key] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSubscriptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BeamNG) Subscribe(requestFields []telemetry.FieldID) []string {
|
||||||
|
// NOTE: document how the Subscribe funtion works
|
||||||
|
slog.Debug(fmt.Sprintf("Len Req: %d\n", len(requestFields)))
|
||||||
|
|
||||||
|
// First we must add the virtual fields
|
||||||
|
// we will add their dependencies and the primitives to a slice
|
||||||
|
toBind := make([]telemetry.FieldID, telemetry.MaxFields)
|
||||||
|
|
||||||
|
b.mut.Lock()
|
||||||
|
for _, id := range requestFields {
|
||||||
|
switch id {
|
||||||
|
case telemetry.RPMStateColour:
|
||||||
|
rpmLights := telemetry.NewRPMLights()
|
||||||
|
b.data.VirtualBinds[rpmLights.Name()] = rpmLights
|
||||||
|
toBind = append(toBind, rpmLights.EnsureSubscribed()...)
|
||||||
|
case telemetry.FCCurrentLap:
|
||||||
|
fuelCalc := telemetry.NewFuelCalculator(b.logger.WithGroup("FUEL CALC"))
|
||||||
|
b.data.VirtualBinds[fuelCalc.Name()] = fuelCalc
|
||||||
|
toBind = append(toBind, fuelCalc.EnsureSubscribed()...)
|
||||||
|
default:
|
||||||
|
// primitive telemetry field
|
||||||
|
toBind = append(toBind, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
b.mut.Unlock()
|
||||||
|
|
||||||
|
newSubs := b.subscribe(toBind)
|
||||||
|
|
||||||
|
slog.Debug(fmt.Sprintf("Subscribed: %+v\n", toBind))
|
||||||
|
|
||||||
|
return newSubs
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal
|
// Internal
|
||||||
|
|||||||
@@ -29,13 +29,14 @@ type IRacing struct {
|
|||||||
mut sync.Mutex
|
mut sync.Mutex
|
||||||
data *telemetry.TelemetryData
|
data *telemetry.TelemetryData
|
||||||
updaters [telemetry.MaxFields]func(*telemetry.TelemetryField)
|
updaters [telemetry.MaxFields]func(*telemetry.TelemetryField)
|
||||||
|
// Field Subscription management
|
||||||
|
boundFields map[telemetry.FieldID]bool
|
||||||
|
|
||||||
// Timing information
|
// Timing information
|
||||||
ticker *time.Ticker // ticker will keep polling intervals constant
|
ticker *time.Ticker // ticker will keep polling intervals constant
|
||||||
|
|
||||||
// Stream
|
// Stream
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
// streamCh chan telemetry.TelemetryData
|
|
||||||
streamCancel context.CancelFunc
|
streamCancel context.CancelFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,6 +56,8 @@ func NewIRacingProvider(
|
|||||||
logger: logger,
|
logger: logger,
|
||||||
SDK: sdk,
|
SDK: sdk,
|
||||||
data: telemetry.NewTelemetryData(),
|
data: telemetry.NewTelemetryData(),
|
||||||
|
// Field subscription management
|
||||||
|
boundFields: make(map[telemetry.FieldID]bool, telemetry.MaxFields),
|
||||||
// TODO: make this configurable from the user side
|
// TODO: make this configurable from the user side
|
||||||
ticker: time.NewTicker(time.Second / 60),
|
ticker: time.NewTicker(time.Second / 60),
|
||||||
}
|
}
|
||||||
@@ -225,46 +228,67 @@ func (i *IRacing) StopStream() {
|
|||||||
i.streamCancel = nil
|
i.streamCancel = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *IRacing) Subscribe(requestFields []telemetry.FieldID) {
|
// TODO: this function is exactly the same in BeamNG drive now, and I reckon it will be the same
|
||||||
i.logger.Debug(fmt.Sprintf("Len Req: %d\n", len(requestFields)))
|
// In plenty other things. I should make it TelemetryData method
|
||||||
|
func (i *IRacing) subscribe(fields []telemetry.FieldID) []string {
|
||||||
|
newSubscriptions := make([]string, 0, telemetry.MaxFields)
|
||||||
|
|
||||||
i.data.ActiveBinds = make([]telemetry.BoundField, 0, len(requestFields))
|
i.mut.Lock()
|
||||||
|
defer i.mut.Unlock()
|
||||||
|
|
||||||
// First we must add the virtual fields
|
for _, id := range fields {
|
||||||
// we will add their dependencies and the primitives to a slice
|
if i.boundFields[id] {
|
||||||
pendingBinds := make([]telemetry.FieldID, 0, telemetry.MaxFields)
|
|
||||||
|
|
||||||
for _, id := range requestFields {
|
|
||||||
switch id {
|
|
||||||
case telemetry.RPMStateColour:
|
|
||||||
i.data.VirtualBinds = append(i.data.VirtualBinds, telemetry.NewRPMLights())
|
|
||||||
case telemetry.FCCurrentLap:
|
|
||||||
i.data.VirtualBinds = append(i.data.VirtualBinds,
|
|
||||||
telemetry.NewFuelCalculator(i.logger.WithGroup("FUEL CALC")))
|
|
||||||
default:
|
|
||||||
// primitive telemetry field
|
|
||||||
pendingBinds = append(pendingBinds, id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boundCheck := make(map[telemetry.FieldID]bool)
|
|
||||||
|
|
||||||
// Now that we know all the fields we need to bind we follow the binding procedure
|
|
||||||
for _, id := range pendingBinds {
|
|
||||||
// Check if we already bound this FieldID
|
|
||||||
if boundCheck[id] {
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
binding := telemetry.BoundField{
|
i.data.ActiveBinds[id] = telemetry.BoundField{
|
||||||
ID: id,
|
ID: id,
|
||||||
}
|
}
|
||||||
|
i.boundFields[id] = true
|
||||||
i.data.ActiveBinds = append(i.data.ActiveBinds, binding)
|
newSubscriptions = append(newSubscriptions, telemetry.FieldNames[id])
|
||||||
boundCheck[id] = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// unsubscribe from fields we many not need anymore
|
||||||
|
for key, bound := range i.boundFields {
|
||||||
|
if _, ok := i.data.ActiveBinds[key]; bound && !ok {
|
||||||
|
delete(i.data.ActiveBinds, key)
|
||||||
|
i.boundFields[key] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return newSubscriptions
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IRacing) Subscribe(requestFields []telemetry.FieldID) []string {
|
||||||
|
i.logger.Debug(fmt.Sprintf("Len Req: %d\n", len(requestFields)))
|
||||||
|
|
||||||
|
// First we must add the virtual fields
|
||||||
|
// we will add their dependencies and the primitives to a slice
|
||||||
|
toBind := make([]telemetry.FieldID, 0, telemetry.MaxFields)
|
||||||
|
|
||||||
|
i.mut.Lock()
|
||||||
|
for _, id := range requestFields {
|
||||||
|
switch id {
|
||||||
|
case telemetry.RPMStateColour:
|
||||||
|
rpmLights := telemetry.NewRPMLights()
|
||||||
|
i.data.VirtualBinds[rpmLights.Name()] = rpmLights
|
||||||
|
toBind = append(toBind, rpmLights.EnsureSubscribed()...)
|
||||||
|
case telemetry.FCCurrentLap:
|
||||||
|
fuelCalc := telemetry.NewFuelCalculator(i.logger.WithGroup("FUEL CALC"))
|
||||||
|
i.data.VirtualBinds[fuelCalc.Name()] = fuelCalc
|
||||||
|
toBind = append(toBind, fuelCalc.EnsureSubscribed()...)
|
||||||
|
default:
|
||||||
|
// primitive telemetry field
|
||||||
|
toBind = append(toBind, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i.mut.Unlock()
|
||||||
|
|
||||||
|
newSubs := i.subscribe(toBind)
|
||||||
|
|
||||||
i.logger.Debug(fmt.Sprintf("Subscribed: %+v\n", i.data.ActiveBinds))
|
i.logger.Debug(fmt.Sprintf("Subscribed: %+v\n", i.data.ActiveBinds))
|
||||||
|
|
||||||
|
return newSubs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *IRacing) Name() string {
|
func (i *IRacing) Name() string {
|
||||||
|
|||||||
+4
-22
@@ -26,7 +26,7 @@ type DeviceService struct {
|
|||||||
// Callbacks
|
// Callbacks
|
||||||
// Telemetry service data fetchers
|
// Telemetry service data fetchers
|
||||||
telemetryProvider func() (string, error)
|
telemetryProvider func() (string, error)
|
||||||
triggerFieldSubscription func() []telemetry.FieldID
|
triggerFieldSubscription func([]telemetry.FieldID) []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDeviceService(logger *slog.Logger, msg chan string) *DeviceService {
|
func NewDeviceService(logger *slog.Logger, msg chan string) *DeviceService {
|
||||||
@@ -80,24 +80,6 @@ func (ds *DeviceService) PeripheralExists(pname string) bool {
|
|||||||
return err == nil
|
return err == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ds *DeviceService) GetRequiredFields() []telemetry.FieldID {
|
|
||||||
// NOTE: this can be optimized, not that it matters at this stage, but if
|
|
||||||
// it runs while telemetry is running we want it optimized I guess
|
|
||||||
seen := make(map[telemetry.FieldID]struct{})
|
|
||||||
var allFields []telemetry.FieldID
|
|
||||||
|
|
||||||
for _, dev := range ds.GetDevices() {
|
|
||||||
for _, field := range dev.RequiredFields() {
|
|
||||||
if _, exists := seen[field]; !exists {
|
|
||||||
seen[field] = struct{}{}
|
|
||||||
allFields = append(allFields, field)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return allFields
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getters [END] ---------------------------------------------------------------
|
// Getters [END] ---------------------------------------------------------------
|
||||||
|
|
||||||
// Actions [START] -------------------------------------------------------------
|
// Actions [START] -------------------------------------------------------------
|
||||||
@@ -168,10 +150,10 @@ func (ds *DeviceService) transmit(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Callbacks [START] -----------------------------------------------------------
|
// Callbacks [START] -----------------------------------------------------------
|
||||||
func (ds *DeviceService) peripheralConfigured(pname string) {
|
func (ds *DeviceService) peripheralConfigured(pname string, fields []telemetry.FieldID) {
|
||||||
// We need to retrigger field subscription here
|
// We need to retrigger field subscription here
|
||||||
fields := ds.triggerFieldSubscription()
|
subscribedTo := ds.triggerFieldSubscription(fields)
|
||||||
ds.Messages <- fmt.Sprintf("subscribed to fields: %+v\n", fields)
|
ds.Messages <- fmt.Sprintf("subscribed to fields: %q\n", subscribedTo)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Callbacks [END] -------------------------------------------------------------
|
// Callbacks [END] -------------------------------------------------------------
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"esdi/devices"
|
"esdi/devices"
|
||||||
"esdi/peripheral"
|
"esdi/peripheral"
|
||||||
|
"esdi/telemetry"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -86,7 +87,7 @@ type PeripheralStateStore struct {
|
|||||||
Messages chan string
|
Messages chan string
|
||||||
// Callbacks
|
// Callbacks
|
||||||
telemetryProvider func() (string, error)
|
telemetryProvider func() (string, error)
|
||||||
onPeripheralConfigured func(string)
|
onPeripheralConfigured func(string, []telemetry.FieldID)
|
||||||
// Internal State
|
// Internal State
|
||||||
isStreaming bool
|
isStreaming bool
|
||||||
}
|
}
|
||||||
@@ -184,6 +185,15 @@ func (pss *PeripheralStateStore) GetStreamingState() bool {
|
|||||||
return pss.isStreaming
|
return pss.isStreaming
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pss *PeripheralStateStore) GetPeripheralFields(pname string) []telemetry.FieldID {
|
||||||
|
per, err := pss.GetState(pname)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return per.Peripheral.RequiredFields()
|
||||||
|
}
|
||||||
|
|
||||||
// "Events" [START] ------------------------------------------------------------
|
// "Events" [START] ------------------------------------------------------------
|
||||||
func (ds *DeviceService) onDeviceTimedOut(pname string) {
|
func (ds *DeviceService) onDeviceTimedOut(pname string) {
|
||||||
ds.PSS.setDeviceTimedOut(pname)
|
ds.PSS.setDeviceTimedOut(pname)
|
||||||
@@ -280,7 +290,7 @@ func (pss *PeripheralStateStore) setDeviceConfigured(pname string) {
|
|||||||
pss.store[pname].State = DeviceIsConfigured
|
pss.store[pname].State = DeviceIsConfigured
|
||||||
pss.mu.Unlock()
|
pss.mu.Unlock()
|
||||||
|
|
||||||
pss.onPeripheralConfigured(pname)
|
pss.onPeripheralConfigured(pname, pss.GetPeripheralFields(pname))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pss *PeripheralStateStore) setDeviceIsStreaming(pname string) {
|
func (pss *PeripheralStateStore) setDeviceIsStreaming(pname string) {
|
||||||
@@ -347,7 +357,6 @@ func (pss *PeripheralStateStore) configurePeripheral(
|
|||||||
}
|
}
|
||||||
|
|
||||||
onSuccess(pname)
|
onSuccess(pname)
|
||||||
pss.setDeviceConfigured(pname)
|
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,10 @@ func NewOrchestrator(logger *slog.Logger) (*Orchestrator, error) {
|
|||||||
|
|
||||||
// Setup device service callbacks
|
// Setup device service callbacks
|
||||||
devService.telemetryProvider = telemService.GetTelemetryProviderName
|
devService.telemetryProvider = telemService.GetTelemetryProviderName
|
||||||
devService.triggerFieldSubscription = telemService.SubscribeToAllFields
|
devService.triggerFieldSubscription = telemService.SubscribeToFields
|
||||||
|
|
||||||
// Setup telemetry service callbacks
|
// Setup telemetry service callbacks
|
||||||
telemService.getRequiredFields = devService.GetRequiredFields
|
// telemService.getRequiredFields = devService.GetRequiredFields
|
||||||
|
|
||||||
return &Orchestrator{
|
return &Orchestrator{
|
||||||
DeviceService: devService,
|
DeviceService: devService,
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ type TelemetryService struct {
|
|||||||
// Callbacks
|
// Callbacks
|
||||||
// Devices data request
|
// Devices data request
|
||||||
// peripheralProvider func() []peripheral.Peripheral
|
// peripheralProvider func() []peripheral.Peripheral
|
||||||
getRequiredFields func() []telemetry.FieldID
|
// getRequiredFields func() []telemetry.FieldID
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTelemetryService(
|
func NewTelemetryService(
|
||||||
@@ -114,13 +114,11 @@ func (t *TelemetryService) UnsubscribeListener(id string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *TelemetryService) SubscribeToAllFields() []telem.FieldID {
|
func (t *TelemetryService) SubscribeToFields(fields []telemetry.FieldID) []string {
|
||||||
fields := t.getRequiredFields()
|
|
||||||
|
|
||||||
t.logger.Debug("requested fields", "fields", fields)
|
t.logger.Debug("requested fields", "fields", fields)
|
||||||
t.activeProvider.Subscribe(fields)
|
subscribed := t.activeProvider.Subscribe(fields)
|
||||||
|
|
||||||
return fields
|
return subscribed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listener Control [END] ------------------------------------------------------
|
// Listener Control [END] ------------------------------------------------------
|
||||||
|
|||||||
+6
-11
@@ -23,13 +23,6 @@ type FieldMapper struct {
|
|||||||
Transform func(any) uint64
|
Transform func(any) uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// VirtualField will derive data from telemetry primitives
|
|
||||||
// So fuel per lap predictions, compound gauge lights and so on
|
|
||||||
type VirtualField interface {
|
|
||||||
Process(td *TelemetryData)
|
|
||||||
EnsureSubscribed() []FieldID
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: Update the iracing SDK to write data to the same map ALWAYS, then
|
// NOTE: Update the iracing SDK to write data to the same map ALWAYS, then
|
||||||
// I can bind that address and read directly from there on the transform
|
// I can bind that address and read directly from there on the transform
|
||||||
|
|
||||||
@@ -228,16 +221,18 @@ func GetFieldID(name string) (FieldID, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TelemetryData is
|
// TelemetryData is
|
||||||
// I need to find a way of having the values be per window or some other
|
|
||||||
type TelemetryData struct {
|
type TelemetryData struct {
|
||||||
Values [MaxFields]TelemetryField
|
Values [MaxFields]TelemetryField
|
||||||
ActiveBinds []BoundField
|
ActiveBinds map[FieldID]BoundField
|
||||||
VirtualBinds []VirtualField
|
VirtualBinds map[string]VirtualField
|
||||||
InitialTime time.Time
|
InitialTime time.Time
|
||||||
PenultimateDataPoll time.Time
|
PenultimateDataPoll time.Time
|
||||||
LastDataPoll time.Time
|
LastDataPoll time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTelemetryData() *TelemetryData {
|
func NewTelemetryData() *TelemetryData {
|
||||||
return &TelemetryData{}
|
return &TelemetryData{
|
||||||
|
ActiveBinds: make(map[FieldID]BoundField, MaxFields),
|
||||||
|
VirtualBinds: make(map[string]VirtualField),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"esdi/constants"
|
||||||
)
|
)
|
||||||
|
|
||||||
// NOTE: for managing fuel consumption and predictions we need to filter out
|
// NOTE: for managing fuel consumption and predictions we need to filter out
|
||||||
@@ -264,3 +266,7 @@ func (fc *FuelCalculator) resetHistory() {
|
|||||||
func (fc *FuelCalculator) EnsureSubscribed() []FieldID {
|
func (fc *FuelCalculator) EnsureSubscribed() []FieldID {
|
||||||
return []FieldID{FuelLevel, LapNumber}
|
return []FieldID{FuelLevel, LapNumber}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fc *FuelCalculator) Name() string {
|
||||||
|
return constants.FuelCalculatorName
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import "time"
|
|||||||
type TelemetryProvider interface {
|
type TelemetryProvider interface {
|
||||||
StopStream()
|
StopStream()
|
||||||
Stream() (<-chan TelemetryData, error)
|
Stream() (<-chan TelemetryData, error)
|
||||||
Subscribe([]FieldID)
|
Subscribe([]FieldID) []string
|
||||||
IsAlive(time.Duration) bool
|
IsAlive(time.Duration) bool
|
||||||
Name() string
|
Name() string
|
||||||
Close()
|
Close()
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package telemetry
|
package telemetry
|
||||||
|
|
||||||
|
import "esdi/constants"
|
||||||
|
|
||||||
type RPMLights struct {
|
type RPMLights struct {
|
||||||
State string
|
State string
|
||||||
}
|
}
|
||||||
@@ -28,3 +30,7 @@ func (rl *RPMLights) Process(td *TelemetryData) {
|
|||||||
func (rl *RPMLights) EnsureSubscribed() []FieldID {
|
func (rl *RPMLights) EnsureSubscribed() []FieldID {
|
||||||
return []FieldID{RPM}
|
return []FieldID{RPM}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rl *RPMLights) Name() string {
|
||||||
|
return constants.RPMLightsName
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package telemetry
|
||||||
|
|
||||||
|
// VirtualField will derive data from telemetry primitives
|
||||||
|
// So fuel per lap predictions, compound gauge lights and so on
|
||||||
|
type VirtualField interface {
|
||||||
|
Name() string
|
||||||
|
Process(td *TelemetryData)
|
||||||
|
EnsureSubscribed() []FieldID
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user