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:
+31
-1
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user