Author SHA1 Message Date
esilva 65f03fd4c9 this is a doozy - device auto discovery and handling
The main goal was to decouple more things
We got devices being looked for in the background and the stream view
is like a device now too
2026-09-17 22:55:17 +01:00
esilva 20025dae1b updated beamng provider for the new SDK 2026-09-17 11:33:10 +01:00
esilva 03d0f8aefc added the IsAlive check for beamng 2026-09-16 19:02:20 +01:00
esilva df99994a75 fixed some little UI bugs
- We could open the CDashDisplay specific layout before it existed -
Crash!
- We could open the stream UI before there was a provider- Crash!
2026-09-13 23:05:55 +01:00
esilva 80651c21cf removing some unecessary logs 2026-09-13 22:53:51 +01:00
esilva 528c62288a 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
2026-09-12 23:28:15 +01:00
esilva 196e1d8916 remove this very verbose log 2026-09-12 18:27:09 +01:00
esilva decd91c406 remove the fatals from here, but I do need to do error handling there 2026-09-12 18:26:58 +01:00
esilva b8e632d3eb Updated the function that checks for the sim running and how we stream data 2026-09-12 15:35:01 +01:00
esilva d8589394f6 fixed the iracing Gear transform 2026-09-11 14:40:16 +01:00
esilva 76f2e265d7 Don't allow program to crash if there's no active provider on StartStream action 2026-09-11 12:48:36 +01:00
esilva 7c03088470 some fixes regarding iracing provider transforms and working the auto detection 2026-09-10 15:12:12 +01:00
esilva 0006b649f3 update the iracing provider to be on the same page as the BeamNG one
Deprecated the Fetch and Transform methods
2026-09-10 15:11:37 +01:00
esilva 3493a73a82 basic loop to discover providers 2026-08-25 22:01:34 +01:00
esilva f4bde52f40 Created a devices service and moved the CDashDisplay service to the sahdow realm 2026-08-25 14:46:20 +01:00
18 changed files with 206 additions and 192 deletions
+4 -1
View File
@@ -1,17 +1,20 @@
package cmd package cmd
import ( import (
"esdi/logger"
esdi "esdi/oldEsdi" esdi "esdi/oldEsdi"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
func liveTelemetryCmdAction(cmd *cobra.Command, args []string) { func liveTelemetryCmdAction(cmd *cobra.Command, args []string) {
log := logger.GetInstance()
ddPort, _ := cmd.Flags().GetString("port") ddPort, _ := cmd.Flags().GetString("port")
outputFile, _ := cmd.Flags().GetString("out") outputFile, _ := cmd.Flags().GetString("out")
sessionFile, _ := cmd.Flags().GetString("session") sessionFile, _ := cmd.Flags().GetString("session")
// log.Printf("Called `live`:\nPort: '%s'\nOutFile: '%s'\n", ddPort, outputFile) log.Printf("Called `live`:\nPort: '%s'\nOutFile: '%s'\n", ddPort, outputFile)
esdi.RunLiveTelemetry(ddPort, outputFile, sessionFile) esdi.RunLiveTelemetry(ddPort, outputFile, sessionFile)
} }
+3 -2
View File
@@ -1,6 +1,7 @@
package cmd package cmd
import ( import (
"esdi/logger"
esdi "esdi/oldEsdi" esdi "esdi/oldEsdi"
// "github.com/ESilva15/goirsdk" // "github.com/ESilva15/goirsdk"
@@ -9,14 +10,14 @@ import (
) )
func offlineTelemetryCmdAction(cmd *cobra.Command, args []string) { func offlineTelemetryCmdAction(cmd *cobra.Command, args []string) {
// log := logger.GetInstance() log := logger.GetInstance()
ddPort, _ := cmd.Flags().GetString("port") ddPort, _ := cmd.Flags().GetString("port")
inFile, _ := cmd.Flags().GetString("in") inFile, _ := cmd.Flags().GetString("in")
outFile, _ := cmd.Flags().GetString("out") outFile, _ := cmd.Flags().GetString("out")
sessionFile, _ := cmd.Flags().GetString("session") sessionFile, _ := cmd.Flags().GetString("session")
// log.Printf("Called `offline`:\nPort: '%s'\nSource: '%s'\nOutFile: '%s'\n", ddPort, inFile, outFile) log.Printf("Called `offline`:\nPort: '%s'\nSource: '%s'\nOutFile: '%s'\n", ddPort, inFile, outFile)
esdi.RunOfflineTelemetry(ddPort, inFile, outFile, sessionFile) esdi.RunOfflineTelemetry(ddPort, inFile, outFile, sessionFile)
} }
+9 -11
View File
@@ -1,12 +1,10 @@
package cdashdisplay package cdashdisplay
import ( import (
"fmt"
"log/slog"
"time"
"esdi/peripheral/communication" "esdi/peripheral/communication"
"esdi/peripheral/communication/packets" "esdi/peripheral/communication/packets"
"fmt"
"time"
"github.com/tarm/serial" "github.com/tarm/serial"
portp "go.bug.st/serial" portp "go.bug.st/serial"
@@ -48,11 +46,11 @@ func findDisplayPort() (*communication.WalkieTalkie, error) {
return nil, err return nil, err
} }
slog.Info(fmt.Sprintf("Looking into %v", ports)) pLogger.Info(fmt.Sprintf("Looking into %v", ports))
var wt *communication.WalkieTalkie var wt *communication.WalkieTalkie
for _, port := range ports { for _, port := range ports {
slog.Info(fmt.Sprintf("Trying port %s", port)) pLogger.Info(fmt.Sprintf("Trying port %s", port))
wt = &communication.WalkieTalkie{ wt = &communication.WalkieTalkie{
Cfg: &serial.Config{ Cfg: &serial.Config{
@@ -62,7 +60,7 @@ func findDisplayPort() (*communication.WalkieTalkie, error) {
}, },
} }
slog.Info(fmt.Sprintf("Started probing port %s", port)) pLogger.Info(fmt.Sprintf("Started probing port %s", port))
probeResult := make(chan error, 1) probeResult := make(chan error, 1)
@@ -78,14 +76,14 @@ func findDisplayPort() (*communication.WalkieTalkie, error) {
err = fmt.Errorf("probe completely hung/timed out: %s", port) err = fmt.Errorf("probe completely hung/timed out: %s", port)
} }
slog.Info(fmt.Sprintf("Finished probing port %s", port)) pLogger.Info(fmt.Sprintf("Finished probing port %s", port))
if err == nil { if err == nil {
slog.Info(fmt.Sprintf("Success probing port %s: %+v", port, err)) pLogger.Info(fmt.Sprintf("Success probing port %s: %+v", port, err))
break break
} }
slog.Info(fmt.Sprintf("wasn't port %s", port)) pLogger.Info(fmt.Sprintf("wasn't port %s", port))
wt = nil wt = nil
} }
@@ -93,6 +91,6 @@ func findDisplayPort() (*communication.WalkieTalkie, error) {
return nil, fmt.Errorf("couldn't find cdashdisplay") return nil, fmt.Errorf("couldn't find cdashdisplay")
} }
slog.Info(fmt.Sprintf("found cdashdisplay on port: %s", wt.Cfg.Name)) pLogger.Info(fmt.Sprintf("found cdashdisplay on port: %s", wt.Cfg.Name))
return wt, nil return wt, nil
} }
+21 -51
View File
@@ -8,7 +8,6 @@ import (
"log/slog" "log/slog"
"os" "os"
"path" "path"
"sync"
"time" "time"
helper "esdi/helpers" helper "esdi/helpers"
@@ -20,6 +19,13 @@ import (
"gopkg.in/yaml.v3" "gopkg.in/yaml.v3"
) )
// TODO: remove this logger. It should be on the device struct
var pLogger *slog.Logger
func SetLogger(l *slog.Logger) {
pLogger = l
}
// I have to move this to some kind of configuration place // I have to move this to some kind of configuration place
const ( const (
layoutsDir = "./layouts/" layoutsDir = "./layouts/"
@@ -109,56 +115,26 @@ func NewCDashState() *CDashState {
type CDashDisplay struct { type CDashDisplay struct {
WT *communication.WalkieTalkie WT *communication.WalkieTalkie
State *CDashState State *CDashState
fieldToWindows map[telemetry.FieldID][]int16
bufPool sync.Pool
} }
// Connect will try to find and connect to the CDashDisplay // Connect will try to find and connect to the CDashDisplay
func NewCDashDisplay() (*CDashDisplay, error) { func Discover() (*CDashDisplay, error) {
// Look for the port // Look for the port
p, err := findDisplayPort() p, err := findDisplayPort()
if err != nil { if err != nil {
slog.Info("failed to find cdashdisplay port: %s", err.Error()) pLogger.Info("failed to find cdashdisplay port: %s", err.Error())
return nil, err return nil, err
} }
return &CDashDisplay{ return &CDashDisplay{
WT: p, WT: p,
State: NewCDashState(), State: NewCDashState(),
fieldToWindows: make(map[telemetry.FieldID][]int16),
bufPool: sync.Pool{
New: func() any {
b := make([]byte, 0, telemetry.MaxFields*8)
return &b
},
},
}, nil }, nil
} }
func (d *CDashDisplay) SendCommand() { func (d *CDashDisplay) SendCommand() {
} }
func (d *CDashDisplay) RegisterFieldMapping(fieldID telemetry.FieldID, winID int16) {
d.fieldToWindows[fieldID] = append(d.fieldToWindows[fieldID], winID)
}
func (d *CDashDisplay) UnregisterFieldMapping(winID int16) {
for fieldID, windows := range d.fieldToWindows {
updated := windows[:0]
for _, w := range windows {
if w != winID {
updated = append(updated, w)
}
if len(updated) == 0 {
delete(d.fieldToWindows, fieldID)
} else {
d.fieldToWindows[fieldID] = updated
}
}
}
}
func (d *CDashDisplay) CreateWindow(win *DesktopUIWindow) (*DesktopUIWindow, error) { func (d *CDashDisplay) CreateWindow(win *DesktopUIWindow) (*DesktopUIWindow, error) {
bytes, err := helper.StructToBytes(win.UIWindow) bytes, err := helper.StructToBytes(win.UIWindow)
if err != nil { if err != nil {
@@ -174,12 +150,9 @@ func (d *CDashDisplay) CreateWindow(win *DesktopUIWindow) (*DesktopUIWindow, err
win.UIData.IDX = wID.ID win.UIData.IDX = wID.ID
slog.Info(fmt.Sprintf("Recived ID message: %v", wID)) pLogger.Info(fmt.Sprintf("Recived ID message: %v", wID))
d.State.Layout.AddWindow(win) d.State.Layout.AddWindow(win)
if fieldID, ok := telemetry.GetFieldID(win.UIData.TelemetryField); ok {
d.RegisterFieldMapping(fieldID, win.UIData.IDX)
}
return win, nil return win, nil
} }
@@ -205,14 +178,12 @@ func (d *CDashDisplay) UpdateWindow(win *DesktopUIWindow) error {
// I get it and update it in the controller // I get it and update it in the controller
// I send the pointer here // I send the pointer here
// -> it should be the same pointer then right? // -> it should be the same pointer then right?
slog.Debug(fmt.Sprintf("PreUpdate ID: %p", win)) pLogger.Debug(fmt.Sprintf("PreUpdate ID: %p", win))
d.State.Layout.Windows[win.UIData.IDX] = win d.State.Layout.Windows[win.UIData.IDX] = win
slog.Debug(fmt.Sprintf("PostUpdate ID: %p", win)) pLogger.Debug(fmt.Sprintf("PostUpdate ID: %p", win))
// Yeah, same address as suspected // Yeah, same address as suspected
// I can't think about it right now. I'll think about that tomorrow // I can't think about it right now. I'll think about that tomorrow
// TODO: need to update the field mappings here!
return nil return nil
} }
@@ -235,15 +206,14 @@ func (d *CDashDisplay) DestroyWindow(wID int16) error {
return err return err
} }
// NOTE: add this // NODE: add this
d.UnregisterFieldMapping(wID)
d.State.Layout.RemoveWindow(wID) d.State.Layout.RemoveWindow(wID)
return nil return nil
} }
func (d *CDashDisplay) updateWindowDimensions(win *UIWindow, packet UpdateDimsPacket) error { func (d *CDashDisplay) updateWindowDimensions(win *UIWindow, packet UpdateDimsPacket) error {
slog.Debug(fmt.Sprintf("UPDATE: %v", packet)) pLogger.Debug(fmt.Sprintf("UPDATE: %v", packet))
bytes, err := helper.StructToBytes(packet) bytes, err := helper.StructToBytes(packet)
if err != nil { if err != nil {
@@ -257,9 +227,9 @@ func (d *CDashDisplay) updateWindowDimensions(win *UIWindow, packet UpdateDimsPa
} }
// Nothing bad happened afaik // Nothing bad happened afaik
slog.Debug(fmt.Sprintf("cur dims: %v", win.Dims)) pLogger.Debug(fmt.Sprintf("cur dims: %v", win.Dims))
win.Dims = packet.Dims win.Dims = packet.Dims
slog.Debug(fmt.Sprintf("new dims: %v", win.Dims)) pLogger.Debug(fmt.Sprintf("new dims: %v", win.Dims))
return nil return nil
} }
@@ -375,18 +345,18 @@ func (d *CDashDisplay) LoadLayout(layoutName string) error {
func (d *CDashDisplay) UnloadLayout() error { func (d *CDashDisplay) UnloadLayout() error {
var err error var err error
for _, w := range d.State.Layout.Windows { for _, w := range d.State.Layout.Windows {
slog.Debug(fmt.Sprintf("= Removing %d ==============================================", pLogger.Debug(fmt.Sprintf("= Removing %d ==============================================",
w.UIData.IDX)) w.UIData.IDX))
err = d.DestroyWindow(w.UIData.IDX) err = d.DestroyWindow(w.UIData.IDX)
time.Sleep(75 * time.Millisecond) time.Sleep(75 * time.Millisecond)
if err != nil { if err != nil {
slog.Error(fmt.Sprintf("failed to destroy window: %+v", err)) pLogger.Error(fmt.Sprintf("failed to destroy window: %+v", err))
// NOTE: Add a way to handle multiple errors ? // NOTE: Add a way to handle multiple errors ?
return err return err
} }
slog.Debug(fmt.Sprintf("= Removing %d ==============================================", pLogger.Debug(fmt.Sprintf("= Removing %d ==============================================",
w.UIData.IDX)) w.UIData.IDX))
} }
@@ -394,7 +364,7 @@ func (d *CDashDisplay) UnloadLayout() error {
} }
func (d *CDashDisplay) SendData(data *telemetry.TelemetryData) { func (d *CDashDisplay) SendData(data *telemetry.TelemetryData) {
packet := d.encodePacket(data) packet := data.Pack()
bytes, err := helper.StructToBytes(packet) bytes, err := helper.StructToBytes(packet)
if err != nil { if err != nil {
@@ -408,7 +378,7 @@ func (d *CDashDisplay) SendData(data *telemetry.TelemetryData) {
curStr += fmt.Sprintf("%02x ", byte) curStr += fmt.Sprintf("%02x ", byte)
if byteCount == 8 { if byteCount == 8 {
// slog.Debug(curStr) // pLogger.Debug(curStr)
curStr = "" curStr = ""
byteCount = 0 byteCount = 0
} }
+4 -5
View File
@@ -15,13 +15,12 @@ func (cds *CDashDisplay) Name() string {
return NAME return NAME
} }
func (cds *CDashDisplay) RequiredFields() []telemetry.FieldID { func (cds *CDashDisplay) RequiredFields() map[int16]telemetry.FieldID {
fields := make([]telemetry.FieldID, 0, len(cds.State.Layout.Windows)) fields := make(map[int16]telemetry.FieldID, len(cds.State.Layout.Windows))
for _, w := range cds.State.Layout.Windows { for _, w := range cds.State.Layout.Windows {
if fieldID, ok := telemetry.GetFieldID(w.UIData.TelemetryField); ok { fieldID, _ := telemetry.GetFieldID(w.UIData.TelemetryField)
fields = append(fields, fieldID) fields[w.UIData.IDX] = fieldID
}
} }
return fields return fields
+5 -8
View File
@@ -1,9 +1,6 @@
package cdashdisplay package cdashdisplay
import ( import "fmt"
"fmt"
"log/slog"
)
type LayoutTree struct { type LayoutTree struct {
Windows map[int16]*DesktopUIWindow `yaml:"Windows"` Windows map[int16]*DesktopUIWindow `yaml:"Windows"`
@@ -16,13 +13,13 @@ func NewLayoutTree() *LayoutTree {
} }
func (l *LayoutTree) AddWindow(w *DesktopUIWindow) { func (l *LayoutTree) AddWindow(w *DesktopUIWindow) {
slog.Debug(fmt.Sprintf("adding window '%d' - %v", w.UIData.IDX)) pLogger.Debug(fmt.Sprintf("adding window '%d' - %v", w.UIData.IDX))
l.Windows[w.UIData.IDX] = w l.Windows[w.UIData.IDX] = w
slog.Debug(fmt.Sprintf("new map - %v", l.Windows)) pLogger.Debug(fmt.Sprintf("new map - %v", l.Windows))
} }
func (l *LayoutTree) RemoveWindow(idx int16) { func (l *LayoutTree) RemoveWindow(idx int16) {
slog.Debug(fmt.Sprintf("removing window '%d'", idx)) pLogger.Debug(fmt.Sprintf("removing window '%d'", idx))
delete(l.Windows, idx) delete(l.Windows, idx)
slog.Debug(fmt.Sprintf("new map - %v", l.Windows)) pLogger.Debug(fmt.Sprintf("new map - %v", l.Windows))
} }
+3 -74
View File
@@ -1,11 +1,5 @@
package cdashdisplay package cdashdisplay
import (
"math"
"esdi/telemetry"
)
// In this file we will place all structs that are 1:1 representation of the // In this file we will place all structs that are 1:1 representation of the
// types in the device transport layer -> // types in the device transport layer ->
@@ -71,71 +65,6 @@ type UIWindowUpdatePacket struct {
Window UIWindow Window UIWindow
} }
// TODO: this is here because this is the only device I use like this // func getUIWindowDTO(w *DesktopWindowData) *UIWindow {
// once I add another serial device I will move this somewhere else that can //
// be reused by all devices but still is decoupled from the telemetry package // }
func (cds *CDashDisplay) encodePacket(td *telemetry.TelemetryData) []byte {
bufPtr := cds.bufPool.Get().(*[]byte)
buf := (*bufPtr)[:0]
for fieldID, windowIDs := range cds.fieldToWindows {
if int(fieldID) >= len(td.Values) || len(windowIDs) == 0 {
continue
}
tf := &td.Values[fieldID]
for _, winID := range windowIDs {
buf = packField(winID, tf, buf)
}
}
// We have to copy here because we have to return the buffer
result := make([]byte, len(buf))
copy(result, buf)
cds.bufPool.Put(&buf)
return result
}
// Pack will pack this current TelemetryField into bytes to send over the wire
// Format:
// 0x00 - Field ID
// 0x00 |
// 0x01 - DataType
// 0x02 - if its a (u)int8
// or
// 0x02 - if its a (u)int16 - first byte
// 0x02 - if its a (u)int16 - second byte
// or
// 0x02 - str len max is 255 chars
// [0x02] - str
func packField(winID int16, tf *telemetry.TelemetryField, dest []byte) []byte {
// NOTE: maybe we can have a pool of these so we don't have to create them here
// or whatever
dest = append(dest, uint8(winID), uint8(winID>>8))
dest = append(dest, uint8(tf.Type))
switch tf.Type {
case telemetry.DataTypeINT8, telemetry.DataTypeUINT8, telemetry.DataTypeCHAR:
dest = append(dest, uint8(tf.Raw))
case telemetry.DataTypeINT16, telemetry.DataTypeUINT16:
dest = append(dest, uint8(tf.Raw), uint8(tf.Raw>>8))
case telemetry.DataTypeINT32, telemetry.DataTypeUINT32:
dest = append(dest, uint8(tf.Raw), uint8(tf.Raw>>8), uint8(tf.Raw>>16), uint8(tf.Raw>>24))
case telemetry.DataTypeINT64, telemetry.DataTypeUINT64:
dest = append(
dest, uint8(tf.Raw), uint8(tf.Raw>>8), uint8(tf.Raw>>16),
uint8(tf.Raw>>24), uint8(tf.Raw>>32), uint8(tf.Raw>>40), uint8(tf.Raw>>48),
uint8(tf.Raw>>56),
)
case telemetry.DataTypeSTRING:
l := min(len(tf.Str), math.MaxUint8)
dest = append(dest, uint8(l))
dest = append(dest, tf.Str[:l]...)
}
return dest
}
+24 -7
View File
@@ -2,6 +2,8 @@
package devices package devices
import ( import (
"errors"
"esdi/devices/cdashdisplay" "esdi/devices/cdashdisplay"
"esdi/devices/uidevice" "esdi/devices/uidevice"
"esdi/peripheral" "esdi/peripheral"
@@ -33,11 +35,26 @@ func DiscoverUIDevice() (peripheral.Peripheral, error) {
} }
func DiscoverCDashDisplay() (peripheral.Peripheral, error) { func DiscoverCDashDisplay() (peripheral.Peripheral, error) {
// Create a cdashdisplay // // Find CDashDisplay
display, err := cdashdisplay.NewCDashDisplay() // {
if err != nil { // ds.Messages <- "looking for " + cdashdisplay.Name + "...\n"
return nil, err // ds.Logger.Info("Looking for " + cdashdisplay.Name)
} //
// cdashdisplay.SetLogger(ds.Logger.With("[device]", cdashdisplay.Name))
return display, nil //
// // Create a cdashdisplay
// display, err := cdashdisplay.Discover()
// if err == nil {
// ds.Devices[cdashdisplay.Name] = display
// ds.Logger.Info("found " + cdashdisplay.Name + " on: " + display.WT.Cfg.Name)
// ds.Messages <- "found " + cdashdisplay.Name + " on: " + display.WT.Cfg.Name + "\n"
// return
// }
//
// ds.Logger.Info("didn't find " + cdashdisplay.Name)
// ds.Messages <- "didn't find " + cdashdisplay.Name + "\n"
// // No CDashDisplay available for one reason or another, so we don't set the
// // key
// }
return nil, errors.New("not implemented yet")
} }
+9 -2
View File
@@ -37,10 +37,17 @@ func (uid *UIDevice) DataChannel() <-chan telemetry.TelemetryData {
return uid.dataChan return uid.dataChan
} }
func (uid *UIDevice) RequiredFields() []telemetry.FieldID { func (uid *UIDevice) RequiredFields() map[int16]telemetry.FieldID {
return []telemetry.FieldID{ subscribeTo := []telemetry.FieldID{
telemetry.Speed, telemetry.Speed,
telemetry.Gear, telemetry.Gear,
telemetry.RPM, telemetry.RPM,
} }
fields := make(map[int16]telemetry.FieldID, len(subscribeTo))
for k, field := range subscribeTo {
fields[int16(k)] = field
}
return fields
} }
+22
View File
@@ -0,0 +1,22 @@
package logger
import (
"log"
"os"
"sync"
)
var l *log.Logger
var once sync.Once
func createLogger() {
l = log.New(os.Stdout, "[esdi] ", log.LstdFlags | log.Lshortfile)
}
func GetInstance() *log.Logger {
once.Do(func() {
createLogger()
})
return l
}
+4
View File
@@ -10,6 +10,7 @@ import (
"esdi/cmd" "esdi/cmd"
"esdi/config" "esdi/config"
"esdi/telemetry"
"github.com/arl/statsviz" "github.com/arl/statsviz"
) )
@@ -41,6 +42,9 @@ func initApplication() {
slog.Error(fmt.Sprintf("failed to setup metrics server: %+v", err)) slog.Error(fmt.Sprintf("failed to setup metrics server: %+v", err))
} }
} }
// Setting up some internal data structures
telemetry.Init()
} }
func setupLogger() error { func setupLogger() error {
+1 -1
View File
@@ -18,7 +18,7 @@ const (
type Peripheral interface { type Peripheral interface {
Name() string Name() string
SendData(*telemetry.TelemetryData) SendData(*telemetry.TelemetryData)
RequiredFields() []telemetry.FieldID RequiredFields() map[int16]telemetry.FieldID
} }
type PeripheralDeviceClerk struct { type PeripheralDeviceClerk struct {
+4 -2
View File
@@ -115,7 +115,7 @@ func (b *BeamNG) Stream() (<-chan telemetry.TelemetryData, error) {
return b.streamCh, nil return b.streamCh, nil
} }
func (b *BeamNG) Subscribe(requestFields []telemetry.FieldID) { func (b *BeamNG) Subscribe(requestFields map[int16]telemetry.FieldID) {
// NOTE: document how the Subscribe funtion works // NOTE: document how the Subscribe funtion works
slog.Debug(fmt.Sprintf("Len Req: %d\n", len(requestFields))) slog.Debug(fmt.Sprintf("Len Req: %d\n", len(requestFields)))
@@ -125,7 +125,9 @@ func (b *BeamNG) Subscribe(requestFields []telemetry.FieldID) {
// we will add their dependencies and the primitives to a slice // we will add their dependencies and the primitives to a slice
pendingBinds := make([]telemetry.FieldID, telemetry.MaxFields) pendingBinds := make([]telemetry.FieldID, telemetry.MaxFields)
for _, id := range requestFields { for winID, id := range requestFields {
b.data.Values[id].IDs = append(b.data.Values[id].IDs, winID)
switch id { switch id {
case telemetry.RPMStateColour: case telemetry.RPMStateColour:
b.data.VirtualBinds = append(b.data.VirtualBinds, telemetry.NewRPMLights()) b.data.VirtualBinds = append(b.data.VirtualBinds, telemetry.NewRPMLights())
+4 -2
View File
@@ -220,7 +220,7 @@ func (i *IRacing) StopStream() {
i.streamCancel = nil i.streamCancel = nil
} }
func (i *IRacing) Subscribe(requestFields []telemetry.FieldID) { func (i *IRacing) Subscribe(requestFields map[int16]telemetry.FieldID) {
i.logger.Debug(fmt.Sprintf("Len Req: %d\n", len(requestFields))) i.logger.Debug(fmt.Sprintf("Len Req: %d\n", len(requestFields)))
i.data.ActiveBinds = make([]telemetry.BoundField, 0, len(requestFields)) i.data.ActiveBinds = make([]telemetry.BoundField, 0, len(requestFields))
@@ -229,7 +229,9 @@ func (i *IRacing) Subscribe(requestFields []telemetry.FieldID) {
// we will add their dependencies and the primitives to a slice // we will add their dependencies and the primitives to a slice
pendingBinds := make([]telemetry.FieldID, 0, telemetry.MaxFields) pendingBinds := make([]telemetry.FieldID, 0, telemetry.MaxFields)
for _, id := range requestFields { for winID, id := range requestFields {
i.data.Values[id].IDs = append(i.data.Values[id].IDs, winID)
switch id { switch id {
case telemetry.RPMStateColour: case telemetry.RPMStateColour:
i.data.VirtualBinds = append(i.data.VirtualBinds, telemetry.NewRPMLights()) i.data.VirtualBinds = append(i.data.VirtualBinds, telemetry.NewRPMLights())
+5 -13
View File
@@ -7,7 +7,6 @@ import (
"time" "time"
"esdi/providers" "esdi/providers"
"esdi/telemetry"
telem "esdi/telemetry" telem "esdi/telemetry"
) )
@@ -204,20 +203,13 @@ func (t *TelemetryService) UnsubscribeListener(id string) {
} }
func (t *TelemetryService) SubscribeToFields() { func (t *TelemetryService) SubscribeToFields() {
seen := make(map[telemetry.FieldID]struct{}) // _ = t.devService.SubscribeFields()
var allFields []telemetry.FieldID // TODO: we need to find a way of requesting devices to send all subscribed fields
// instead of going through the devices on DevService here
for _, dev := range t.devService.Devices { for _, dev := range t.devService.Devices {
for _, field := range dev.RequiredFields() { fields := dev.RequiredFields()
if _, exists := seen[field]; !exists { t.activeProvider.Subscribe(fields)
seen[field] = struct{}{}
allFields = append(allFields, field)
} }
}
}
t.logger.Debug("requested fields", "fields", allFields)
t.activeProvider.Subscribe(allFields)
} }
func (t *TelemetryService) StartStream() { func (t *TelemetryService) StartStream() {
+75 -8
View File
@@ -1,18 +1,12 @@
package telemetry package telemetry
import ( import (
"math"
"strconv" "strconv"
"sync" "sync"
"time" "time"
) )
func init() {
fieldNameToID = make(map[string]FieldID, MaxFields)
for id, name := range FieldNames {
fieldNameToID[name] = FieldID(id)
}
}
// NOTE: allow the user to create custom data things. For example, iRacing provides // NOTE: allow the user to create custom data things. For example, iRacing provides
// multiple surface temps, but I guess the user doesn't want all of them at once. // multiple surface temps, but I guess the user doesn't want all of them at once.
// allow him to make something that allows some data transformation to occur. // allow him to make something that allows some data transformation to occur.
@@ -70,7 +64,7 @@ const (
// NOTE: we can optimize this via a special command that says a given piece of data // NOTE: we can optimize this via a special command that says a given piece of data
// is for multiple targets // is for multiple targets
type TelemetryField struct { type TelemetryField struct {
// IDs []int16 // Identification for the serial device IDs []int16 // Identification for the serial device
Type DataType Type DataType
Raw uint64 Raw uint64
Str string // Only to be used with DataTypeSTRING Str string // Only to be used with DataTypeSTRING
@@ -81,6 +75,49 @@ func (tf *TelemetryField) Unused() {
tf.Raw = uint64('-') tf.Raw = uint64('-')
} }
// Pack will pack this current TelemetryField into bytes to send over the wire
// Format:
// 0x00 - Field ID
// 0x00 |
// 0x01 - DataType
// 0x02 - if its a (u)int8
// or
// 0x02 - if its a (u)int16 - first byte
// 0x02 - if its a (u)int16 - second byte
// or
// 0x02 - str len max is 255 chars
// [0x02] - str
func (tf *TelemetryField) Pack(dest []byte) []byte {
// NOTE: maybe we can have a pool of these so we don't have to create them here
// or whatever
for _, id := range tf.IDs {
dest = append(dest, uint8(id), uint8(id>>8))
dest = append(dest, uint8(tf.Type))
switch tf.Type {
case DataTypeINT8, DataTypeUINT8, DataTypeCHAR:
dest = append(dest, uint8(tf.Raw))
case DataTypeINT16, DataTypeUINT16:
dest = append(dest, uint8(tf.Raw), uint8(tf.Raw>>8))
case DataTypeINT32, DataTypeUINT32:
dest = append(dest, uint8(tf.Raw), uint8(tf.Raw>>8), uint8(tf.Raw>>16), uint8(tf.Raw>>24))
case DataTypeINT64, DataTypeUINT64:
dest = append(
dest, uint8(tf.Raw), uint8(tf.Raw>>8), uint8(tf.Raw>>16),
uint8(tf.Raw>>24), uint8(tf.Raw>>32), uint8(tf.Raw>>40), uint8(tf.Raw>>48),
uint8(tf.Raw>>56),
)
case DataTypeSTRING:
l := min(len(tf.Str), math.MaxUint8)
dest = append(dest, uint8(l))
dest = append(dest, tf.Str[:l]...)
}
}
return dest
}
func (tf *TelemetryField) String() string { func (tf *TelemetryField) String() string {
switch tf.Type { switch tf.Type {
case DataTypeSTRING: case DataTypeSTRING:
@@ -222,6 +259,13 @@ func GetFieldName(id FieldID) string {
var fieldNameToID map[string]FieldID var fieldNameToID map[string]FieldID
func initFieldNamesMap() {
fieldNameToID = make(map[string]FieldID, MaxFields)
for id, name := range FieldNames {
fieldNameToID[name] = FieldID(id)
}
}
func GetFieldID(name string) (FieldID, bool) { func GetFieldID(name string) (FieldID, bool) {
id, ok := fieldNameToID[name] id, ok := fieldNameToID[name]
return id, ok return id, ok
@@ -241,3 +285,26 @@ type TelemetryData struct {
func NewTelemetryData() *TelemetryData { func NewTelemetryData() *TelemetryData {
return &TelemetryData{} return &TelemetryData{}
} }
func (td *TelemetryData) Pack() []byte {
bufPtr := bufferPool.Get().(*[]byte)
buf := (*bufPtr)[:0]
// for _, bind := range td.ActiveBinds {
// buf = td.Values[bind.ID].Pack(buf)
// }
for k := range td.Values {
if len(td.Values[k].IDs) > 0 {
buf = td.Values[k].Pack(buf)
}
}
// We have to copy here because we have to return the buffer
result := make([]byte, len(buf))
copy(result, buf)
bufferPool.Put(&buf)
return result
}
+1 -1
View File
@@ -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(map[int16]FieldID)
IsAlive(time.Duration) bool IsAlive(time.Duration) bool
Name() string Name() string
Close() Close()
+4
View File
@@ -1 +1,5 @@
package telemetry package telemetry
func Init() {
initFieldNamesMap()
}