First commit
This project shall offer a iRacing telemetry parser with as little dependencies as possible
This commit is contained in:
@@ -0,0 +1,47 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
SubHeaderSize = 32 // SubHeaderSize is the size of the subheader
|
||||||
|
)
|
||||||
|
|
||||||
|
// DiskSubHeader represents the IBT sub headers
|
||||||
|
type DiskSubHeader struct {
|
||||||
|
StartDate float64 // StartDate represents the start data of the telemetry
|
||||||
|
StartTime float64 // StartTime ...
|
||||||
|
EndTime float64 // EndTime ...
|
||||||
|
LapCount int32 // LapCount represents the total number laps
|
||||||
|
RecordCount int32 // RecordCount ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTelemetrySubHeader will return a pointer to a DiskSubHeader variable
|
||||||
|
// or nil if an error occurs. In which case the error return value is more
|
||||||
|
// valuable
|
||||||
|
func parseTelemetrySubHeader(buf [SubHeaderSize]byte) (*DiskSubHeader, error) {
|
||||||
|
dst := DiskSubHeader{}
|
||||||
|
err := binary.Read(bytes.NewBuffer(buf[:]), binary.LittleEndian, &dst)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dst, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString renders a string showing the values of the struct
|
||||||
|
func (d *DiskSubHeader) ToString() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"StartDate: %13f (0x%04x)\n" +
|
||||||
|
"StartTime: %13f (0x%08x)\n" +
|
||||||
|
"EndTime: %13f (0x%08x)\n" +
|
||||||
|
"LapCount: %13d (0x%04x)\n" +
|
||||||
|
"RecordCount: %13d (0x%04x)\n",
|
||||||
|
d.StartDate, d.StartDate, d.StartTime, d.StartTime,
|
||||||
|
d.EndTime, d.EndTime, d.LapCount, d.LapCount,
|
||||||
|
d.RecordCount, d.RecordCount,
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
+80
@@ -0,0 +1,80 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
FileHeaderSize = 56 // FileHeaderSize is the size of the headers
|
||||||
|
HeaderSize = 4 // HeaderSize is the size of a single header
|
||||||
|
)
|
||||||
|
|
||||||
|
// TelemetryHeaders struct to hold an IBT file's headers
|
||||||
|
type TelemetryHeaders struct {
|
||||||
|
Version int32
|
||||||
|
// Status of 1 indicates a completed session and status of 0 a live session
|
||||||
|
Status int32
|
||||||
|
// TickRate indicates the frequency of writes (usually 60)
|
||||||
|
TickRate int32
|
||||||
|
// SessionInfoUpdate indicates the number of times the SessionInfo was
|
||||||
|
// updated. 0 for finished sessions and >1 for active sessions
|
||||||
|
SessionInfoUpdate int32
|
||||||
|
// SessionInfoLength is the length of the session info buffer
|
||||||
|
SessionInfoLength int32
|
||||||
|
// SessionInfoOffset is the offset of the session info in the buffer
|
||||||
|
SessionInfoOffset int32
|
||||||
|
// NumVars is the number of variables in each input
|
||||||
|
NumVars int32
|
||||||
|
// VarHeaderOffset is the offset of the VarHeader
|
||||||
|
VarHeaderOffset int32
|
||||||
|
// NumBuf will be 1 for static files and 3 for live telemetry files
|
||||||
|
NumBuf int32
|
||||||
|
// BufLen is the length for parsing VarHeader values
|
||||||
|
BufLen int32
|
||||||
|
// Padding
|
||||||
|
Padding [12]byte
|
||||||
|
// I still don't know what this is:
|
||||||
|
BufOffset int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString renders a string showing the values of the struct
|
||||||
|
func (th *TelemetryHeaders) ToString() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Version: %5d (0x%04x)\n"+
|
||||||
|
"Status: %5d (0x%04x)\n"+
|
||||||
|
"TickRate: %5d (0x%04x)\n"+
|
||||||
|
"SIUpdate: %5d (0x%04x)\n"+
|
||||||
|
"SILength: %5d (0x%04x)\n"+
|
||||||
|
"SIOffset: %5d (0x%04x)\n"+
|
||||||
|
"NumVars: %5d (0x%04x)\n"+
|
||||||
|
"VarHeaderOffset: %5d (0x%04x)\n"+
|
||||||
|
"NumBuf: %5d (0x%04x)\n"+
|
||||||
|
"BufLen: %5d (0x%04x)\n"+
|
||||||
|
"BufOffset: %5d (0x%04x)\n",
|
||||||
|
th.Version, th.Version, th.Status, th.Status, th.TickRate, th.TickRate,
|
||||||
|
th.SessionInfoUpdate, th.SessionInfoUpdate,
|
||||||
|
th.SessionInfoLength, th.SessionInfoLength,
|
||||||
|
th.SessionInfoOffset, th.SessionInfoOffset,
|
||||||
|
th.NumVars, th.NumVars, th.VarHeaderOffset, th.VarHeaderOffset,
|
||||||
|
th.NumBuf, th.NumBuf, th.BufLen, th.BufLen, th.BufOffset, th.BufOffset,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseTelemetryHeader will read the IBT file headers from a correctly sized
|
||||||
|
// buffer.
|
||||||
|
// You need to pass a the first FILE_HEADER_SIZE bytes of the buffer
|
||||||
|
func parseTelemetryHeader(buf [FileHeaderSize]byte) (*TelemetryHeaders, error) {
|
||||||
|
if len(buf)%HeaderSize != 0 {
|
||||||
|
return nil, fmt.Errorf("buffer must be multiple of size: %d", HeaderSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
dst := TelemetryHeaders{}
|
||||||
|
err := binary.Read(bytes.NewBuffer(buf[:]), binary.LittleEndian, &dst)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to unpack data: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &dst, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
// Package IbtParser is all you need for you iRacing telemetry parsing
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ibtFile = "./telemetryFiles/mx5_2016Okayama_full_2024_10_19_22_02_12.ibt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Reader is an interface (??? very useful)
|
||||||
|
type Reader interface {
|
||||||
|
io.Reader
|
||||||
|
io.ReaderAt
|
||||||
|
io.ReadCloser
|
||||||
|
}
|
||||||
|
|
||||||
|
// IBT struct will hold the relevant data for a given IBT file
|
||||||
|
type IBT struct {
|
||||||
|
File Reader // Source of the data
|
||||||
|
Headers *TelemetryHeaders // IBT file Headers
|
||||||
|
SubHeaders *DiskSubHeader // IBT file Sub Headers
|
||||||
|
SessionInfo *SessionInfoYAML // IBT file Session Info
|
||||||
|
Vars *TelemetryVars
|
||||||
|
LastValidData int64
|
||||||
|
Tick int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// Init serves to initialize and get a hold of a IBT struct
|
||||||
|
func Init(f Reader) (*IBT, error) {
|
||||||
|
// Read the header of the file
|
||||||
|
var err error
|
||||||
|
ibt := IBT{
|
||||||
|
File: f,
|
||||||
|
Vars: &TelemetryVars{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the file headers
|
||||||
|
var headerRaw [FileHeaderSize]byte
|
||||||
|
_, err = ibt.File.ReadAt(headerRaw[:], 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to read from file: %v", err)
|
||||||
|
}
|
||||||
|
ibt.Headers, err = parseTelemetryHeader(headerRaw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to read headers from file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the disk sub headers
|
||||||
|
var subheaderRaw [SubHeaderSize]byte
|
||||||
|
_, err = ibt.File.ReadAt(subheaderRaw[:], FileHeaderSize)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to read subheader from file: %v", err)
|
||||||
|
}
|
||||||
|
ibt.SubHeaders, err = parseTelemetrySubHeader(subheaderRaw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to parse subheaders from file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read session info string
|
||||||
|
sessionInfoStringRaw := make([]byte, ibt.Headers.SessionInfoLength)
|
||||||
|
_, err = ibt.File.ReadAt(sessionInfoStringRaw, int64(ibt.Headers.SessionInfoOffset))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Failed to read sessionInfoString from file: %v", err)
|
||||||
|
}
|
||||||
|
ibt.SessionInfo, err = parseSessionInfo(sessionInfoStringRaw)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("Unable to parse Session Info from file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ibt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IBT) readVariablerHeaders() {
|
||||||
|
i.Vars = &TelemetryVars{Vars: make(map[string]Var, i.Headers.NumVars)}
|
||||||
|
|
||||||
|
var k int32
|
||||||
|
for k = 0; k < i.Headers.NumVars; k++ {
|
||||||
|
rbuf := make([]byte, VarSize)
|
||||||
|
|
||||||
|
_, err := i.File.ReadAt(rbuf, int64(i.Headers.VarHeaderOffset+k*VarSize))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var dst IBTVar
|
||||||
|
err = binary.Read(bytes.NewBuffer(rbuf[:]), binary.LittleEndian, &dst)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
v := Var{
|
||||||
|
Type: dst.Type,
|
||||||
|
Offset: dst.Offset,
|
||||||
|
Count: dst.Count,
|
||||||
|
CountAsTime: dst.CountAsTime,
|
||||||
|
Name: strings.TrimLeft(strings.TrimRight(string(dst.Name[:]), "\x00"), "\x00"),
|
||||||
|
Description: strings.TrimLeft(strings.TrimRight(string(dst.Description[:]), "\x00"), "\x00"),
|
||||||
|
Unit: strings.TrimLeft(strings.TrimRight(string(dst.Unit[:]), "\x00"), "\x00"),
|
||||||
|
Value: nil,
|
||||||
|
}
|
||||||
|
|
||||||
|
// fmt.Println(dst.Name)
|
||||||
|
// fmt.Println(v.Name)
|
||||||
|
|
||||||
|
i.Vars.Vars[v.Name] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IBT) readData() error {
|
||||||
|
start := i.Headers.BufOffset + i.Tick*i.Headers.BufLen
|
||||||
|
buf := make([]byte, i.Headers.BufLen)
|
||||||
|
_, err := i.File.ReadAt(buf, int64(start))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range i.Vars.Vars {
|
||||||
|
rbuf := buf[v.Offset : v.Offset+int32(VarTypes[int(v.Type)].Size)]
|
||||||
|
|
||||||
|
// Read the value
|
||||||
|
switch v.Type {
|
||||||
|
case IRSDK_char:
|
||||||
|
v.Value = string(rbuf[0])
|
||||||
|
case IRSDK_bool:
|
||||||
|
v.Value = int(rbuf[0]) > 0
|
||||||
|
case IRSDK_int:
|
||||||
|
v.Value = int(binary.LittleEndian.Uint32(rbuf))
|
||||||
|
case IRSDK_bitField:
|
||||||
|
v.Value = fmt.Sprintf("0x%x", int(binary.LittleEndian.Uint32(rbuf)))
|
||||||
|
case IRSDK_float:
|
||||||
|
v.Value = math.Float32frombits(uint32(binary.LittleEndian.Uint32(rbuf)))
|
||||||
|
case IRSDK_double:
|
||||||
|
v.Value = math.Float64frombits(uint64(binary.LittleEndian.Uint64(rbuf)))
|
||||||
|
}
|
||||||
|
// --------------
|
||||||
|
|
||||||
|
i.Vars.Vars[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
i.Tick++
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *IBT) Update() bool {
|
||||||
|
err := i.readData()
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
log.Fatalf("What happened?\n%v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == io.EOF {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func msToKph(v float32) int {
|
||||||
|
return int((3600 * v) / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
fmt.Println("================== IBT FILE PARSER ==================")
|
||||||
|
|
||||||
|
file, err := os.Open(ibtFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to open IBT file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ibt, err := Init(file)
|
||||||
|
fmt.Printf("%s\n", ibt.Headers.ToString())
|
||||||
|
fmt.Printf("%s", ibt.SubHeaders.ToString())
|
||||||
|
// fmt.Println(ibt.SessionInfo.ToString())
|
||||||
|
|
||||||
|
// last := time.Now().Unix()
|
||||||
|
ibt.readVariablerHeaders()
|
||||||
|
ibt.Update()
|
||||||
|
|
||||||
|
last := time.Now().UnixMilli()
|
||||||
|
for {
|
||||||
|
time.Sleep(time.Second / 60)
|
||||||
|
res := ibt.Update()
|
||||||
|
|
||||||
|
curTime := time.Now().UnixMilli()
|
||||||
|
|
||||||
|
if curTime - last > 250{
|
||||||
|
fmt.Printf(" \r")
|
||||||
|
if val, ok := ibt.Vars.Vars["Speed"]; ok {
|
||||||
|
fmt.Printf("\r%d %d", ibt.Tick/60, msToKph(val.Value.(float32)))
|
||||||
|
} else {
|
||||||
|
fmt.Printf("\r%d %s", ibt.Tick/60, "KEY DOESN'T EXIST")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !res {
|
||||||
|
fmt.Println("\nEnd of file found...")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+324
@@ -0,0 +1,324 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SessionInfoYAML is a string with session info in the IBT file
|
||||||
|
type SessionInfoYAML struct {
|
||||||
|
WeekendInfo struct {
|
||||||
|
TrackName string `yaml:"TrackName"`
|
||||||
|
TrackID int `yaml:"TrackID"`
|
||||||
|
TrackLength string `yaml:"TrackLength"`
|
||||||
|
TrackDisplayName string `yaml:"TrackDisplayName"`
|
||||||
|
TrackDisplayShortName string `yaml:"TrackDisplayShortName"`
|
||||||
|
TrackConfigName string `yaml:"TrackConfigName"`
|
||||||
|
TrackCity string `yaml:"TrackCity"`
|
||||||
|
TrackCountry string `yaml:"TrackCountry"`
|
||||||
|
TrackAltitude string `yaml:"TrackAltitude"`
|
||||||
|
TrackLatitude string `yaml:"TrackLatitude"`
|
||||||
|
TrackLongitude string `yaml:"TrackLongitude"`
|
||||||
|
TrackNorthOffset string `yaml:"TrackNorthOffset"`
|
||||||
|
TrackNumTurns int `yaml:"TrackNumTurns"`
|
||||||
|
TrackPitSpeedLimit string `yaml:"TrackPitSpeedLimit"`
|
||||||
|
TrackType string `yaml:"TrackType"`
|
||||||
|
TrackDirection string `yaml:"TrackDirection"`
|
||||||
|
TrackWeatherType string `yaml:"TrackWeatherType"`
|
||||||
|
TrackSkies string `yaml:"TrackSkies"`
|
||||||
|
TrackSurfaceTemp string `yaml:"TrackSurfaceTemp"`
|
||||||
|
TrackAirTemp string `yaml:"TrackAirTemp"`
|
||||||
|
TrackAirPressure string `yaml:"TrackAirPressure"`
|
||||||
|
TrackWindVel string `yaml:"TrackWindVel"`
|
||||||
|
TrackWindDir string `yaml:"TrackWindDir"`
|
||||||
|
TrackRelativeHumidity string `yaml:"TrackRelativeHumidity"`
|
||||||
|
TrackFogLevel string `yaml:"TrackFogLevel"`
|
||||||
|
TrackCleanup int `yaml:"TrackCleanup"`
|
||||||
|
TrackDynamicTrack int `yaml:"TrackDynamicTrack"`
|
||||||
|
TrackVersion string `yaml:"TrackVersion"`
|
||||||
|
SeriesID int `yaml:"SeriesID"`
|
||||||
|
SeasonID int `yaml:"SeasonID"`
|
||||||
|
SessionID int `yaml:"SessionID"`
|
||||||
|
SubSessionID int `yaml:"SubSessionID"`
|
||||||
|
LeagueID int `yaml:"LeagueID"`
|
||||||
|
Official int `yaml:"Official"`
|
||||||
|
RaceWeek int `yaml:"RaceWeek"`
|
||||||
|
EventType string `yaml:"EventType"`
|
||||||
|
Category string `yaml:"Category"`
|
||||||
|
SimMode string `yaml:"SimMode"`
|
||||||
|
TeamRacing int `yaml:"TeamRacing"`
|
||||||
|
MinDrivers int `yaml:"MinDrivers"`
|
||||||
|
MaxDrivers int `yaml:"MaxDrivers"`
|
||||||
|
DCRuleSet string `yaml:"DCRuleSet"`
|
||||||
|
QualifierMustStartRace int `yaml:"QualifierMustStartRace"`
|
||||||
|
NumCarClasses int `yaml:"NumCarClasses"`
|
||||||
|
NumCarTypes int `yaml:"NumCarTypes"`
|
||||||
|
HeatRacing int `yaml:"HeatRacing"`
|
||||||
|
BuildType string `yaml:"BuildType"`
|
||||||
|
BuildTarget string `yaml:"BuildTarget"`
|
||||||
|
BuildVersion string `yaml:"BuildVersion"`
|
||||||
|
WeekendOptions struct {
|
||||||
|
NumStarters int `yaml:"NumStarters"`
|
||||||
|
StartingGrid string `yaml:"StartingGrid"`
|
||||||
|
QualifyScoring string `yaml:"QualifyScoring"`
|
||||||
|
CourseCautions string `yaml:"CourseCautions"`
|
||||||
|
StandingStart int `yaml:"StandingStart"`
|
||||||
|
ShortParadeLap int `yaml:"ShortParadeLap"`
|
||||||
|
Restarts string `yaml:"Restarts"`
|
||||||
|
WeatherType string `yaml:"WeatherType"`
|
||||||
|
Skies string `yaml:"Skies"`
|
||||||
|
WindDirection string `yaml:"WindDirection"`
|
||||||
|
WindSpeed string `yaml:"WindSpeed"`
|
||||||
|
WeatherTemp string `yaml:"WeatherTemp"`
|
||||||
|
RelativeHumidity string `yaml:"RelativeHumidity"`
|
||||||
|
FogLevel string `yaml:"FogLevel"`
|
||||||
|
TimeOfDay string `yaml:"TimeOfDay"`
|
||||||
|
Date string `yaml:"Date"`
|
||||||
|
EarthRotationSpeedupFactor int `yaml:"EarthRotationSpeedupFactor"`
|
||||||
|
Unofficial int `yaml:"Unofficial"`
|
||||||
|
CommercialMode string `yaml:"CommercialMode"`
|
||||||
|
NightMode string `yaml:"NightMode"`
|
||||||
|
IsFixedSetup int `yaml:"IsFixedSetup"`
|
||||||
|
StrictLapsChecking string `yaml:"StrictLapsChecking"`
|
||||||
|
HasOpenRegistration int `yaml:"HasOpenRegistration"`
|
||||||
|
HardcoreLevel int `yaml:"HardcoreLevel"`
|
||||||
|
NumJokerLaps int `yaml:"NumJokerLaps"`
|
||||||
|
IncidentLimit string `yaml:"IncidentLimit"`
|
||||||
|
FastRepairsLimit string `yaml:"FastRepairsLimit"`
|
||||||
|
GreenWhiteCheckeredLimit int `yaml:"GreenWhiteCheckeredLimit"`
|
||||||
|
} `yaml:"WeekendOptions"`
|
||||||
|
TelemetryOptions struct {
|
||||||
|
TelemetryDiskFile string `yaml:"TelemetryDiskFile"`
|
||||||
|
} `yaml:"TelemetryOptions"`
|
||||||
|
} `yaml:"WeekendInfo"`
|
||||||
|
SessionInfo struct {
|
||||||
|
Sessions []struct {
|
||||||
|
SessionNum int `yaml:"SessionNum"`
|
||||||
|
SessionLaps string `yaml:"SessionLaps"`
|
||||||
|
SessionTime string `yaml:"SessionTime"`
|
||||||
|
SessionNumLapsToAvg int `yaml:"SessionNumLapsToAvg"`
|
||||||
|
SessionType string `yaml:"SessionType"`
|
||||||
|
SessionTrackRubberState string `yaml:"SessionTrackRubberState"`
|
||||||
|
SessionName string `yaml:"SessionName"`
|
||||||
|
SessionSubType interface{} `yaml:"SessionSubType"`
|
||||||
|
SessionSkipped int `yaml:"SessionSkipped"`
|
||||||
|
SessionRunGroupsUsed int `yaml:"SessionRunGroupsUsed"`
|
||||||
|
ResultsPositions interface{} `yaml:"ResultsPositions"`
|
||||||
|
ResultsFastestLap []struct {
|
||||||
|
CarIdx int `yaml:"CarIdx"`
|
||||||
|
FastestLap int `yaml:"FastestLap"`
|
||||||
|
FastestTime int `yaml:"FastestTime"`
|
||||||
|
} `yaml:"ResultsFastestLap"`
|
||||||
|
ResultsAverageLapTime int `yaml:"ResultsAverageLapTime"`
|
||||||
|
ResultsNumCautionFlags int `yaml:"ResultsNumCautionFlags"`
|
||||||
|
ResultsNumCautionLaps int `yaml:"ResultsNumCautionLaps"`
|
||||||
|
ResultsNumLeadChanges int `yaml:"ResultsNumLeadChanges"`
|
||||||
|
ResultsLapsComplete int `yaml:"ResultsLapsComplete"`
|
||||||
|
ResultsOfficial int `yaml:"ResultsOfficial"`
|
||||||
|
} `yaml:"Sessions"`
|
||||||
|
} `yaml:"SessionInfo"`
|
||||||
|
CameraInfo struct {
|
||||||
|
Groups []struct {
|
||||||
|
GroupNum int `yaml:"GroupNum"`
|
||||||
|
GroupName string `yaml:"GroupName"`
|
||||||
|
Cameras []struct {
|
||||||
|
CameraNum int `yaml:"CameraNum"`
|
||||||
|
CameraName string `yaml:"CameraName"`
|
||||||
|
} `yaml:"Cameras"`
|
||||||
|
IsScenic bool `yaml:"IsScenic,omitempty"`
|
||||||
|
} `yaml:"Groups"`
|
||||||
|
} `yaml:"CameraInfo"`
|
||||||
|
RadioInfo struct {
|
||||||
|
SelectedRadioNum int `yaml:"SelectedRadioNum"`
|
||||||
|
Radios []struct {
|
||||||
|
RadioNum int `yaml:"RadioNum"`
|
||||||
|
HopCount int `yaml:"HopCount"`
|
||||||
|
NumFrequencies int `yaml:"NumFrequencies"`
|
||||||
|
TunedToFrequencyNum int `yaml:"TunedToFrequencyNum"`
|
||||||
|
ScanningIsOn int `yaml:"ScanningIsOn"`
|
||||||
|
Frequencies []struct {
|
||||||
|
FrequencyNum int `yaml:"FrequencyNum"`
|
||||||
|
FrequencyName string `yaml:"FrequencyName"`
|
||||||
|
Priority int `yaml:"Priority"`
|
||||||
|
CarIdx int `yaml:"CarIdx"`
|
||||||
|
EntryIdx int `yaml:"EntryIdx"`
|
||||||
|
ClubID int `yaml:"ClubID"`
|
||||||
|
CanScan int `yaml:"CanScan"`
|
||||||
|
CanSquawk int `yaml:"CanSquawk"`
|
||||||
|
Muted int `yaml:"Muted"`
|
||||||
|
IsMutable int `yaml:"IsMutable"`
|
||||||
|
IsDeletable int `yaml:"IsDeletable"`
|
||||||
|
} `yaml:"Frequencies"`
|
||||||
|
} `yaml:"Radios"`
|
||||||
|
} `yaml:"RadioInfo"`
|
||||||
|
DriverInfo struct {
|
||||||
|
DriverCarIdx int `yaml:"DriverCarIdx"`
|
||||||
|
DriverUserID int `yaml:"DriverUserID"`
|
||||||
|
PaceCarIdx int `yaml:"PaceCarIdx"`
|
||||||
|
DriverHeadPosX float64 `yaml:"DriverHeadPosX"`
|
||||||
|
DriverHeadPosY float64 `yaml:"DriverHeadPosY"`
|
||||||
|
DriverHeadPosZ float64 `yaml:"DriverHeadPosZ"`
|
||||||
|
DriverCarIdleRPM float64 `yaml:"DriverCarIdleRPM"`
|
||||||
|
DriverCarRedLine float64 `yaml:"DriverCarRedLine"`
|
||||||
|
DriverCarEngCylinderCount int `yaml:"DriverCarEngCylinderCount"`
|
||||||
|
DriverCarFuelKgPerLtr float64 `yaml:"DriverCarFuelKgPerLtr"`
|
||||||
|
DriverCarFuelMaxLtr float64 `yaml:"DriverCarFuelMaxLtr"`
|
||||||
|
DriverCarMaxFuelPct float64 `yaml:"DriverCarMaxFuelPct"`
|
||||||
|
DriverCarGearNumForward int `yaml:"DriverCarGearNumForward"`
|
||||||
|
DriverCarGearNeutral int `yaml:"DriverCarGearNeutral"`
|
||||||
|
DriverCarGearReverse int `yaml:"DriverCarGearReverse"`
|
||||||
|
DriverCarSLFirstRPM float64 `yaml:"DriverCarSLFirstRPM"`
|
||||||
|
DriverCarSLShiftRPM float64 `yaml:"DriverCarSLShiftRPM"`
|
||||||
|
DriverCarSLLastRPM float64 `yaml:"DriverCarSLLastRPM"`
|
||||||
|
DriverCarSLBlinkRPM float64 `yaml:"DriverCarSLBlinkRPM"`
|
||||||
|
DriverCarVersion string `yaml:"DriverCarVersion"`
|
||||||
|
DriverPitTrkPct float64 `yaml:"DriverPitTrkPct"`
|
||||||
|
DriverCarEstLapTime float64 `yaml:"DriverCarEstLapTime"`
|
||||||
|
DriverSetupName string `yaml:"DriverSetupName"`
|
||||||
|
DriverSetupIsModified int `yaml:"DriverSetupIsModified"`
|
||||||
|
DriverSetupLoadTypeName string `yaml:"DriverSetupLoadTypeName"`
|
||||||
|
DriverSetupPassedTech int `yaml:"DriverSetupPassedTech"`
|
||||||
|
DriverIncidentCount int `yaml:"DriverIncidentCount"`
|
||||||
|
Drivers []Driver `yaml:"Drivers"`
|
||||||
|
} `yaml:"DriverInfo"`
|
||||||
|
SplitTimeInfo struct {
|
||||||
|
Sectors []struct {
|
||||||
|
SectorNum int `yaml:"SectorNum"`
|
||||||
|
SectorStartPct float64 `yaml:"SectorStartPct"`
|
||||||
|
} `yaml:"Sectors"`
|
||||||
|
} `yaml:"SplitTimeInfo"`
|
||||||
|
CarSetup struct {
|
||||||
|
UpdateCount int `yaml:"UpdateCount"`
|
||||||
|
TiresAero struct {
|
||||||
|
LeftFront struct {
|
||||||
|
StartingPressure string `yaml:"StartingPressure"`
|
||||||
|
LastHotPressure string `yaml:"LastHotPressure"`
|
||||||
|
LastTempsOMI string `yaml:"LastTempsOMI"`
|
||||||
|
TreadRemaining string `yaml:"TreadRemaining"`
|
||||||
|
} `yaml:"LeftFront"`
|
||||||
|
LeftRear struct {
|
||||||
|
StartingPressure string `yaml:"StartingPressure"`
|
||||||
|
LastHotPressure string `yaml:"LastHotPressure"`
|
||||||
|
LastTempsOMI string `yaml:"LastTempsOMI"`
|
||||||
|
TreadRemaining string `yaml:"TreadRemaining"`
|
||||||
|
} `yaml:"LeftRear"`
|
||||||
|
RightFront struct {
|
||||||
|
StartingPressure string `yaml:"StartingPressure"`
|
||||||
|
LastHotPressure string `yaml:"LastHotPressure"`
|
||||||
|
LastTempsIMO string `yaml:"LastTempsIMO"`
|
||||||
|
TreadRemaining string `yaml:"TreadRemaining"`
|
||||||
|
} `yaml:"RightFront"`
|
||||||
|
RightRear struct {
|
||||||
|
StartingPressure string `yaml:"StartingPressure"`
|
||||||
|
LastHotPressure string `yaml:"LastHotPressure"`
|
||||||
|
LastTempsIMO string `yaml:"LastTempsIMO"`
|
||||||
|
TreadRemaining string `yaml:"TreadRemaining"`
|
||||||
|
} `yaml:"RightRear"`
|
||||||
|
} `yaml:"TiresAero"`
|
||||||
|
Chassis struct {
|
||||||
|
Front struct {
|
||||||
|
ArbSetting int `yaml:"ArbSetting"`
|
||||||
|
ToeIn string `yaml:"ToeIn"`
|
||||||
|
FuelLevel string `yaml:"FuelLevel"`
|
||||||
|
CrossWeight string `yaml:"CrossWeight"`
|
||||||
|
} `yaml:"Front"`
|
||||||
|
LeftFront struct {
|
||||||
|
CornerWeight string `yaml:"CornerWeight"`
|
||||||
|
RideHeight string `yaml:"RideHeight"`
|
||||||
|
SpringPerchOffset string `yaml:"SpringPerchOffset"`
|
||||||
|
Camber string `yaml:"Camber"`
|
||||||
|
} `yaml:"LeftFront"`
|
||||||
|
LeftRear struct {
|
||||||
|
CornerWeight string `yaml:"CornerWeight"`
|
||||||
|
RideHeight string `yaml:"RideHeight"`
|
||||||
|
SpringPerchOffset string `yaml:"SpringPerchOffset"`
|
||||||
|
Camber string `yaml:"Camber"`
|
||||||
|
ToeIn string `yaml:"ToeIn"`
|
||||||
|
} `yaml:"LeftRear"`
|
||||||
|
InCarDials struct {
|
||||||
|
DisplayPage string `yaml:"DisplayPage"`
|
||||||
|
BrakePressureBias string `yaml:"BrakePressureBias"`
|
||||||
|
} `yaml:"InCarDials"`
|
||||||
|
RightFront struct {
|
||||||
|
CornerWeight string `yaml:"CornerWeight"`
|
||||||
|
RideHeight string `yaml:"RideHeight"`
|
||||||
|
SpringPerchOffset string `yaml:"SpringPerchOffset"`
|
||||||
|
Camber string `yaml:"Camber"`
|
||||||
|
} `yaml:"RightFront"`
|
||||||
|
RightRear struct {
|
||||||
|
CornerWeight string `yaml:"CornerWeight"`
|
||||||
|
RideHeight string `yaml:"RideHeight"`
|
||||||
|
SpringPerchOffset string `yaml:"SpringPerchOffset"`
|
||||||
|
Camber string `yaml:"Camber"`
|
||||||
|
ToeIn string `yaml:"ToeIn"`
|
||||||
|
} `yaml:"RightRear"`
|
||||||
|
Rear struct {
|
||||||
|
ArbSetting int `yaml:"ArbSetting"`
|
||||||
|
WingSetting int `yaml:"WingSetting"`
|
||||||
|
} `yaml:"Rear"`
|
||||||
|
} `yaml:"Chassis"`
|
||||||
|
} `yaml:"CarSetup"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Driver ...
|
||||||
|
type Driver struct {
|
||||||
|
CarIdx int `yaml:"CarIdx"`
|
||||||
|
UserName string `yaml:"UserName"`
|
||||||
|
AbbrevName string `yaml:"AbbrevName"`
|
||||||
|
Initials string `yaml:"Initials"`
|
||||||
|
UserID int `yaml:"UserID"`
|
||||||
|
TeamID int `yaml:"TeamID"`
|
||||||
|
TeamName string `yaml:"TeamName"`
|
||||||
|
CarNumber string `yaml:"CarNumber"`
|
||||||
|
CarNumberRaw int `yaml:"CarNumberRaw"`
|
||||||
|
CarPath string `yaml:"CarPath"`
|
||||||
|
CarClassID int `yaml:"CarClassID"`
|
||||||
|
CarID int `yaml:"CarID"`
|
||||||
|
CarIsPaceCar int `yaml:"CarIsPaceCar"`
|
||||||
|
CarIsAI int `yaml:"CarIsAI"`
|
||||||
|
CarScreenName string `yaml:"CarScreenName"`
|
||||||
|
CarScreenNameShort string `yaml:"CarScreenNameShort"`
|
||||||
|
CarClassShortName string `yaml:"CarClassShortName"`
|
||||||
|
CarClassRelSpeed int `yaml:"CarClassRelSpeed"`
|
||||||
|
CarClassLicenseLevel int `yaml:"CarClassLicenseLevel"`
|
||||||
|
CarClassMaxFuelPct string `yaml:"CarClassMaxFuelPct"`
|
||||||
|
CarClassWeightPenalty string `yaml:"CarClassWeightPenalty"`
|
||||||
|
CarClassPowerAdjust string `yaml:"CarClassPowerAdjust"`
|
||||||
|
CarClassDryTireSetLimit string `yaml:"CarClassDryTireSetLimit"`
|
||||||
|
CarClassColor int `yaml:"CarClassColor"`
|
||||||
|
CarClassEstLapTime float64 `yaml:"CarClassEstLapTime"`
|
||||||
|
IRating int `yaml:"IRating"`
|
||||||
|
LicLevel int `yaml:"LicLevel"`
|
||||||
|
LicSubLevel int `yaml:"LicSubLevel"`
|
||||||
|
LicString string `yaml:"LicString"`
|
||||||
|
LicColor string `yaml:"LicColor"`
|
||||||
|
IsSpectator int `yaml:"IsSpectator"`
|
||||||
|
CarDesignStr string `yaml:"CarDesignStr"`
|
||||||
|
HelmetDesignStr string `yaml:"HelmetDesignStr"`
|
||||||
|
SuitDesignStr string `yaml:"SuitDesignStr"`
|
||||||
|
CarNumberDesignStr string `yaml:"CarNumberDesignStr"`
|
||||||
|
CarSponsor1 int `yaml:"CarSponsor_1"`
|
||||||
|
CarSponsor2 int `yaml:"CarSponsor_2"`
|
||||||
|
CurDriverIncidentCount int `yaml:"CurDriverIncidentCount"`
|
||||||
|
TeamIncidentCount int `yaml:"TeamIncidentCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseSessionInfo will parse the sessionInfo buffer into the SessionInfoYAML
|
||||||
|
// struct
|
||||||
|
func parseSessionInfo(data []byte) (*SessionInfoYAML, error) {
|
||||||
|
var sessionInfo SessionInfoYAML
|
||||||
|
err := yaml.Unmarshal(data, &sessionInfo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &sessionInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToString will return a readable string of the struct
|
||||||
|
func (s *SessionInfoYAML) ToString() string {
|
||||||
|
stringified, _ := json.MarshalIndent(s, "", " ")
|
||||||
|
return string(stringified)
|
||||||
|
}
|
||||||
|
|
||||||
+167
@@ -0,0 +1,167 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
VarSize = 144
|
||||||
|
IRSDK_char = 0
|
||||||
|
IRSDK_bool = 1
|
||||||
|
IRSDK_int = 2
|
||||||
|
IRSDK_bitField = 3
|
||||||
|
IRSDK_float = 4
|
||||||
|
IRSDK_double = 5
|
||||||
|
)
|
||||||
|
|
||||||
|
// I think I can make an interface if IRSDK types with available types and
|
||||||
|
// that they need a parser (reads and type coerces I guess)
|
||||||
|
var (
|
||||||
|
VarTypes = map[int]VarType{
|
||||||
|
IRSDK_char: {1, "irsdk_char"},
|
||||||
|
IRSDK_bool: {1, "irsdk_bool"},
|
||||||
|
IRSDK_int: {4, "irsdk_int"},
|
||||||
|
IRSDK_bitField: {4, "irsdk_bitField"},
|
||||||
|
IRSDK_float: {4, "irsdk_float"},
|
||||||
|
IRSDK_double: {8, "irsdk_double"},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
type VarType struct {
|
||||||
|
Size int // Size is the var type size in bytes
|
||||||
|
Name string // Name is the irsdk var name
|
||||||
|
}
|
||||||
|
|
||||||
|
type IBTVar struct {
|
||||||
|
Type int32
|
||||||
|
Offset int32
|
||||||
|
Count int32
|
||||||
|
CountAsTime bool
|
||||||
|
Padding [0]byte
|
||||||
|
Name [32]byte
|
||||||
|
Description [64]byte
|
||||||
|
Unit [32]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Var struct {
|
||||||
|
Type int32
|
||||||
|
Offset int32
|
||||||
|
Count int32
|
||||||
|
CountAsTime bool
|
||||||
|
Name string
|
||||||
|
Description string
|
||||||
|
Unit string
|
||||||
|
// TODO
|
||||||
|
// Create an interface for this value
|
||||||
|
// Represent the IRSDK var types with a struct each that implements the Parse
|
||||||
|
// method or something like that I guess
|
||||||
|
Value interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *IBTVar) ToString() string {
|
||||||
|
return fmt.Sprintf(
|
||||||
|
"Type: %5d (0x%08x)\n"+
|
||||||
|
"Offset: %5d (0x%08x)\n"+
|
||||||
|
"Count: %5d (0x%08x)\n"+
|
||||||
|
"CountAsTime: %5t\n"+
|
||||||
|
"Name: %s\n"+
|
||||||
|
"Description: %s\n"+
|
||||||
|
"Unit: %s",
|
||||||
|
v.Type, v.Type, v.Offset, v.Offset, v.Count, v.Count,
|
||||||
|
v.CountAsTime, v.Name, v.Description, v.Unit,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
type varBuffer struct {
|
||||||
|
tickCount int
|
||||||
|
bufOffset int
|
||||||
|
}
|
||||||
|
|
||||||
|
type TelemetryVars struct {
|
||||||
|
LastVersion int
|
||||||
|
Vars map[string]Var
|
||||||
|
}
|
||||||
|
|
||||||
|
// func findLatestBuffer(i *IBT) varBuffer {
|
||||||
|
// var vb varBuffer
|
||||||
|
// foundTickCount := 0
|
||||||
|
// for k := 0; k < int(i.Headers.NumBuf); k++ {
|
||||||
|
// rbuf := make([]byte, 16)
|
||||||
|
// _, err := i.File.ReadAt(rbuf, int64(48+k*16))
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// currentVb := varBuffer{
|
||||||
|
// int(binary.LittleEndian.Uint32(rbuf[0:4])),
|
||||||
|
// int(binary.LittleEndian.Uint32(rbuf[4:8])),
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if foundTickCount < currentVb.tickCount {
|
||||||
|
// foundTickCount = currentVb.tickCount
|
||||||
|
// vb = currentVb
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return vb
|
||||||
|
// }
|
||||||
|
|
||||||
|
// func (i *IBT) parseVariableHeaders(offset int32) (int32, error) {
|
||||||
|
// if i.Vars.Vars == nil {
|
||||||
|
// i.Vars.Vars = make(map[string]*Variable, i.Headers.NumVars)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// var size int32 = 0
|
||||||
|
// for k := range i.Headers.NumVars {
|
||||||
|
// start := k * VarSize + offset
|
||||||
|
// size += start
|
||||||
|
//
|
||||||
|
// buf := make([]byte, VarSize)
|
||||||
|
// _, err := i.File.ReadAt(buf, int64(start))
|
||||||
|
// if err != nil {
|
||||||
|
// return 0, err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// newVar, err := parseVariable(buf)
|
||||||
|
// i.Vars.Vars[string(newVar.Name[:])] = newVar
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return size, nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// This function will read a single variable
|
||||||
|
// func parseVariable(buf []byte) (*Variable, error) {
|
||||||
|
// if len(buf)%VarSize != 0 {
|
||||||
|
// return nil, fmt.Errorf("buffer must be multiple of size: %d", VarSize)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// dst := Variable{}
|
||||||
|
// err := binary.Read(bytes.NewBuffer(buf[:]), binary.LittleEndian, &dst)
|
||||||
|
// if err != nil {
|
||||||
|
// return nil, err
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return &dst, nil
|
||||||
|
// }
|
||||||
|
|
||||||
|
// this function will read the variables
|
||||||
|
// func parseVariables(i *IBT) error {
|
||||||
|
// i.parseVariableHeaders(0)
|
||||||
|
// vb := findLatestBuffer(i)
|
||||||
|
// fmt.Printf("%+v\n", vb)
|
||||||
|
// if i.Vars.LastVersion < vb.tickCount {
|
||||||
|
// // Then we have new data
|
||||||
|
// i.Vars.LastVersion = vb.tickCount
|
||||||
|
// i.LastValidData = time.Now().Unix()
|
||||||
|
// for _, v := range i.Vars.Vars {
|
||||||
|
// // fmt.Printf("%s\n", v.ToString())
|
||||||
|
// rbuf := make([]byte, VarTypes[int(v.Type)].Size)
|
||||||
|
// _, err := i.File.ReadAt(rbuf, int64(vb.bufOffset + int(v.Offset)))
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatalf("Reading values: %v\n", err)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return nil
|
||||||
|
// }
|
||||||
Reference in New Issue
Block a user