Too many things for a single commit but in summary:

We got them data bytes computer majigs being transmitted to the display
and correctly parsed over there. Also seems to be fast. I won't touch
performance issues until I have this working and documented tho.
This commit is contained in:
2026-03-10 23:10:10 +00:00
parent a8d71e0827
commit 16e102dd83
8 changed files with 162 additions and 37 deletions
+31 -1
View File
@@ -13,6 +13,7 @@ import (
"esdi/peripheral/communication"
"esdi/peripheral/communication/packets"
"esdi/peripheral/types"
"esdi/telemetry"
"gopkg.in/yaml.v3"
)
@@ -33,7 +34,8 @@ const (
destroyWindowCMDID types.Command = 4
updateWindowDimsCMDID types.Command = 5
updateWindowCMDID types.Command = 6 // Change this to a move cmd instead
newLayoutCMDID types.Command = 7
sendDataCMDID types.Command = 7
newLayoutCMDID types.Command = 8
)
const (
@@ -336,3 +338,31 @@ func (d *CDashDisplay) LoadLayout(layoutName string) error {
return nil
}
func (d *CDashDisplay) SendData(data *telemetry.TelemetryData) {
packet := data.Pack()
bytes, err := helper.StructToBytes(packet)
if err != nil {
return
}
curStr := ""
byteCount := 0
for _, byte := range bytes {
byteCount++
curStr += fmt.Sprintf("%02x ", byte)
if byteCount == 8 {
pLogger.Debug(curStr)
curStr = ""
byteCount = 0
}
}
// var ack packets.AckPacket
err = d.WT.SendCommand(sendDataCMDID, bytes, nil)
if err != nil && err != io.EOF {
return
}
}
-14
View File
@@ -4,20 +4,6 @@ import (
"esdi/telemetry"
)
type FieldMapper struct {
SDKKey string
DataType telemetry.DataType
Transform func(any) uint64
}
// 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
type boundField struct {
Key string
ID telemetry.FieldID
Transform func(any, *telemetry.TelemetryField)
}
var internalToSDKFieldNames = map[telemetry.FieldID]string{
telemetry.Speed: "Speed",
telemetry.Gear: "Gear",
+7 -8
View File
@@ -21,9 +21,8 @@ type IRacing struct {
SDK *goirsdk.IBT
// Data Handling
mut sync.Mutex
data *telem.TelemetryData
activeBindings []boundField
mut sync.Mutex
data *telem.TelemetryData
// Timing information
ticker *time.Ticker // ticker will keep polling intervals constant
@@ -98,7 +97,7 @@ func (i *IRacing) readData() {
}
// Read binded data
for _, b := range i.activeBindings {
for _, b := range i.data.ActiveBinds {
v := i.SDK.Vars.Vars[b.Key].Value
// NOTE: for the love of god, find a way of avoiding this shit
b.Transform(v, &i.data.Values[b.ID])
@@ -125,7 +124,7 @@ func (i *IRacing) Stream() (<-chan telem.TelemetryData, error) {
func (i *IRacing) Subscribe(requestFields []telem.FieldID) {
i.logger.Debug(fmt.Sprintf("Len Req: %d\n", len(requestFields)))
i.activeBindings = make([]boundField, 0, len(requestFields))
i.data.ActiveBinds = make([]telem.BoundField, 0, len(requestFields))
for _, id := range requestFields {
// Translate the UI FieldIDs to this provider's field names
@@ -136,7 +135,7 @@ func (i *IRacing) Subscribe(requestFields []telem.FieldID) {
continue
}
binding := boundField{
binding := telem.BoundField{
Key: sdkKey,
ID: id,
}
@@ -159,8 +158,8 @@ func (i *IRacing) Subscribe(requestFields []telem.FieldID) {
}
}
i.activeBindings = append(i.activeBindings, binding)
i.data.ActiveBinds = append(i.data.ActiveBinds, binding)
}
i.logger.Debug(fmt.Sprintf("Subscribed: %+v\n", i.activeBindings))
i.logger.Debug(fmt.Sprintf("Subscribed: %+v\n", i.data.ActiveBinds))
}
+51 -11
View File
@@ -3,9 +3,31 @@ package telemetry
import (
"math"
"strconv"
"sync"
"time"
)
type FieldMapper struct {
SDKKey string
DataType DataType
Transform func(any) uint64
}
// 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
type BoundField struct {
Key string
ID FieldID
Transform func(any, *TelemetryField)
}
var bufferPool = sync.Pool{
New: func() any {
b := make([]byte, 0, MaxFields*8)
return &b
},
}
type DataType uint8
const (
@@ -40,33 +62,33 @@ type TelemetryField struct {
// or
// 0x02 - str len max is 255 chars
// [0x02] - str
func (tf *TelemetryField) Pack() []byte {
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
buf := make([]byte, 0, 8)
buf = append(buf, uint8(tf.Type))
dest = append(dest, uint8(tf.Type))
switch tf.Type {
case DataTypeINT8, DataTypeUINT8:
buf = append(buf, uint8(tf.Raw))
dest = append(dest, uint8(tf.Raw))
case DataTypeINT16, DataTypeUINT16:
buf = append(buf, uint8(tf.Raw), uint8(tf.Raw>>8))
dest = append(dest, uint8(tf.Raw), uint8(tf.Raw>>8))
case DataTypeINT32, DataTypeUINT32:
buf = append(buf, uint8(tf.Raw), uint8(tf.Raw>>8), uint8(tf.Raw>>16))
dest = append(dest, uint8(tf.Raw), uint8(tf.Raw>>8), uint8(tf.Raw>>16))
case DataTypeINT64, DataTypeUINT64:
buf = append(buf, uint8(tf.Raw), uint8(tf.Raw>>8), uint8(tf.Raw>>16),
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)
buf = append(buf, uint8(l))
buf = append(buf, tf.Str[:l]...)
dest = append(dest, uint8(l))
dest = append(dest, tf.Str[:l]...)
case DataTypeCHAR:
dest = append(dest, uint8(tf.Raw))
}
return buf
return dest
}
func (tf *TelemetryField) String() string {
@@ -134,6 +156,7 @@ func GetFieldID(name string) (FieldID, bool) {
type TelemetryData struct {
// Values map[string]*TelemetryField
Values [MaxFields]TelemetryField
ActiveBinds []BoundField
InitialTime time.Time
PenultimateDataPoll time.Time
LastDataPoll time.Time
@@ -142,3 +165,20 @@ type TelemetryData struct {
func NewTelemetryData() *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)
}
// 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
}
+44 -1
View File
@@ -55,13 +55,56 @@ func Test_TelemetryField(t *testing.T) {
expect: []byte{0x08, 0x0E, 0x61, 0x20, 0x63, 0x6F, 0x6F, 0x6C, 0x20, 0x73,
0x74, 0x72, 0x69, 0x6E, 0x67, 0x21},
},
{
name: "test_char",
tf: TelemetryField{
Type: DataTypeCHAR,
Raw: uint64('R'),
},
expect: []byte{0x09, 0x52},
},
}
bufPtr := bufferPool.Get().(*[]byte)
buf := (*bufPtr)[:0]
for _, test := range tests {
result := test.tf.Pack()
result := test.tf.Pack(buf)
if !bytes.Equal(result, test.expect) {
t.Errorf("\nTest: %s\nExpected: %v\nGot: %v\n", test.name, test.expect, result)
}
}
}
func Test_TelemetryData(t *testing.T) {
data := TelemetryData{
ActiveBinds: []BoundField{
{
Key: "Speed",
ID: Speed,
},
{
Key: "Gear",
ID: Gear,
},
},
}
data.Values[Speed] = TelemetryField{
Type: DataTypeUINT16,
Raw: uint64(254),
}
data.Values[Gear] = TelemetryField{
Type: DataTypeCHAR,
Raw: uint64('R'),
}
expect := []byte{0x02, 0xFE, 0x00, 0x09, 0x52}
result := data.Pack()
if !bytes.Equal(result, expect) {
t.Errorf("\nExpected: %v\nGot: %v\n", expect, result)
}
}
+20
View File
@@ -4,8 +4,10 @@ import (
"esdi/cdashdisplay"
helper "esdi/helpers"
"esdi/peripheral"
"esdi/telemetry"
"fmt"
"log/slog"
"sync/atomic"
)
type CDashService struct {
@@ -89,3 +91,21 @@ func (cds *CDashService) MoveWindow(idx int16, vec *helper.Vector) error {
return nil
}
func (cds *CDashService) StreamData(stream <-chan telemetry.TelemetryData) {
var isSending atomic.Bool
go func() {
for msg := range stream {
if isSending.Load() {
continue
}
isSending.Store(true)
cds.CDash.SendData(&msg)
isSending.Store(false)
}
}()
}
+7 -1
View File
@@ -11,12 +11,14 @@ import (
// It should hook to a data sink and handle it like iRacing, BeamNG, AC and so on
type TelemetryService struct {
logger *slog.Logger
cdash *CDashService
ActiveProvider telemetry.TelemetryProvider
}
func NewTelemetryService(logger *slog.Logger) *TelemetryService {
func NewTelemetryService(logger *slog.Logger, cdash *CDashService) *TelemetryService {
return &TelemetryService{
logger: logger,
cdash: cdash,
}
}
@@ -45,5 +47,9 @@ func (t *TelemetryService) SetProvider(provider string) *TelemetryService {
func (t *TelemetryService) StartStream() <-chan telemetry.TelemetryData {
stream, _ := t.ActiveProvider.Stream()
// Somewhere around here we have to tell the device service to also send data
t.cdash.StreamData(stream)
return stream
}
+2 -1
View File
@@ -22,7 +22,8 @@ func NewControlPanel(logger *slog.Logger) *ControlPanel {
}
devService := services.NewCDashService(logger)
telemService := services.NewTelemetryService(logger).SetProvider("iRacing")
telemService := services.NewTelemetryService(logger, devService).
SetProvider("iRacing")
if telemService == nil {
panic("failed to create the telemetry service")
}