Got a bunch of stuff going on here

the main is thing is that I hammered one a way of showing data from an
ibt
This commit is contained in:
2026-02-20 21:51:57 +00:00
parent c071730337
commit 6a37d7e97c
10 changed files with 474 additions and 36 deletions
+3 -3
View File
@@ -249,11 +249,11 @@ func (d *CDashDisplay) updateWindowDimensions(win *UIWindow, packet UpdateDimsPa
return nil
}
func (d *CDashDisplay) MoveWindow(wID int16, delta helper.Vector) error {
func (d *CDashDisplay) MoveWindow(wID int16, delta helper.Vector) (UIDimensions, error) {
// Get the window from the layout
window, ok := d.State.Layout.Windows[wID]
if !ok {
return fmt.Errorf("window with ID '%d' doesn't exist", wID)
return UIDimensions{}, fmt.Errorf("window with ID '%d' doesn't exist", wID)
}
// Now we will create new dimensions
@@ -267,7 +267,7 @@ func (d *CDashDisplay) MoveWindow(wID int16, delta helper.Vector) error {
Dims: newDimensions,
}
return d.updateWindowDimensions(window, packet)
return newDimensions, d.updateWindowDimensions(window, packet)
}
func (d *CDashDisplay) ResizeWindow(wID int16, delta helper.Vector) error {
+14
View File
@@ -3,6 +3,11 @@ package cdashdisplay
// In this file we will place all structs that are 1:1 representation of the
// types in the device transport layer ->
const (
ShowIDFalse uint8 = 0
ShowIDTrue uint8 = 1
)
type UIDimensions struct {
X0 uint16 `yaml:"X0"`
Y0 uint16 `yaml:"Y0"`
@@ -21,9 +26,18 @@ type UIDecorations struct {
Padding uint8 `yaml:"Padding"`
}
// NOTE: we can't use FString32 for this - too many bytes
// NOTE: use a bit flags for this options instead
type UIWindowOpts struct {
ShowID uint8 `yaml:"ShowID"`
WinType uint8 `yaml:"WinType"`
PreviewValue FString32 `yaml:"PreviewValue"`
}
type UIWindow struct {
Dims UIDimensions `yaml:"Dims"`
Decor UIDecorations `yaml:"Decor"`
Opts UIWindowOpts `yaml:"Opts"`
Title FString32 `yaml:"Title"`
}
+36 -8
View File
@@ -1,20 +1,24 @@
Windows:
1:
Dims:
X0: 250
Y0: 120
Width: 148
Height: 76
X0: 234
Y0: 124
Width: 117
Height: 72
Decor:
BGColour: 4161
FGColour: 65535
TitleColour: 65535
BorderColour: 63488
TitleSize: 2
TextSize: 4
TextSize: 6
HasBorder: 1
Padding: 0
Title: TITLE
Opts:
ShowID: 0
WinType: 0
PreviewValue: "255"
Title: SPEEDO
2:
Dims:
X0: 401
@@ -27,7 +31,31 @@ Windows:
TitleColour: 65535
BorderColour: 63488
TitleSize: 2
TextSize: 4
TextSize: 6
HasBorder: 1
Padding: 0
Title: NEWTITLE
Opts:
ShowID: 0
WinType: 0
PreviewValue: "7456"
Title: TACH
3:
Dims:
X0: 340
Y0: 240
Width: 84
Height: 126
Decor:
BGColour: 4161
FGColour: 65535
TitleColour: 65535
BorderColour: 63488
TitleSize: 2
TextSize: 14
HasBorder: 1
Padding: 0
Opts:
ShowID: 0
WinType: 0
PreviewValue: "6"
Title: GEAR
+28 -5
View File
@@ -60,6 +60,10 @@ func NewMainController(logger *slog.Logger) *MainController {
mc.EvBus.On(ui.CreateWindowEv{}, func(e any) {
win := e.(ui.CreateWindowEv).Window
winDecor := cdashdisplay.DefaultDecorations
winDecor.TextSize = win.TextSize
winDecor.TitleSize = win.TitleSize
uiWindow := cdashdisplay.UIWindow{
Dims: cdashdisplay.UIDimensions{
X0: win.X,
@@ -67,7 +71,12 @@ func NewMainController(logger *slog.Logger) *MainController {
Width: win.Width,
Height: win.Height,
},
Decor: cdashdisplay.DefaultDecorations,
Opts: cdashdisplay.UIWindowOpts{
WinType: win.Type, // NOTE: values aren't implemented yet
ShowID: win.ShowID,
PreviewValue: helper.B32(win.PreviewValue),
},
Decor: winDecor,
Title: helper.B32(win.Title),
}
@@ -91,10 +100,20 @@ func NewMainController(logger *slog.Logger) *MainController {
return
}
// Need to use a better interface for this me things
// Update the dimensions
curWindow.Dims.X0 = winModel.Window.X
curWindow.Dims.Y0 = winModel.Window.Y
curWindow.Dims.Width = winModel.Window.Width
curWindow.Dims.Height = winModel.Window.Height
// Update the opts
curWindow.Opts.ShowID = winModel.Window.ShowID
curWindow.Opts.WinType = winModel.Window.Type
curWindow.Opts.PreviewValue = helper.B32(winModel.Window.PreviewValue)
// Update the decorations
curWindow.Decor.TitleSize = winModel.Window.TitleSize
curWindow.Decor.TextSize = winModel.Window.TextSize
// Update the title
curWindow.Title = helper.B32(winModel.Window.Title)
err := mc.CDash.UpdateWindow(winModel.ID, curWindow)
@@ -113,11 +132,14 @@ func NewMainController(logger *slog.Logger) *MainController {
mc.EvBus.On(ui.MoveWindowEv{}, func(e any) {
mvData := e.(ui.MoveWindowEv)
pLogger.Debug(fmt.Sprintf("request to move window '%d'", mvData.WindowID))
err := mc.CDash.MoveWindow(mvData.WindowID, mvData.Delta)
newDims, err := mc.CDash.MoveWindow(mvData.WindowID, mvData.Delta)
if err != nil && err != io.EOF {
pLogger.Debug(fmt.Sprintf("failed to move window '%d' %s", mvData.WindowID, err.Error()))
return
}
mc.EvBus.Emit(ui.WindowMovedEv{ID: mvData.WindowID, Dims: newDims})
})
mc.EvBus.On(ui.ResizeWindowEv{}, func(e any) {
@@ -153,14 +175,15 @@ func NewMainController(logger *slog.Logger) *MainController {
return
}
// pLogger.Info(fmt.Sprintf("found cdashdisplay on %s!", mc.CDash.WT.Cfg.Name))
// mc.EvBus.Emit(ui.PrintLogEv{Log: "found cdashdisplay" + mc.CDash.WT.Cfg.Name + "\n"})
mc.CDash = display
pLogger.Info("found cdashdisplay on: " + display.WT.Cfg.Name)
mc.EvBus.Emit(ui.PrintLogEv{Log: "found cdashdisplay on: " + display.WT.Cfg.Name + "\n"})
}()
})
mc.EvBus.On(ui.ForceRedraw{}, func(e any) {
mc.App.QueueUpdateDraw(func() {})
// mc.App.QueueUpdateDraw(func() {})
mc.App.Draw()
})
return mc
+1
View File
@@ -0,0 +1 @@
package controllers
+10 -5
View File
@@ -2,9 +2,14 @@
package models
type Window struct {
X uint16
Y uint16
Width uint16
Height uint16
Title string
X uint16
Y uint16
Width uint16
Height uint16
TitleSize uint8
TextSize uint8
Title string
Type uint8
ShowID uint8
PreviewValue string
}
+5
View File
@@ -69,6 +69,11 @@ type WindowCreatedEv struct {
Win cdashdisplay.UIWindow
}
type WindowMovedEv struct {
ID int16
Dims cdashdisplay.UIDimensions
}
type WindowDestroyedEv struct {
ID int16
}
+3
View File
@@ -189,6 +189,9 @@ func layoutToolTreeViewEvCapture(bus *events.Bus, doc *dom.DOM,
case 'l':
// Load the layout
bus.Emit(ui.LoadLayoutEv{})
case 'g':
// Go -> launches the current set up source
streamingWindow(bus, doc)
}
return event
+108 -15
View File
@@ -15,7 +15,9 @@ import (
)
// Validate the form inputs
func validateFormInputs(x, y, w, h, title string) (models.Window, error) {
func validateFormInputs(x, y, w, h,
title, prev, titleSize, textSize string,
showID bool) (models.Window, error) {
xValue, err := strconv.ParseUint(x, 10, 64)
if err != nil {
return models.Window{}, err
@@ -32,13 +34,30 @@ func validateFormInputs(x, y, w, h, title string) (models.Window, error) {
if err != nil {
return models.Window{}, err
}
titleSizeValue, err := strconv.ParseUint(titleSize, 10, 64)
if err != nil {
return models.Window{}, err
}
textSizeValue, err := strconv.ParseUint(textSize, 10, 64)
if err != nil {
return models.Window{}, err
}
showIDValue := cdashdisplay.ShowIDFalse
if showID {
showIDValue = cdashdisplay.ShowIDTrue
}
return models.Window{
X: uint16(xValue),
Y: uint16(yValue),
Width: uint16(widthValue),
Height: uint16(heightValue),
Title: title,
X: uint16(xValue),
Y: uint16(yValue),
Width: uint16(widthValue),
Height: uint16(heightValue),
ShowID: showIDValue,
TitleSize: uint8(titleSizeValue),
TextSize: uint8(textSizeValue),
PreviewValue: prev,
Title: title,
}, nil
}
@@ -50,12 +69,22 @@ func createNewWindowForm(bus *events.Bus, doc *dom.DOM) {
width := tview.NewInputField().SetLabel("width")
height := tview.NewInputField().SetLabel("height")
title := tview.NewInputField().SetLabel("title")
previewValue := tview.NewInputField().SetLabel("preview value")
showID := tview.NewCheckbox().SetLabel("show ID").SetChecked(true)
winType := tview.NewDropDown().SetLabel("type").
SetOptions([]string{"string", "bar"}, func(s string, id int) {
// Here we have to add extra options for the configuration of default
// values and so on
}).
SetCurrentOption(0)
titleSize := tview.NewDropDown().SetLabel("Title Size").
SetOptions([]string{"1", "2", "3", "4", "5", "6", "7", "8"}, func(s string, id int) {
}).
SetCurrentOption(0)
textSize := tview.NewDropDown().SetLabel("Text Size").
SetOptions([]string{"1", "2", "3", "4", "5", "6", "7", "8"}, func(s string, id int) {
}).
SetCurrentOption(0)
form := tview.NewForm().
AddFormItem(x0).
@@ -63,8 +92,14 @@ func createNewWindowForm(bus *events.Bus, doc *dom.DOM) {
AddFormItem(width).
AddFormItem(height).
AddFormItem(title).
AddFormItem(previewValue).
AddFormItem(showID).
AddFormItem(winType).
AddFormItem(titleSize).
AddFormItem(textSize).
AddButton("Create", func() {
_, titleSizeTxt := titleSize.GetCurrentOption()
_, textSizeTxt := textSize.GetCurrentOption()
// Validate the inputs
window, err := validateFormInputs(
x0.GetText(),
@@ -72,6 +107,10 @@ func createNewWindowForm(bus *events.Bus, doc *dom.DOM) {
width.GetText(),
height.GetText(),
title.GetText(),
previewValue.GetText(),
titleSizeTxt,
textSizeTxt,
showID.IsChecked(),
)
if err != nil {
@@ -79,6 +118,7 @@ func createNewWindowForm(bus *events.Bus, doc *dom.DOM) {
return
}
bus.Emit(ui.LogEv{Log: fmt.Sprintf("showID: %d\n", window.ShowID)})
bus.Emit(ui.CreateWindowEv{Window: window})
})
@@ -116,17 +156,38 @@ func windowInfoForm(bus *events.Bus, doc *dom.DOM, idx int16,
win *cdashdisplay.UIWindow) *dom.UINode {
var err error
x0 := tview.NewInputField().SetLabel("x").SetText(fmt.Sprintf("%d", win.Dims.X0))
y0 := tview.NewInputField().SetLabel("y").SetText(fmt.Sprintf("%d", win.Dims.Y0))
width := tview.NewInputField().SetLabel("width").SetText(fmt.Sprintf("%d", win.Dims.Width))
height := tview.NewInputField().SetLabel("height").SetText(fmt.Sprintf("%d", win.Dims.Height))
title := tview.NewInputField().SetLabel("title").SetText(win.Title.String())
x0 := tview.NewInputField().SetLabel("x").
SetText(fmt.Sprintf("%d", win.Dims.X0))
y0 := tview.NewInputField().SetLabel("y").
SetText(fmt.Sprintf("%d", win.Dims.Y0))
width := tview.NewInputField().SetLabel("width").
SetText(fmt.Sprintf("%d", win.Dims.Width))
height := tview.NewInputField().SetLabel("height").
SetText(fmt.Sprintf("%d", win.Dims.Height))
title := tview.NewInputField().SetLabel("title").
SetText(win.Title.String())
previewValue := tview.NewInputField().SetLabel("preview value").
SetText(win.Opts.PreviewValue.String())
showID := tview.NewCheckbox().SetLabel("show ID").SetChecked(true)
winType := tview.NewDropDown().SetLabel("type").
SetOptions([]string{"string", "bar"}, func(s string, id int) {
// Here we have to add extra options for the configuration of default
// values and so on
}).
SetCurrentOption(0)
// NOTE: put this thing into a loop that generate the numbers or something yo
titleSize := tview.NewDropDown().SetLabel("Title Size").
SetOptions([]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
"11", "12", "13", "14", "15", "16", "17", "18", "19", "20"},
func(s string, id int) {
}).
SetCurrentOption(int(win.Decor.TitleSize))
textSize := tview.NewDropDown().SetLabel("Text Size").
SetOptions([]string{"1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
"11", "12", "13", "14", "15", "16", "17", "18", "19", "20"},
func(s string, id int) {
}).
SetCurrentOption(int(win.Decor.TextSize))
form := tview.NewForm().
AddFormItem(x0).
@@ -134,9 +195,16 @@ func windowInfoForm(bus *events.Bus, doc *dom.DOM, idx int16,
AddFormItem(width).
AddFormItem(height).
AddFormItem(title).
AddFormItem(previewValue).
AddFormItem(showID).
AddFormItem(titleSize).
AddFormItem(textSize).
AddFormItem(winType).
AddButton("Update", func() {
bus.Emit(ui.LogEv{Log: "update event was created\n"})
_, titleSizeTxt := titleSize.GetCurrentOption()
_, textSizeTxt := textSize.GetCurrentOption()
// Validate the inputs
window, err := validateFormInputs(
x0.GetText(),
@@ -144,6 +212,10 @@ func windowInfoForm(bus *events.Bus, doc *dom.DOM, idx int16,
width.GetText(),
height.GetText(),
title.GetText(),
previewValue.GetText(),
titleSizeTxt,
textSizeTxt,
showID.IsChecked(),
)
if err != nil {
@@ -151,6 +223,7 @@ func windowInfoForm(bus *events.Bus, doc *dom.DOM, idx int16,
return
}
bus.Emit(ui.LogEv{Log: fmt.Sprintf("showID: %d\n", window.ShowID)})
bus.Emit(ui.LogEv{Log: "sending update window ev\n"})
bus.Emit(ui.UpdateWindowEv{ID: idx, Window: window})
})
@@ -167,17 +240,37 @@ func windowInfoForm(bus *events.Bus, doc *dom.DOM, idx int16,
return ev
})
// In case the window is moved we need to update this form
bus.On(ui.WindowMovedEv{}, func(e any) {
data := e.(ui.WindowMovedEv)
// Only update the fields if the event is for this thing
if data.ID != idx {
return
}
// Get the current window-info-page
elem := doc.GetElemByID(windowInfoPageID(data.ID))
if elem == nil {
bus.Emit(ui.LogEv{Log: "could't get hold of info page"})
return
}
x0.SetText(fmt.Sprintf("%d", data.Dims.X0))
x0.SetText(fmt.Sprintf("%d", data.Dims.Y0))
width.SetText(fmt.Sprintf("%d", data.Dims.Width))
height.SetText(fmt.Sprintf("%d", data.Dims.Height))
})
var formNode *dom.UINode
elemId := windowInfoPageID(idx)
formNode = doc.GetNodeByID(elemId)
elemID := windowInfoPageID(idx)
formNode = doc.GetNodeByID(elemID)
layoutToolActionPagesElem := doc.GetElemByID(layoutToolActionPagesID)
if formNode == nil {
formNode, err = doc.NewUINode(elemId, layoutToolActionPagesElem, form)
formNode, err = doc.NewUINode(elemID, layoutToolActionPagesElem, form)
if err != nil {
panic("failed to create UI node for the new window form: " + err.Error())
}
}
// AddAndShowPage(bus, doc, layoutToolActionPagesElem.(*tview.Pages), formNode)
return formNode
}
+266
View File
@@ -0,0 +1,266 @@
package views
import (
esdi "esdi/oldEsdi"
"esdi/tui/internal/dom"
"esdi/tui/internal/events"
"esdi/tui/internal/ui"
"fmt"
"log"
"os"
"strings"
"sync"
"time"
"github.com/ESilva15/goirsdk"
"github.com/gdamore/tcell/v2"
"github.com/rivo/tview"
)
// Car data lengths
const (
SpeedLen = 5
GearLen = 3
RpmLen = 6
BrakeBiasLen = 6
)
const (
streamingBoxID = "streaming-box"
)
var (
lastMessageTime time.Time
data *esdi.SimulationData = &esdi.SimulationData{}
dataView *esdi.DataPacket = &esdi.DataPacket{}
initialTime = time.Now()
lastTime = time.Now()
mu sync.Mutex
irsdk *goirsdk.IBT
)
func setUpSource(bus *events.Bus, doc *dom.DOM, tv *tview.TextView) {
// Open the telemetry file
file, err := os.Open("/home/esilva/Desktop/projetos/simracing_peripherals/testTelemetry/supercars_indianapolis.ibt")
if err != nil {
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")
}
startTime := time.Now()
go readData()
// global irsdk is now here
go func() {
ticker := time.NewTicker(time.Second / 60)
defer ticker.Stop()
counter := 0
for t := range ticker.C {
// whatever
currentTime := time.Now()
delta := currentTime.Sub(startTime)
str := stringified() + fmt.Sprintf("%v - %7d\n%5f - %5f",
t.UTC(), counter, delta.Seconds(), delta.Seconds()/float64(counter))
tv.SetText(str)
bus.Emit(ui.ForceRedraw{})
counter++
}
}()
}
func streamingWindow(bus *events.Bus, doc *dom.DOM) {
bus.Emit(ui.LogEv{Log: "creating streaming window"})
// Get the ActionPages parent
box := tview.NewTextView()
box.SetBorder(true).SetTitle("STREAMING")
box.SetInputCapture(func(ev *tcell.EventKey) *tcell.EventKey {
switch ev.Rune() {
case 'q':
// well, I really need to design the UI to work properly
os.Exit(0)
}
return ev
})
var err error
var boxNode *dom.UINode
// delete the currently existing streaming-box
actionPages := doc.GetElemByID(layoutToolActionPagesID).(*tview.Pages)
actionPages.RemovePage(streamingBoxID)
// Get the currently exisiting streaming box in the dom so we can delete it
boxNode = doc.GetNodeByID(streamingBoxID)
if boxNode != nil {
doc.DeleteElem(boxNode)
}
// Register this streaming box as an UINode
boxNode, err = doc.NewUINode(
streamingBoxID,
doc.GetElemByID(layoutToolActionPagesID),
box,
)
if err != nil {
return
}
// Add it to the action pages and view it
AddAndShowPage(bus, doc, actionPages, boxNode, true)
setUpSource(bus, doc, box)
}
func stringified() string {
var buffer strings.Builder
mu.Lock()
sessionTimeR := irsdk.Vars.Vars["SessionTime"].Value
sessionTime := float64(sessionTimeR.(float64))
currTime := time.Now()
delta := currTime.Sub(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))
lastTime = currTime
elapsed := currTime.Sub(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",
data.Gear, data.RPM, 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))
mu.Unlock()
return buffer.String()
}
func msToKph(v float32) int {
return int((3600 * v) / 1000)
}
func lapTimeRepresentation(t float32, f string) string {
if t < 0 {
t = 0
}
wholeSeconds := int64(t)
lapTime := time.Unix(wholeSeconds, int64((t-float32(wholeSeconds))*1e9))
return lapTime.Format(f)
}
func lapTimeDeltaRepresentation(t float32) string {
sign := '-'
if t < 0 {
sign = '+'
t = -t
}
// Cap to 99.9 max
if t > 99.9 {
t = 99.9
}
if t >= 1 {
// Round to nearest tenth
rounded := float32(int(t*10+0.5)) / 10
return fmt.Sprintf("%c%.1f", sign, rounded)
}
// t < 1: round to nearest tenth and remove leading zero (e.g., "0.1" → ".1")
rounded := float32(int(t*10+0.5)) / 10
s := fmt.Sprintf("%.1f", rounded)
if strings.HasPrefix(s, "0") {
s = s[1:]
}
return fmt.Sprintf("%c%s", sign, s)
}
func readData() {
dataReaderTicker := time.NewTicker(time.Second / 60)
defer dataReaderTicker.Stop()
initialTime = time.Now()
for _ = range dataReaderTicker.C {
var err error
mu.Lock()
_, err = irsdk.Update(time.Millisecond * 100)
if err != nil {
fmt.Printf("could not update data: %v", err)
continue
}
mu.Unlock()
getVehicleData()
// Test the actual dataPacket we are sending over the wire
copyBytes(dataView.Speed[:], SpeedLen, fmt.Sprintf("%3d", data.Speed))
copyBytes(dataView.Gear[:], GearLen, fmt.Sprintf("%2d", data.Gear))
copyBytes(dataView.RPM[:], RpmLen, fmt.Sprintf("%3d", data.RPM))
lastMessageTime = time.Now()
}
}
func getVehicleData() {
mu.Lock()
curGear := irsdk.Vars.Vars["Gear"].Value
curRPM := irsdk.Vars.Vars["RPM"].Value
curSpeed := irsdk.Vars.Vars["Speed"].Value
data.Gear = int32(curGear.(int))
data.RPM = int32(curRPM.(float32))
data.Speed = int32(msToKph(curSpeed.(float32)))
mu.Unlock()
}
func copyBytes(dest []byte, destSize int, src string) {
copy(dest[:], []byte(src))
dest[min(destSize-1, len(src))] = '\x00'
}