decoupled the winIDs from the telemetry data
hell yeah, brother!
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
helper "esdi/helpers"
|
helper "esdi/helpers"
|
||||||
@@ -106,8 +107,10 @@ 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
|
||||||
@@ -120,14 +123,42 @@ func NewCDashDisplay() (*CDashDisplay, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -146,6 +177,9 @@ func (d *CDashDisplay) CreateWindow(win *DesktopUIWindow) (*DesktopUIWindow, err
|
|||||||
slog.Info(fmt.Sprintf("Recived ID message: %v", wID))
|
slog.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
|
||||||
}
|
}
|
||||||
@@ -177,6 +211,8 @@ func (d *CDashDisplay) UpdateWindow(win *DesktopUIWindow) error {
|
|||||||
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -199,7 +235,8 @@ func (d *CDashDisplay) DestroyWindow(wID int16) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NODE: add this
|
// NOTE: add this
|
||||||
|
d.UnregisterFieldMapping(wID)
|
||||||
d.State.Layout.RemoveWindow(wID)
|
d.State.Layout.RemoveWindow(wID)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -357,7 +394,7 @@ func (d *CDashDisplay) UnloadLayout() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *CDashDisplay) SendData(data *telemetry.TelemetryData) {
|
func (d *CDashDisplay) SendData(data *telemetry.TelemetryData) {
|
||||||
packet := data.Pack()
|
packet := d.encodePacket(data)
|
||||||
|
|
||||||
bytes, err := helper.StructToBytes(packet)
|
bytes, err := helper.StructToBytes(packet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
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 ->
|
||||||
|
|
||||||
@@ -65,6 +71,71 @@ type UIWindowUpdatePacket struct {
|
|||||||
Window UIWindow
|
Window UIWindow
|
||||||
}
|
}
|
||||||
|
|
||||||
// func getUIWindowDTO(w *DesktopWindowData) *UIWindow {
|
// TODO: this is here because this is the only device I use like this
|
||||||
//
|
// 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
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import (
|
|||||||
|
|
||||||
"esdi/cmd"
|
"esdi/cmd"
|
||||||
"esdi/config"
|
"esdi/config"
|
||||||
"esdi/telemetry"
|
|
||||||
|
|
||||||
"github.com/arl/statsviz"
|
"github.com/arl/statsviz"
|
||||||
)
|
)
|
||||||
@@ -44,7 +43,7 @@ func initApplication() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setting up some internal data structures
|
// Setting up some internal data structures
|
||||||
telemetry.Init()
|
// telemetry.Init()
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupLogger() error {
|
func setupLogger() error {
|
||||||
@@ -56,6 +55,7 @@ func setupLogger() error {
|
|||||||
logger := slog.New(
|
logger := slog.New(
|
||||||
slog.NewTextHandler(output, &slog.HandlerOptions{
|
slog.NewTextHandler(output, &slog.HandlerOptions{
|
||||||
Level: slog.LevelDebug,
|
Level: slog.LevelDebug,
|
||||||
|
// AddSource: true, // NOTE: this may have some performance impacts, disable it for prod
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -125,8 +125,8 @@ func (b *BeamNG) Subscribe(requestFields map[int16]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 winID, id := range requestFields {
|
for _, id := range requestFields {
|
||||||
b.data.Values[id].IDs = append(b.data.Values[id].IDs, winID)
|
// b.data.Values[id].IDs = append(b.data.Values[id].IDs, winID)
|
||||||
|
|
||||||
switch id {
|
switch id {
|
||||||
case telemetry.RPMStateColour:
|
case telemetry.RPMStateColour:
|
||||||
|
|||||||
@@ -229,8 +229,8 @@ func (i *IRacing) Subscribe(requestFields map[int16]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 winID, id := range requestFields {
|
for _, id := range requestFields {
|
||||||
i.data.Values[id].IDs = append(i.data.Values[id].IDs, winID)
|
// i.data.Values[id].IDs = append(i.data.Values[id].IDs, winID)
|
||||||
|
|
||||||
switch id {
|
switch id {
|
||||||
case telemetry.RPMStateColour:
|
case telemetry.RPMStateColour:
|
||||||
|
|||||||
@@ -206,8 +206,10 @@ func (t *TelemetryService) SubscribeToFields() {
|
|||||||
// _ = t.devService.SubscribeFields()
|
// _ = t.devService.SubscribeFields()
|
||||||
// TODO: we need to find a way of requesting devices to send all subscribed fields
|
// TODO: we need to find a way of requesting devices to send all subscribed fields
|
||||||
// instead of going through the devices on DevService here
|
// instead of going through the devices on DevService here
|
||||||
|
// REVAMP
|
||||||
for _, dev := range t.devService.Devices {
|
for _, dev := range t.devService.Devices {
|
||||||
fields := dev.RequiredFields()
|
fields := dev.RequiredFields()
|
||||||
|
t.logger.Debug("requested fields", "fields", fields)
|
||||||
t.activeProvider.Subscribe(fields)
|
t.activeProvider.Subscribe(fields)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-73
@@ -1,12 +1,18 @@
|
|||||||
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.
|
||||||
@@ -64,7 +70,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
|
||||||
@@ -75,49 +81,6 @@ 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:
|
||||||
@@ -259,12 +222,12 @@ func GetFieldName(id FieldID) string {
|
|||||||
|
|
||||||
var fieldNameToID map[string]FieldID
|
var fieldNameToID map[string]FieldID
|
||||||
|
|
||||||
func initFieldNamesMap() {
|
// func initFieldNamesMap() {
|
||||||
fieldNameToID = make(map[string]FieldID, MaxFields)
|
// fieldNameToID = make(map[string]FieldID, MaxFields)
|
||||||
for id, name := range FieldNames {
|
// for id, name := range FieldNames {
|
||||||
fieldNameToID[name] = FieldID(id)
|
// fieldNameToID[name] = FieldID(id)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
func GetFieldID(name string) (FieldID, bool) {
|
func GetFieldID(name string) (FieldID, bool) {
|
||||||
id, ok := fieldNameToID[name]
|
id, ok := fieldNameToID[name]
|
||||||
@@ -286,25 +249,25 @@ func NewTelemetryData() *TelemetryData {
|
|||||||
return &TelemetryData{}
|
return &TelemetryData{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (td *TelemetryData) Pack() []byte {
|
// func (td *TelemetryData) Pack() []byte {
|
||||||
bufPtr := bufferPool.Get().(*[]byte)
|
// bufPtr := bufferPool.Get().(*[]byte)
|
||||||
buf := (*bufPtr)[:0]
|
// buf := (*bufPtr)[:0]
|
||||||
|
//
|
||||||
// for _, bind := range td.ActiveBinds {
|
// // for _, bind := range td.ActiveBinds {
|
||||||
// buf = td.Values[bind.ID].Pack(buf)
|
// // buf = td.Values[bind.ID].Pack(buf)
|
||||||
// }
|
// // }
|
||||||
|
//
|
||||||
for k := range td.Values {
|
// for k := range td.Values {
|
||||||
if len(td.Values[k].IDs) > 0 {
|
// if len(td.Values[k].IDs) > 0 {
|
||||||
buf = td.Values[k].Pack(buf)
|
// buf = td.Values[k].Pack(buf)
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// We have to copy here because we have to return the buffer
|
// // We have to copy here because we have to return the buffer
|
||||||
result := make([]byte, len(buf))
|
// result := make([]byte, len(buf))
|
||||||
copy(result, buf)
|
// copy(result, buf)
|
||||||
|
//
|
||||||
bufferPool.Put(&buf)
|
// bufferPool.Put(&buf)
|
||||||
|
//
|
||||||
return result
|
// return result
|
||||||
}
|
// }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
package telemetry
|
package telemetry
|
||||||
|
|
||||||
func Init() {
|
// func Init() {
|
||||||
initFieldNamesMap()
|
// initFieldNamesMap()
|
||||||
}
|
// }
|
||||||
|
|||||||
Reference in New Issue
Block a user