27 Commits
Author SHA1 Message Date
esilva 45a0aa43aa This was on the wrong place 2025-01-06 18:29:45 +00:00
esilva 38e7ea32cc Add the method for SessionInfo export 2025-01-06 18:01:56 +00:00
esilva bcbffdc7a2 Some miscellanious stuff 2025-01-06 17:52:12 +00:00
esilva 2dc1771bb6 Fixed the file exporting for live telemetry data 2025-01-06 17:46:14 +00:00
esilva f925cede6d Add the ability to export to an IBT file 2025-01-03 14:31:25 +00:00
esilva 300c86190b Updated this to use the package 2024-11-05 18:05:27 +00:00
esilva 5fa39f2935 Merge branch 'master' into unittesting 2024-11-05 18:02:59 +00:00
esilva 423b8dcc22 Move this into its own package 2024-11-05 17:54:41 +00:00
esilva b6aaaab8b2 Merge branch 'master' of ssh://esilva.org:666/home/esilva/privGit/ibtReader 2024-11-05 15:51:13 +00:00
esilva 0f33e19558 Added the README 2024-11-05 15:50:52 +00:00
esilva 57d006c0e7 Addig the PDF document I found with some data 2024-11-03 22:57:24 +00:00
esilva 23a7efc37d Moved the check for EOF on the Update method 2024-11-03 22:54:40 +00:00
esilva 7a1b128d6b Got the constants from the CPP SDK and will comb trough them later 2024-11-02 16:03:34 +00:00
esilva 2e1b811a80 Forgot to add the cmp pacakge 2024-11-02 16:02:33 +00:00
esilva 6d03e46b44 Added the test for the disksubheaders and fixed the headers one 2024-11-02 16:02:21 +00:00
esilva f8149d687d Merge branch 'master' into unittesting 2024-11-02 15:38:04 +00:00
esilva dcfaec6d63 Moved the mstokph function to a subpackage of its own (conversions) 2024-11-02 15:36:56 +00:00
esilva b66606fc6c Added the gitignore 2024-11-02 15:33:50 +00:00
esilva e153a2efb1 Added a telemetry file to use for testing stuff and whatnot 2024-11-02 15:33:25 +00:00
esilva b2a3e2f97c Added a unittest for the header parsing 2024-11-01 22:42:33 +00:00
esilva 4c795c49c4 Removed the check for the length of header size because its a fixed size buf 2024-11-01 22:38:15 +00:00
esilva 7ce4d0ed4b Added the Linux and Darwin stubs for the winutils package
This means that the livedata bit of the code is only available on
windows
2024-10-30 23:26:29 +00:00
esilva 422d90e98a The live data also works now, but I have some refactoring to do 2024-10-29 14:52:40 +00:00
esilva dbf30c1d4e Fixed the width, its only for the looks 2024-10-29 14:52:21 +00:00
esilva 78a2512410 Code now runs on Linux for local files
I still need to add the support for message broadcasting tho, it should
not be that hard
2024-10-28 12:15:16 +00:00
esilva d48a6fa24a SessionInfo parsing and another stuff 2024-10-27 23:25:28 +00:00
esilva 2bc756a2f0 Rolled back some stuff here like the package stuff
This will add support to view live data
2024-10-27 22:48:32 +00:00
25 changed files with 2048 additions and 772 deletions
+2
View File
@@ -0,0 +1,2 @@
coverage*
*.txt
+3
View File
@@ -0,0 +1,3 @@
test:
go test -coverprofile=coverage.out ./... -cover -bench=
go tool cover -html=coverage.out -o coverage.html
+17
View File
@@ -0,0 +1,17 @@
# About
This project will be able to parse `.ibt` files, read live data from races and
broadcast messages to the service. Its still not very mature at all, but after
this commit I will turn it into a package instead and give it a stable API.
Will also put some examples then.
## The SDK
I used various sources to develop and understand how `iRacing` works, and learn
a lot with it. Once I have matured this project a bit I will document its
inner workings too.
## SharedMem
I vendored in the code from [hidez8891/shm](https://github.com/hidez8891/shm)
since the repo has been archived. I took the opportunity to update some of its
code.
+252
View File
@@ -0,0 +1,252 @@
package goirsdk
type Msg struct {
Cmd int
P1 int32
P2 int32
P3 int32
}
const (
SimStatusUrl string = "http://127.0.0.1:32034/get_sim_status?object=simStatus"
IRSDK_DATAVALIDEVENTNAME string = "Local\\IRSDKDataValidEvent"
IRSDK_MEMMAPFILENAME string = "Local\\IRSDKMemMapFileName"
IRSDK_BROADCASTMSGNAME string = "IRSDK_BROADCASTMSG"
fileMapSize uint32 = 1164 * 1024
connTimeout int64 = 30
stConnected int = 1
)
const (
BroadcastCamSwitchPos int = 0 // car position, group, camera
BroadcastCamSwitchNum int = 1 // driver #, group, camera
BroadcastCamSetState int = 2 // irsdk_CameraState, unused, unused
BroadcastReplaySetPlaySpeed int = 3 // speed, slowMotion, unused
BroadcastReplaySetPlayPosition int = 4 // irsdk_RpyPosMode, Frame Number (high, low)
BroadcastReplaySearch int = 5 // irsdk_RpySrchMode, unused, unused
BroadcastReplaySetState int = 6 // irsdk_RpyStateMode, unused, unused
BroadcastReloadTextures int = 7 // irsdk_ReloadTexturesMode, carIdx, unused
BroadcastChatComand int = 8 // irsdk_ChatCommandMode, subCommand, unused
BroadcastPitCommand int = 9 // irsdk_PitCommandMode, parameter
BroadcastTelemCommand int = 10 // irsdk_TelemCommandMode, unused, unused
BroadcastFFBCommand int = 11 // irsdk_FFBCommandMode, value (float, high, low)
BroadcastReplaySearchSessionTime int = 12 // sessionNum, sessionTimeMS (high, low)
BroadcastLast int = 13 // unused placeholder
)
const (
ChatCommandMacro int = 0 // pass in a number from 1-15 representing the chat macro to launch
ChatCommandBeginChat int = 1 // Open up a new chat window
ChatCommandReply int = 2 // Reply to last private chat
ChatCommandCancel int = 3 // Close chat window
)
// this only works when the driver is in the car
const (
PitCommandClear int = 0 // Clear all pit checkboxes
PitCommandWS int = 1 // Clean the winshield, using one tear off
PitCommandFuel int = 2 // Add fuel, optionally specify the amount to add in liters or pass '0' to use existing amount
PitCommandLF int = 3 // Change the left front tire, optionally specifying the pressure in KPa or pass '0' to use existing pressure
PitCommandRF int = 4 // right front
PitCommandLR int = 5 // left rear
PitCommandRR int = 6 // right rear
PitCommandClearTires int = 7 // Clear tire pit checkboxes
PitCommandFR int = 8 // Request a fast repair
PitCommandClearWS int = 9 // Uncheck Clean the winshield checkbox
PitCommandClearFR int = 10 // Uncheck request a fast repair
PitCommandClearFuel int = 11 // Uncheck add fuel
)
// You can call this any time, but telemtry only records when driver is in there car
const (
TelemCommandStop int = 0 // Turn telemetry recording off
TelemCommandStart int = 1 // Turn telemetry recording on
TelemCommandRestart int = 2 // Write current file to disk and start a new one
)
const (
RpyStateEraseTape int = 0 // clear any data in the replay tape
RpyStateLast int = 1 // unused place holder
)
const (
ReloadTexturesAll int = 0 // reload all textuers
ReloadTexturesCarIdx int = 1 // reload only textures for the specific carIdx
)
// Search replay tape for events
const (
RpySrchToStart int = 0
RpySrchToEnd int = 1
RpySrchPrevSession int = 2
RpySrchNextSession int = 3
RpySrchPrevLap int = 4
RpySrchNextLap int = 5
RpySrchPrevFrame int = 6
RpySrchNextFrame int = 7
RpySrchPrevIncident int = 8
RpySrchNextIncident int = 9
RpySrchLast int = 10 // unused placeholder
)
const (
RpyPosBegin int = 0
RpyPosCurrent int = 1
RpyPosEnd int = 2
RpyPosLast int = 3 // unused placeholder
)
// You can call this any time
const (
FFBCommandMaxForce int = 0 // Set the maximum force when mapping steering torque force to direct input units (float in Nm)
FFBCommandLast int = 1 // unused placeholder
)
// irsdk_BroadcastCamSwitchPos or irsdk_BroadcastCamSwitchNum camera focus defines
// pass these in for the first parameter to select the 'focus at' types in the camera system.
const (
csFocusAtIncident int = -3
csFocusAtLeader int = -2
csFocusAtExiting int = -1
csFocusAtDriver int = 0 // ctFocusAtDriver + car number...
)
// Camera positions
const (
// CamNose
)
// Some other constants that I need to get trough
// // bit fields
// enum irsdk_EngineWarnings
// {
// irsdk_waterTempWarning = 0x01,
// irsdk_fuelPressureWarning = 0x02,
// irsdk_oilPressureWarning = 0x04,
// irsdk_engineStalled = 0x08,
// irsdk_pitSpeedLimiter = 0x10,
// irsdk_revLimiterActive = 0x20,
// };
//
// // global flags
// enum irsdk_Flags
// {
// // global flags
// irsdk_checkered = 0x00000001,
// irsdk_white = 0x00000002,
// irsdk_green = 0x00000004,
// irsdk_yellow = 0x00000008,
// irsdk_red = 0x00000010,
// irsdk_blue = 0x00000020,
// irsdk_debris = 0x00000040,
// irsdk_crossed = 0x00000080,
// irsdk_yellowWaving = 0x00000100,
// irsdk_oneLapToGreen = 0x00000200,
// irsdk_greenHeld = 0x00000400,
// irsdk_tenToGo = 0x00000800,
// irsdk_fiveToGo = 0x00001000,
// irsdk_randomWaving = 0x00002000,
// irsdk_caution = 0x00004000,
// irsdk_cautionWaving = 0x00008000,
//
// // drivers black flags
// irsdk_black = 0x00010000,
// irsdk_disqualify = 0x00020000,
// irsdk_servicible = 0x00040000, // car is allowed service (not a flag)
// irsdk_furled = 0x00080000,
// irsdk_repair = 0x00100000,
//
// // start lights
// irsdk_startHidden = 0x10000000,
// irsdk_startReady = 0x20000000,
// irsdk_startSet = 0x40000000,
// irsdk_startGo = 0x80000000,
// };
//
//
// // status
// enum irsdk_TrkLoc
// {
// irsdk_NotInWorld = -1,
// irsdk_OffTrack,
// irsdk_InPitStall,
// irsdk_AproachingPits,
// irsdk_OnTrack
// };
//
// enum irsdk_TrkSurf
// {
// irsdk_SurfaceNotInWorld = -1,
// irsdk_UndefinedMaterial = 0,
//
// irsdk_Asphalt1Material,
// irsdk_Asphalt2Material,
// irsdk_Asphalt3Material,
// irsdk_Asphalt4Material,
// irsdk_Concrete1Material,
// irsdk_Concrete2Material,
// irsdk_RacingDirt1Material,
// irsdk_RacingDirt2Material,
// irsdk_Paint1Material,
// irsdk_Paint2Material,
// irsdk_Rumble1Material,
// irsdk_Rumble2Material,
// irsdk_Rumble3Material,
// irsdk_Rumble4Material,
//
// irsdk_Grass1Material,
// irsdk_Grass2Material,
// irsdk_Grass3Material,
// irsdk_Grass4Material,
// irsdk_Dirt1Material,
// irsdk_Dirt2Material,
// irsdk_Dirt3Material,
// irsdk_Dirt4Material,
// irsdk_SandMaterial,
// irsdk_Gravel1Material,
// irsdk_Gravel2Material,
// irsdk_GrasscreteMaterial,
// irsdk_AstroturfMaterial,
// };
//
// enum irsdk_SessionState
// {
// irsdk_StateInvalid,
// irsdk_StateGetInCar,
// irsdk_StateWarmup,
// irsdk_StateParadeLaps,
// irsdk_StateRacing,
// irsdk_StateCheckered,
// irsdk_StateCoolDown
// };
//
// enum irsdk_CameraState
// {
// irsdk_IsSessionScreen = 0x0001, // the camera tool can only be activated if viewing the session screen (out of car)
// irsdk_IsScenicActive = 0x0002, // the scenic camera is active (no focus car)
//
// //these can be changed with a broadcast message
// irsdk_CamToolActive = 0x0004,
// irsdk_UIHidden = 0x0008,
// irsdk_UseAutoShotSelection = 0x0010,
// irsdk_UseTemporaryEdits = 0x0020,
// irsdk_UseKeyAcceleration = 0x0040,
// irsdk_UseKey10xAcceleration = 0x0080,
// irsdk_UseMouseAimMode = 0x0100
// };
//
// enum irsdk_PitSvFlags
// {
// irsdk_LFTireChange = 0x0001,
// irsdk_RFTireChange = 0x0002,
// irsdk_LRTireChange = 0x0004,
// irsdk_RRTireChange = 0x0008,
//
// irsdk_FuelFill = 0x0010,
// irsdk_WindshieldTearoff = 0x0020,
// irsdk_FastRepair = 0x0040
// };
//----
//
+5
View File
@@ -0,0 +1,5 @@
package conversions
func MsToKph(v float32) int {
return int((3600 * v) / 1000)
}
+49 -47
View File
@@ -1,47 +1,49 @@
package ibtReader package goirsdk
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
) )
const ( const (
SubHeaderSize = 32 // SubHeaderSize is the size of the subheader SubHeaderSize = 32 // SubHeaderSize is the size of the subheader
) )
// DiskSubHeader represents the IBT sub headers // DiskSubHeader represents the IBT sub headers
type DiskSubHeader struct { type DiskSubHeader struct {
StartDate int64 // StartDate represents the start date of the telemetry StartDate int64 // StartDate represents the start date of the telemetry
StartTime float64 // StartTime of file relative to start of session StartTime float64 // StartTime of file relative to start of session
EndTime float64 // EndTime of file relative to start of session EndTime float64 // EndTime of file relative to start of session
LapCount int32 // LapCount represents the total number laps LapCount int32 // LapCount represents the total number laps
RecordCount int32 // RecordCount holds the number of data frames RecordCount int32 // RecordCount holds the number of data frames
} }
// parseTelemetrySubHeader will return a pointer to a DiskSubHeader variable // parseTelemetrySubHeader will return a pointer to a DiskSubHeader variable
// or nil if an error occurs. In which case the error return value is more // or nil if an error occurs. In which case the error return value is more
// valuable // valuable
func parseTelemetrySubHeader(buf [SubHeaderSize]byte) (*DiskSubHeader, error) { func parseTelemetrySubHeader(buf [SubHeaderSize]byte) (*DiskSubHeader, error) {
dst := DiskSubHeader{} // utils.HexDump(buf[:])
err := binary.Read(bytes.NewBuffer(buf[:]), binary.LittleEndian, &dst)
if err != nil { dst := DiskSubHeader{}
return nil, err err := binary.Read(bytes.NewBuffer(buf[:]), binary.LittleEndian, &dst)
} if err != nil {
return nil, err
return &dst, nil }
}
return &dst, nil
// ToString renders a string showing the values of the struct }
func (d *DiskSubHeader) ToString() string {
return fmt.Sprintf( // ToString renders a string showing the values of the struct
"StartDate: %13d (0x%04x)\n"+ func (d *DiskSubHeader) ToString() string {
"StartTime: %13f (0x%08x)\n"+ return fmt.Sprintf(
"EndTime: %13f (0x%08x)\n"+ "StartDate: %13d (0x%04x)\n"+
"LapCount: %13d (0x%04x)\n"+ "StartTime: %13f (0x%08x)\n"+
"RecordCount: %13d (0x%04x)\n", "EndTime: %13f (0x%08x)\n"+
d.StartDate, d.StartDate, d.StartTime, d.StartTime, "LapCount: %13d (0x%04x)\n"+
d.EndTime, d.EndTime, d.LapCount, d.LapCount, "RecordCount: %13d (0x%04x)\n",
d.RecordCount, d.RecordCount, d.StartDate, d.StartDate, d.StartTime, d.StartTime,
) d.EndTime, d.EndTime, d.LapCount, d.LapCount,
} d.RecordCount, d.RecordCount,
)
}
+38
View File
@@ -0,0 +1,38 @@
package goirsdk
import (
"testing"
"github.com/google/go-cmp/cmp"
)
// TestParseTelemetrySubHeader_WithGoodBuffer
// Given a well structured buffer it will output the expected
// DiskSubHeader struct
func TestParseTelemetrySubHeader_WithGoodBuffer(t *testing.T) {
// Arrange
header := [32]byte{
0x54, 0x1e, 0x14, 0x67, 0x00, 0x00, 0x00, 0x00, 0x54, 0x09, 0x00, 0xf0,
0xee, 0x7e, 0x6b, 0x40, 0x53, 0x74, 0x88, 0x44, 0x44, 0x86, 0x8f, 0x40,
0x08, 0x00, 0x00, 0x00, 0xe1, 0xb8, 0x00, 0x00,
}
expectedHeader := DiskSubHeader{
StartDate: 1729371732,
StartTime: 219.96666717536084,
EndTime: 1008.7833338413715,
LapCount: 8,
RecordCount: 47329,
}
// Act
headers, err := parseTelemetrySubHeader(header)
// Assert
if err != nil {
t.Fatalf("Error parsing buffer: %v", err)
}
if !cmp.Equal(&expectedHeader, headers) {
t.Fatalf("Expected:\n%#v\nGot:\n%#v\n", expectedHeader, headers)
}
}
+10 -5
View File
@@ -1,5 +1,10 @@
module esilvalabs.org/ibtReader.git module github.com/ESilva15/goirsdk
go 1.23.2 go 1.23.2
require gopkg.in/yaml.v3 v3.0.1 require (
github.com/google/go-cmp v0.6.0
golang.org/x/sys v0.26.0
golang.org/x/text v0.19.0
gopkg.in/yaml.v3 v3.0.1
)
+10 -4
View File
@@ -1,4 +1,10 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
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=
+79 -80
View File
@@ -1,80 +1,79 @@
package ibtReader package goirsdk
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
) )
const ( const (
FileHeaderSize = 112 // FileHeaderSize is the size of the headers FileHeaderSize = 112 // FileHeaderSize is the size of the headers
HeaderSize = 4 // HeaderSize is the size of a single header HeaderSize = 4 // HeaderSize is the size of a single header
) )
// TelemetryHeaders struct to hold an IBT file's headers // TelemetryHeaders struct to hold an IBT file's headers
type TelemetryHeaders struct { type TelemetryHeaders struct {
Version int32 Version int32
// Status of 1 indicates a completed session and status of 0 a live session // Status of 1 indicates a completed session and status of 0 a live session
Status int32 Status int32
// TickRate indicates the frequency of writes (usually 60) // TickRate indicates the frequency of writes (usually 60)
TickRate int32 TickRate int32
// SessionInfoUpdate indicates the number of times the SessionInfo was // SessionInfoUpdate indicates the number of times the SessionInfo was
// updated. 0 for finished sessions and >1 for active sessions // updated. 0 for finished sessions and >1 for active sessions
SessionInfoUpdate int32 SessionInfoUpdate int32
// SessionInfoLength is the length of the session info buffer // SessionInfoLength is the length of the session info buffer
SessionInfoLength int32 SessionInfoLength int32
// SessionInfoOffset is the offset of the session info in the buffer // SessionInfoOffset is the offset of the session info in the buffer
SessionInfoOffset int32 SessionInfoOffset int32
// NumVars is the number of variables in each input // NumVars is the number of variables in each input
NumVars int32 NumVars int32
// VarHeaderOffset is the offset of the VarHeader // VarHeaderOffset is the offset of the VarHeader
VarHeaderOffset int32 VarHeaderOffset int32
// NumBuf will be 1 for static files and 3 for live telemetry files // NumBuf will be 1 for static files and 3 for live telemetry files
NumBuf int32 NumBuf int32
// BufLen is the length for parsing VarHeader values // BufLen is the length for parsing VarHeader values
BufLen int32 BufLen int32
// Padding // Padding
Padding [12]byte Padding [12]byte
// I still don't know what this is: // I still don't know what this is:
BufOffset int32 BufOffset int32
} }
// ToString renders a string showing the values of the struct // ToString renders a string showing the values of the struct
func (th *TelemetryHeaders) ToString() string { func (th *TelemetryHeaders) ToString() string {
return fmt.Sprintf( return fmt.Sprintf(
"Version: %5d (0x%04x)\n"+ "Version: %5d (0x%04x)\n"+
"Status: %5d (0x%04x)\n"+ "Status: %5d (0x%04x)\n"+
"TickRate: %5d (0x%04x)\n"+ "TickRate: %5d (0x%04x)\n"+
"SIUpdate: %5d (0x%04x)\n"+ "SIUpdate: %5d (0x%04x)\n"+
"SILength: %5d (0x%04x)\n"+ "SILength: %5d (0x%04x)\n"+
"SIOffset: %5d (0x%04x)\n"+ "SIOffset: %5d (0x%04x)\n"+
"NumVars: %5d (0x%04x)\n"+ "NumVars: %5d (0x%04x)\n"+
"VarHeaderOffset: %5d (0x%04x)\n"+ "VarHeaderOffset: %5d (0x%04x)\n"+
"NumBuf: %5d (0x%04x)\n"+ "NumBuf: %5d (0x%04x)\n"+
"BufLen: %5d (0x%04x)\n"+ "BufLen: %5d (0x%04x)\n"+
"BufOffset: %5d (0x%04x)\n", "BufOffset: %5d (0x%04x)\n",
th.Version, th.Version, th.Status, th.Status, th.TickRate, th.TickRate, th.Version, th.Version, th.Status, th.Status, th.TickRate, th.TickRate,
th.SessionInfoUpdate, th.SessionInfoUpdate, th.SessionInfoUpdate, th.SessionInfoUpdate,
th.SessionInfoLength, th.SessionInfoLength, th.SessionInfoLength, th.SessionInfoLength,
th.SessionInfoOffset, th.SessionInfoOffset, th.SessionInfoOffset, th.SessionInfoOffset,
th.NumVars, th.NumVars, th.VarHeaderOffset, th.VarHeaderOffset, th.NumVars, th.NumVars, th.VarHeaderOffset, th.VarHeaderOffset,
th.NumBuf, th.NumBuf, th.BufLen, th.BufLen, th.BufOffset, th.BufOffset, th.NumBuf, th.NumBuf, th.BufLen, th.BufLen, th.BufOffset, th.BufOffset,
) )
} }
// parseTelemetryHeader will read the IBT file headers from a correctly sized // parseTelemetryHeader will read the IBT file headers from a correctly sized
// buffer. // buffer.
// You need to pass a the first FILE_HEADER_SIZE bytes of the buffer // You need to pass a the first FILE_HEADER_SIZE bytes of the buffer
func parseTelemetryHeader(buf [FileHeaderSize]byte) (*TelemetryHeaders, error) { func parseTelemetryHeader(buf [FileHeaderSize]byte) (*TelemetryHeaders, error) {
if len(buf)%HeaderSize != 0 { // utils.HexDump(buf[:])
return nil, fmt.Errorf("buffer must be multiple of size: %d", HeaderSize) // fmt.Printf("Len: %d\n", len(buf))
}
dst := TelemetryHeaders{}
dst := TelemetryHeaders{} err := binary.Read(bytes.NewBuffer(buf[:]), binary.LittleEndian, &dst)
err := binary.Read(bytes.NewBuffer(buf[:]), binary.LittleEndian, &dst) if err != nil {
if err != nil { return nil, fmt.Errorf("unable to unpack data: %v", err)
return nil, fmt.Errorf("unable to unpack data: %v", err) }
}
return &dst, nil
return &dst, nil }
}
+52
View File
@@ -0,0 +1,52 @@
package goirsdk
import (
"testing"
"github.com/google/go-cmp/cmp"
)
// TestParseTelemetryHeader_WithGoodBuffer
// Given a well structured buffer it will output the expected
// TelemetryHeaders struct
func TestParseTelemetryHeader_WithGoodBuffer(t *testing.T) {
// Arrange
header := [112]byte{
0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x05, 0x3f, 0x00, 0x00, 0x90, 0x99, 0x00, 0x00,
0x10, 0x01, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x1d, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x60, 0x2b, 0x00, 0x00, 0x95, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00,
}
expectedHeader := TelemetryHeaders{
Version: 2,
Status: 1,
TickRate: 60,
SessionInfoUpdate: 0,
SessionInfoLength: 16133,
SessionInfoOffset: 39312,
NumVars: 272,
VarHeaderOffset: 144,
NumBuf: 1,
BufLen: 1053,
Padding: [12]byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x60, 0x2b, 0x0, 0x0},
BufOffset: 55445,
}
// Act
headers, err := parseTelemetryHeader(header)
// Assert
if err != nil {
t.Fatalf("Error parsing buffer: %v", err)
}
if !cmp.Equal(&expectedHeader, headers) {
t.Fatalf("Expected:\n%#v\nGot:\n%#v\n", expectedHeader, headers)
}
}
+188 -136
View File
@@ -1,136 +1,188 @@
// Package IbtReader is all you need for you iRacing telemetry parsing // Package goirsdk is all you need for you iRacing telemetry parsing
package ibtReader package goirsdk
import ( import (
"fmt" "fmt"
"io" "log"
) "os"
const ( "io"
ibtFile = "./telemetryFiles/mx5_2016Okayama_full_2024_10_19_22_02_12.ibt"
) "github.com/ESilva15/goirsdk/winutils"
"gopkg.in/yaml.v3"
// Reader is an interface to represent the readable data that can be either )
// a .ibt file (or live data, hopefully)
type Reader interface { const (
io.Reader ibtFile = "./telemetryFiles/mx5_2016Okayama_full_2024_10_19_22_02_12.ibt"
io.ReaderAt )
io.ReadCloser
} func msToKph(v float32) int {
return int((3600 * v) / 1000)
// IBT struct will hold the relevant data for a given IBT file }
type IBT struct {
File Reader // Source of the data // Reader is an interface to represent the readable data that can be either
Headers *TelemetryHeaders // IBT file Headers // a .ibt file (or live data, hopefully)
SubHeaders *DiskSubHeader // IBT file Sub Headers type Reader interface {
SessionInfo *SessionInfoYAML // IBT file Session Info io.Reader
Vars *TelemetryVars // Vars will hold the telemetry data io.ReaderAt
Tick int32 // Tick holds the cound of the reads io.ReadCloser
} }
// Init serves to initialize and get a hold of a IBT struct // IBT struct will hold the relevant data for a given IBT file
func Init(f Reader) (*IBT, error) { type IBT struct {
// Read the header of the file File Reader // Source of the data
var err error FileToExport *os.File // If set, it will export the IBT data to the file
ibt := IBT{ YAMLExport *os.File // If set, it will export the session YAML to the file
File: f, Headers *TelemetryHeaders // IBT file Headers
Vars: &TelemetryVars{}, SubHeaders *DiskSubHeader // IBT file Sub Headers
} SessionInfo *SessionInfoYAML // IBT file Session Info
Vars *TelemetryVars // Vars will hold the telemetry data
// Read the file headers winUtils *winutils.IRacingWinUtils // WinUtils gives access to the system utilities
var headerRaw [FileHeaderSize]byte }
_, err = ibt.File.ReadAt(headerRaw[:], 0)
if err != nil { func (i *IBT) IsConnected() bool {
return nil, fmt.Errorf("Failed to read headers from file: %v", err) if i.Headers != nil {
} if sessionStatusOK(int(i.Headers.Status)) {
ibt.Headers, err = parseTelemetryHeader(headerRaw) return true
if err != nil { }
return nil, fmt.Errorf("Unable to read headers from file: %v", err) // if sessionStatusOK(int(i.Headers.Status)) && (sdk.lastValidData+connTimeout > time.Now().Unix()) {
} // return true
// }
// Read the disk sub headers }
var subheaderRaw [SubHeaderSize]byte
_, err = ibt.File.ReadAt(subheaderRaw[:], 112) return false
if err != nil { }
return nil, fmt.Errorf("Failed to read disk subheaders from file: %v", err)
} func (i *IBT) exportYAML(path string) {
ibt.SubHeaders, err = parseTelemetrySubHeader(subheaderRaw) file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil { if err != nil {
return nil, fmt.Errorf("Unable to parse disk subheaders from file: %v", err) log.Println("Failed to open output file for YAML: ", err)
} }
defer file.Close()
// Read session info string
sessionInfoStringRaw := make([]byte, ibt.Headers.SessionInfoLength) enc := yaml.NewEncoder(file)
_, err = ibt.File.ReadAt(sessionInfoStringRaw, int64(ibt.Headers.SessionInfoOffset))
if err != nil { err = enc.Encode(i.SessionInfo)
return nil, fmt.Errorf("Failed to read sessionInfoString from file: %v", err) if err != nil {
} log.Println("Failed to print YAML to file: ", err)
ibt.SessionInfo, err = parseSessionInfo(sessionInfoStringRaw) }
if err != nil { }
return nil, fmt.Errorf("Unable to parse SessionInfoString from file: %v", err)
} // Init serves to initialize and get a hold of a IBT struct
func Init(f Reader, exportTelem string, exportYAML string) (*IBT, error) {
// Read the telemetry vars info // Read the header of the file
err = ibt.readVariablerHeaders() var err error
if err != nil { ibt := IBT{
return nil, fmt.Errorf("Unable to parser variable headers from file: %v", err) File: f,
} FileToExport: nil,
YAMLExport: nil,
return &ibt, nil Vars: &TelemetryVars{},
} winUtils: nil,
}
func msToKph(v float32) int {
return int((3600 * v) / 1000) // If requested to output to a telemetry file
} if exportTelem != "" {
ibt.FileToExport, err = os.OpenFile(exportTelem, os.O_CREATE|os.O_RDWR, 0644)
// func main() { if err != nil {
// fmt.Println("================== IBT FILE PARSER ==================") return nil, err
// }
// file, err := os.Open(ibtFile) ibt.FileToExport.Sync()
// if err != nil { }
// log.Fatalf("Failed to open IBT file: %v", err)
// } if ibt.File == nil {
// // User is requesting us to read live data - present in the mem map file
// ibt, err := Init(file) ibt.File, err = winutils.OpenMemMap(IRSDK_MEMMAPFILENAME, fileMapSize)
// fmt.Printf("%s\n", ibt.Headers.ToString()) if err != nil {
// fmt.Printf("%s\n", ibt.SubHeaders.ToString()) return nil, fmt.Errorf("Failed to open memory mapped file: %v", err)
// }
// // Display the human readable start date
// unixStartDate := time.Unix(ibt.SubHeaders.StartDate, 0) // To use our windows interface we need to initialize it first
// startDate := unixStartDate.Format("2006/01/02 15:04:05 -0700 MST") // it will return a struct with a pointer to the windows handles
// fmt.Println("StartDate:", startDate) // if, for some reason, we need to stub out this to run in on Linux its easier
// ibt.winUtils, err = winutils.Init()
// // Display the human readable version of start time if err != nil {
// unixStartTime := time.Unix(ibt.SubHeaders.StartDate+int64(ibt.SubHeaders.StartTime), 0) return nil, err
// startTime := unixStartTime.Format("2006/01/02 15:04:05 -0700 MST") }
// fmt.Println("StartTime:", startTime)
// // We need to open the windows event thing
// // Display the human readable version of end time err = ibt.winUtils.OpenWinEvent(IRSDK_DATAVALIDEVENTNAME)
// unixEndTime := time.Unix(ibt.SubHeaders.StartDate+int64(ibt.SubHeaders.EndTime), 0) if err != nil {
// endTime := unixEndTime.Format("2006/01/02 15:04:05 -0700 MST") return nil, err
// fmt.Println("EndTime: ", endTime) }
//
// last := time.Now().UnixMilli() // We need to open the broadcast channel
// for { err = ibt.winUtils.OpenBroadcastChannel(IRSDK_BROADCASTMSGNAME)
// time.Sleep(time.Second / 60) if err != nil {
// res := ibt.Update() return nil, err
// }
// curTime := time.Now().UnixMilli() }
//
// if curTime-last > 250 { // Read the file headers
// fmt.Printf(" \r") var headerRaw [FileHeaderSize]byte
// if val, ok := ibt.Vars.Vars["Speed"]; ok { _, err = ibt.File.ReadAt(headerRaw[:], 0)
// fmt.Printf("\r%d %d", ibt.Tick/60, msToKph(val.Value.(float32))) if err != nil {
// } else { return nil, fmt.Errorf("Failed to read headers from file: %v", err)
// fmt.Printf("\r%d %s", ibt.Tick/60, "KEY DOESN'T EXIST") }
// } ibt.Headers, err = parseTelemetryHeader(headerRaw)
// } if err != nil {
// return nil, fmt.Errorf("Unable to read headers from file: %v", err)
// if !res { }
// fmt.Println("\nEnd of file found...") // Write to the output file
// break _, err = ibt.FileToExport.WriteAt(headerRaw[:], 0)
// } if err != nil {
// } log.Fatal(err)
// }
// fmt.Printf("%d\n", ibt.Tick)
// } // Read the disk sub headers
var subheaderRaw [SubHeaderSize]byte
_, err = ibt.File.ReadAt(subheaderRaw[:], HeaderSize)
if err != nil {
return nil, fmt.Errorf("Failed to read disk subheaders from file: %v", err)
}
ibt.SubHeaders, err = parseTelemetrySubHeader(subheaderRaw)
if err != nil {
return nil, fmt.Errorf("Unable to parse disk subheaders from file: %v", err)
}
// Write to the output file
_, err = ibt.FileToExport.WriteAt(subheaderRaw[:], HeaderSize)
if err != nil {
log.Fatal(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)
}
// Write to the output file
_, err = ibt.FileToExport.WriteAt(sessionInfoStringRaw[:], int64(ibt.Headers.SessionInfoOffset))
if err != nil {
log.Fatal(err)
}
ibt.SessionInfo, err = parseSessionInfo(sessionInfoStringRaw, ibt.Headers.SessionInfoLength)
if err != nil {
return nil, fmt.Errorf("Unable to parse SessionInfoString from file: %v", err)
}
// Write to YAML output file
if exportYAML != "" {
ibt.exportYAML(exportYAML)
}
// Read the telemetry vars info
err = ibt.readVariablerHeaders()
if err != nil {
return nil, fmt.Errorf("Unable to parser variable headers from file: %v", err)
}
return &ibt, nil
}
func (i *IBT) Close() {
if i.winUtils != nil {
// If its not live data, the user is the one with ownership of the handle
i.File.Close()
i.winUtils.Close()
}
}
+345 -323
View File
@@ -1,323 +1,345 @@
package ibtReader package goirsdk
import ( import (
"encoding/json" "encoding/json"
"log"
"gopkg.in/yaml.v3" "strings"
)
"golang.org/x/text/encoding/charmap"
// SessionInfoYAML is a string with session info in the IBT file "gopkg.in/yaml.v3"
type SessionInfoYAML struct { )
WeekendInfo struct {
TrackName string `yaml:"TrackName"` // SessionInfoYAML is a string with session info in the IBT file
TrackID int `yaml:"TrackID"` type SessionInfoYAML struct {
TrackLength string `yaml:"TrackLength"` WeekendInfo struct {
TrackDisplayName string `yaml:"TrackDisplayName"` TrackName string `yaml:"TrackName"`
TrackDisplayShortName string `yaml:"TrackDisplayShortName"` TrackID int `yaml:"TrackID"`
TrackConfigName string `yaml:"TrackConfigName"` TrackLength string `yaml:"TrackLength"`
TrackCity string `yaml:"TrackCity"` TrackDisplayName string `yaml:"TrackDisplayName"`
TrackCountry string `yaml:"TrackCountry"` TrackDisplayShortName string `yaml:"TrackDisplayShortName"`
TrackAltitude string `yaml:"TrackAltitude"` TrackConfigName string `yaml:"TrackConfigName"`
TrackLatitude string `yaml:"TrackLatitude"` TrackCity string `yaml:"TrackCity"`
TrackLongitude string `yaml:"TrackLongitude"` TrackCountry string `yaml:"TrackCountry"`
TrackNorthOffset string `yaml:"TrackNorthOffset"` TrackAltitude string `yaml:"TrackAltitude"`
TrackNumTurns int `yaml:"TrackNumTurns"` TrackLatitude string `yaml:"TrackLatitude"`
TrackPitSpeedLimit string `yaml:"TrackPitSpeedLimit"` TrackLongitude string `yaml:"TrackLongitude"`
TrackType string `yaml:"TrackType"` TrackNorthOffset string `yaml:"TrackNorthOffset"`
TrackDirection string `yaml:"TrackDirection"` TrackNumTurns int `yaml:"TrackNumTurns"`
TrackWeatherType string `yaml:"TrackWeatherType"` TrackPitSpeedLimit string `yaml:"TrackPitSpeedLimit"`
TrackSkies string `yaml:"TrackSkies"` TrackType string `yaml:"TrackType"`
TrackSurfaceTemp string `yaml:"TrackSurfaceTemp"` TrackDirection string `yaml:"TrackDirection"`
TrackAirTemp string `yaml:"TrackAirTemp"` TrackWeatherType string `yaml:"TrackWeatherType"`
TrackAirPressure string `yaml:"TrackAirPressure"` TrackSkies string `yaml:"TrackSkies"`
TrackWindVel string `yaml:"TrackWindVel"` TrackSurfaceTemp string `yaml:"TrackSurfaceTemp"`
TrackWindDir string `yaml:"TrackWindDir"` TrackAirTemp string `yaml:"TrackAirTemp"`
TrackRelativeHumidity string `yaml:"TrackRelativeHumidity"` TrackAirPressure string `yaml:"TrackAirPressure"`
TrackFogLevel string `yaml:"TrackFogLevel"` TrackWindVel string `yaml:"TrackWindVel"`
TrackCleanup int `yaml:"TrackCleanup"` TrackWindDir string `yaml:"TrackWindDir"`
TrackDynamicTrack int `yaml:"TrackDynamicTrack"` TrackRelativeHumidity string `yaml:"TrackRelativeHumidity"`
TrackVersion string `yaml:"TrackVersion"` TrackFogLevel string `yaml:"TrackFogLevel"`
SeriesID int `yaml:"SeriesID"` TrackCleanup int `yaml:"TrackCleanup"`
SeasonID int `yaml:"SeasonID"` TrackDynamicTrack int `yaml:"TrackDynamicTrack"`
SessionID int `yaml:"SessionID"` TrackVersion string `yaml:"TrackVersion"`
SubSessionID int `yaml:"SubSessionID"` SeriesID int `yaml:"SeriesID"`
LeagueID int `yaml:"LeagueID"` SeasonID int `yaml:"SeasonID"`
Official int `yaml:"Official"` SessionID int `yaml:"SessionID"`
RaceWeek int `yaml:"RaceWeek"` SubSessionID int `yaml:"SubSessionID"`
EventType string `yaml:"EventType"` LeagueID int `yaml:"LeagueID"`
Category string `yaml:"Category"` Official int `yaml:"Official"`
SimMode string `yaml:"SimMode"` RaceWeek int `yaml:"RaceWeek"`
TeamRacing int `yaml:"TeamRacing"` EventType string `yaml:"EventType"`
MinDrivers int `yaml:"MinDrivers"` Category string `yaml:"Category"`
MaxDrivers int `yaml:"MaxDrivers"` SimMode string `yaml:"SimMode"`
DCRuleSet string `yaml:"DCRuleSet"` TeamRacing int `yaml:"TeamRacing"`
QualifierMustStartRace int `yaml:"QualifierMustStartRace"` MinDrivers int `yaml:"MinDrivers"`
NumCarClasses int `yaml:"NumCarClasses"` MaxDrivers int `yaml:"MaxDrivers"`
NumCarTypes int `yaml:"NumCarTypes"` DCRuleSet string `yaml:"DCRuleSet"`
HeatRacing int `yaml:"HeatRacing"` QualifierMustStartRace int `yaml:"QualifierMustStartRace"`
BuildType string `yaml:"BuildType"` NumCarClasses int `yaml:"NumCarClasses"`
BuildTarget string `yaml:"BuildTarget"` NumCarTypes int `yaml:"NumCarTypes"`
BuildVersion string `yaml:"BuildVersion"` HeatRacing int `yaml:"HeatRacing"`
WeekendOptions struct { BuildType string `yaml:"BuildType"`
NumStarters int `yaml:"NumStarters"` BuildTarget string `yaml:"BuildTarget"`
StartingGrid string `yaml:"StartingGrid"` BuildVersion string `yaml:"BuildVersion"`
QualifyScoring string `yaml:"QualifyScoring"` WeekendOptions struct {
CourseCautions string `yaml:"CourseCautions"` NumStarters int `yaml:"NumStarters"`
StandingStart int `yaml:"StandingStart"` StartingGrid string `yaml:"StartingGrid"`
ShortParadeLap int `yaml:"ShortParadeLap"` QualifyScoring string `yaml:"QualifyScoring"`
Restarts string `yaml:"Restarts"` CourseCautions string `yaml:"CourseCautions"`
WeatherType string `yaml:"WeatherType"` StandingStart int `yaml:"StandingStart"`
Skies string `yaml:"Skies"` ShortParadeLap int `yaml:"ShortParadeLap"`
WindDirection string `yaml:"WindDirection"` Restarts string `yaml:"Restarts"`
WindSpeed string `yaml:"WindSpeed"` WeatherType string `yaml:"WeatherType"`
WeatherTemp string `yaml:"WeatherTemp"` Skies string `yaml:"Skies"`
RelativeHumidity string `yaml:"RelativeHumidity"` WindDirection string `yaml:"WindDirection"`
FogLevel string `yaml:"FogLevel"` WindSpeed string `yaml:"WindSpeed"`
TimeOfDay string `yaml:"TimeOfDay"` WeatherTemp string `yaml:"WeatherTemp"`
Date string `yaml:"Date"` RelativeHumidity string `yaml:"RelativeHumidity"`
EarthRotationSpeedupFactor int `yaml:"EarthRotationSpeedupFactor"` FogLevel string `yaml:"FogLevel"`
Unofficial int `yaml:"Unofficial"` TimeOfDay string `yaml:"TimeOfDay"`
CommercialMode string `yaml:"CommercialMode"` Date string `yaml:"Date"`
NightMode string `yaml:"NightMode"` EarthRotationSpeedupFactor int `yaml:"EarthRotationSpeedupFactor"`
IsFixedSetup int `yaml:"IsFixedSetup"` Unofficial int `yaml:"Unofficial"`
StrictLapsChecking string `yaml:"StrictLapsChecking"` CommercialMode string `yaml:"CommercialMode"`
HasOpenRegistration int `yaml:"HasOpenRegistration"` NightMode string `yaml:"NightMode"`
HardcoreLevel int `yaml:"HardcoreLevel"` IsFixedSetup int `yaml:"IsFixedSetup"`
NumJokerLaps int `yaml:"NumJokerLaps"` StrictLapsChecking string `yaml:"StrictLapsChecking"`
IncidentLimit string `yaml:"IncidentLimit"` HasOpenRegistration int `yaml:"HasOpenRegistration"`
FastRepairsLimit string `yaml:"FastRepairsLimit"` HardcoreLevel int `yaml:"HardcoreLevel"`
GreenWhiteCheckeredLimit int `yaml:"GreenWhiteCheckeredLimit"` NumJokerLaps int `yaml:"NumJokerLaps"`
} `yaml:"WeekendOptions"` IncidentLimit string `yaml:"IncidentLimit"`
TelemetryOptions struct { FastRepairsLimit string `yaml:"FastRepairsLimit"`
TelemetryDiskFile string `yaml:"TelemetryDiskFile"` GreenWhiteCheckeredLimit int `yaml:"GreenWhiteCheckeredLimit"`
} `yaml:"TelemetryOptions"` } `yaml:"WeekendOptions"`
} `yaml:"WeekendInfo"` TelemetryOptions struct {
SessionInfo struct { TelemetryDiskFile string `yaml:"TelemetryDiskFile"`
Sessions []struct { } `yaml:"TelemetryOptions"`
SessionNum int `yaml:"SessionNum"` } `yaml:"WeekendInfo"`
SessionLaps string `yaml:"SessionLaps"` SessionInfo struct {
SessionTime string `yaml:"SessionTime"` Sessions []struct {
SessionNumLapsToAvg int `yaml:"SessionNumLapsToAvg"` SessionNum int `yaml:"SessionNum"`
SessionType string `yaml:"SessionType"` SessionLaps string `yaml:"SessionLaps"`
SessionTrackRubberState string `yaml:"SessionTrackRubberState"` SessionTime string `yaml:"SessionTime"`
SessionName string `yaml:"SessionName"` SessionNumLapsToAvg int `yaml:"SessionNumLapsToAvg"`
SessionSubType interface{} `yaml:"SessionSubType"` SessionType string `yaml:"SessionType"`
SessionSkipped int `yaml:"SessionSkipped"` SessionTrackRubberState string `yaml:"SessionTrackRubberState"`
SessionRunGroupsUsed int `yaml:"SessionRunGroupsUsed"` SessionName string `yaml:"SessionName"`
ResultsPositions interface{} `yaml:"ResultsPositions"` SessionSubType interface{} `yaml:"SessionSubType"`
ResultsFastestLap []struct { SessionSkipped int `yaml:"SessionSkipped"`
CarIdx int `yaml:"CarIdx"` SessionRunGroupsUsed int `yaml:"SessionRunGroupsUsed"`
FastestLap int `yaml:"FastestLap"` ResultsPositions interface{} `yaml:"ResultsPositions"`
FastestTime int `yaml:"FastestTime"` ResultsFastestLap []struct {
} `yaml:"ResultsFastestLap"` CarIdx int `yaml:"CarIdx"`
ResultsAverageLapTime int `yaml:"ResultsAverageLapTime"` FastestLap int `yaml:"FastestLap"`
ResultsNumCautionFlags int `yaml:"ResultsNumCautionFlags"` FastestTime int `yaml:"FastestTime"`
ResultsNumCautionLaps int `yaml:"ResultsNumCautionLaps"` } `yaml:"ResultsFastestLap"`
ResultsNumLeadChanges int `yaml:"ResultsNumLeadChanges"` ResultsAverageLapTime int `yaml:"ResultsAverageLapTime"`
ResultsLapsComplete int `yaml:"ResultsLapsComplete"` ResultsNumCautionFlags int `yaml:"ResultsNumCautionFlags"`
ResultsOfficial int `yaml:"ResultsOfficial"` ResultsNumCautionLaps int `yaml:"ResultsNumCautionLaps"`
} `yaml:"Sessions"` ResultsNumLeadChanges int `yaml:"ResultsNumLeadChanges"`
} `yaml:"SessionInfo"` ResultsLapsComplete int `yaml:"ResultsLapsComplete"`
CameraInfo struct { ResultsOfficial int `yaml:"ResultsOfficial"`
Groups []struct { } `yaml:"Sessions"`
GroupNum int `yaml:"GroupNum"` } `yaml:"SessionInfo"`
GroupName string `yaml:"GroupName"` CameraInfo struct {
Cameras []struct { Groups []struct {
CameraNum int `yaml:"CameraNum"` GroupNum int `yaml:"GroupNum"`
CameraName string `yaml:"CameraName"` GroupName string `yaml:"GroupName"`
} `yaml:"Cameras"` Cameras []struct {
IsScenic bool `yaml:"IsScenic,omitempty"` CameraNum int `yaml:"CameraNum"`
} `yaml:"Groups"` CameraName string `yaml:"CameraName"`
} `yaml:"CameraInfo"` } `yaml:"Cameras"`
RadioInfo struct { IsScenic bool `yaml:"IsScenic,omitempty"`
SelectedRadioNum int `yaml:"SelectedRadioNum"` } `yaml:"Groups"`
Radios []struct { } `yaml:"CameraInfo"`
RadioNum int `yaml:"RadioNum"` RadioInfo struct {
HopCount int `yaml:"HopCount"` SelectedRadioNum int `yaml:"SelectedRadioNum"`
NumFrequencies int `yaml:"NumFrequencies"` Radios []struct {
TunedToFrequencyNum int `yaml:"TunedToFrequencyNum"` RadioNum int `yaml:"RadioNum"`
ScanningIsOn int `yaml:"ScanningIsOn"` HopCount int `yaml:"HopCount"`
Frequencies []struct { NumFrequencies int `yaml:"NumFrequencies"`
FrequencyNum int `yaml:"FrequencyNum"` TunedToFrequencyNum int `yaml:"TunedToFrequencyNum"`
FrequencyName string `yaml:"FrequencyName"` ScanningIsOn int `yaml:"ScanningIsOn"`
Priority int `yaml:"Priority"` Frequencies []struct {
CarIdx int `yaml:"CarIdx"` FrequencyNum int `yaml:"FrequencyNum"`
EntryIdx int `yaml:"EntryIdx"` FrequencyName string `yaml:"FrequencyName"`
ClubID int `yaml:"ClubID"` Priority int `yaml:"Priority"`
CanScan int `yaml:"CanScan"` CarIdx int `yaml:"CarIdx"`
CanSquawk int `yaml:"CanSquawk"` EntryIdx int `yaml:"EntryIdx"`
Muted int `yaml:"Muted"` ClubID int `yaml:"ClubID"`
IsMutable int `yaml:"IsMutable"` CanScan int `yaml:"CanScan"`
IsDeletable int `yaml:"IsDeletable"` CanSquawk int `yaml:"CanSquawk"`
} `yaml:"Frequencies"` Muted int `yaml:"Muted"`
} `yaml:"Radios"` IsMutable int `yaml:"IsMutable"`
} `yaml:"RadioInfo"` IsDeletable int `yaml:"IsDeletable"`
DriverInfo struct { } `yaml:"Frequencies"`
DriverCarIdx int `yaml:"DriverCarIdx"` } `yaml:"Radios"`
DriverUserID int `yaml:"DriverUserID"` } `yaml:"RadioInfo"`
PaceCarIdx int `yaml:"PaceCarIdx"` DriverInfo struct {
DriverHeadPosX float64 `yaml:"DriverHeadPosX"` DriverCarIdx int `yaml:"DriverCarIdx"`
DriverHeadPosY float64 `yaml:"DriverHeadPosY"` DriverUserID int `yaml:"DriverUserID"`
DriverHeadPosZ float64 `yaml:"DriverHeadPosZ"` PaceCarIdx int `yaml:"PaceCarIdx"`
DriverCarIdleRPM float64 `yaml:"DriverCarIdleRPM"` DriverHeadPosX float64 `yaml:"DriverHeadPosX"`
DriverCarRedLine float64 `yaml:"DriverCarRedLine"` DriverHeadPosY float64 `yaml:"DriverHeadPosY"`
DriverCarEngCylinderCount int `yaml:"DriverCarEngCylinderCount"` DriverHeadPosZ float64 `yaml:"DriverHeadPosZ"`
DriverCarFuelKgPerLtr float64 `yaml:"DriverCarFuelKgPerLtr"` DriverCarIdleRPM float64 `yaml:"DriverCarIdleRPM"`
DriverCarFuelMaxLtr float64 `yaml:"DriverCarFuelMaxLtr"` DriverCarRedLine float64 `yaml:"DriverCarRedLine"`
DriverCarMaxFuelPct float64 `yaml:"DriverCarMaxFuelPct"` DriverCarEngCylinderCount int `yaml:"DriverCarEngCylinderCount"`
DriverCarGearNumForward int `yaml:"DriverCarGearNumForward"` DriverCarFuelKgPerLtr float64 `yaml:"DriverCarFuelKgPerLtr"`
DriverCarGearNeutral int `yaml:"DriverCarGearNeutral"` DriverCarFuelMaxLtr float64 `yaml:"DriverCarFuelMaxLtr"`
DriverCarGearReverse int `yaml:"DriverCarGearReverse"` DriverCarMaxFuelPct float64 `yaml:"DriverCarMaxFuelPct"`
DriverCarSLFirstRPM float64 `yaml:"DriverCarSLFirstRPM"` DriverCarGearNumForward int `yaml:"DriverCarGearNumForward"`
DriverCarSLShiftRPM float64 `yaml:"DriverCarSLShiftRPM"` DriverCarGearNeutral int `yaml:"DriverCarGearNeutral"`
DriverCarSLLastRPM float64 `yaml:"DriverCarSLLastRPM"` DriverCarGearReverse int `yaml:"DriverCarGearReverse"`
DriverCarSLBlinkRPM float64 `yaml:"DriverCarSLBlinkRPM"` DriverCarSLFirstRPM float64 `yaml:"DriverCarSLFirstRPM"`
DriverCarVersion string `yaml:"DriverCarVersion"` DriverCarSLShiftRPM float64 `yaml:"DriverCarSLShiftRPM"`
DriverPitTrkPct float64 `yaml:"DriverPitTrkPct"` DriverCarSLLastRPM float64 `yaml:"DriverCarSLLastRPM"`
DriverCarEstLapTime float64 `yaml:"DriverCarEstLapTime"` DriverCarSLBlinkRPM float64 `yaml:"DriverCarSLBlinkRPM"`
DriverSetupName string `yaml:"DriverSetupName"` DriverCarVersion string `yaml:"DriverCarVersion"`
DriverSetupIsModified int `yaml:"DriverSetupIsModified"` DriverPitTrkPct float64 `yaml:"DriverPitTrkPct"`
DriverSetupLoadTypeName string `yaml:"DriverSetupLoadTypeName"` DriverCarEstLapTime float64 `yaml:"DriverCarEstLapTime"`
DriverSetupPassedTech int `yaml:"DriverSetupPassedTech"` DriverSetupName string `yaml:"DriverSetupName"`
DriverIncidentCount int `yaml:"DriverIncidentCount"` DriverSetupIsModified int `yaml:"DriverSetupIsModified"`
Drivers []Driver `yaml:"Drivers"` DriverSetupLoadTypeName string `yaml:"DriverSetupLoadTypeName"`
} `yaml:"DriverInfo"` DriverSetupPassedTech int `yaml:"DriverSetupPassedTech"`
SplitTimeInfo struct { DriverIncidentCount int `yaml:"DriverIncidentCount"`
Sectors []struct { Drivers []Driver `yaml:"Drivers"`
SectorNum int `yaml:"SectorNum"` } `yaml:"DriverInfo"`
SectorStartPct float64 `yaml:"SectorStartPct"` SplitTimeInfo struct {
} `yaml:"Sectors"` Sectors []struct {
} `yaml:"SplitTimeInfo"` SectorNum int `yaml:"SectorNum"`
CarSetup struct { SectorStartPct float64 `yaml:"SectorStartPct"`
UpdateCount int `yaml:"UpdateCount"` } `yaml:"Sectors"`
TiresAero struct { } `yaml:"SplitTimeInfo"`
LeftFront struct { CarSetup struct {
StartingPressure string `yaml:"StartingPressure"` UpdateCount int `yaml:"UpdateCount"`
LastHotPressure string `yaml:"LastHotPressure"` TiresAero struct {
LastTempsOMI string `yaml:"LastTempsOMI"` LeftFront struct {
TreadRemaining string `yaml:"TreadRemaining"` StartingPressure string `yaml:"StartingPressure"`
} `yaml:"LeftFront"` LastHotPressure string `yaml:"LastHotPressure"`
LeftRear struct { LastTempsOMI string `yaml:"LastTempsOMI"`
StartingPressure string `yaml:"StartingPressure"` TreadRemaining string `yaml:"TreadRemaining"`
LastHotPressure string `yaml:"LastHotPressure"` } `yaml:"LeftFront"`
LastTempsOMI string `yaml:"LastTempsOMI"` LeftRear struct {
TreadRemaining string `yaml:"TreadRemaining"` StartingPressure string `yaml:"StartingPressure"`
} `yaml:"LeftRear"` LastHotPressure string `yaml:"LastHotPressure"`
RightFront struct { LastTempsOMI string `yaml:"LastTempsOMI"`
StartingPressure string `yaml:"StartingPressure"` TreadRemaining string `yaml:"TreadRemaining"`
LastHotPressure string `yaml:"LastHotPressure"` } `yaml:"LeftRear"`
LastTempsIMO string `yaml:"LastTempsIMO"` RightFront struct {
TreadRemaining string `yaml:"TreadRemaining"` StartingPressure string `yaml:"StartingPressure"`
} `yaml:"RightFront"` LastHotPressure string `yaml:"LastHotPressure"`
RightRear struct { LastTempsIMO string `yaml:"LastTempsIMO"`
StartingPressure string `yaml:"StartingPressure"` TreadRemaining string `yaml:"TreadRemaining"`
LastHotPressure string `yaml:"LastHotPressure"` } `yaml:"RightFront"`
LastTempsIMO string `yaml:"LastTempsIMO"` RightRear struct {
TreadRemaining string `yaml:"TreadRemaining"` StartingPressure string `yaml:"StartingPressure"`
} `yaml:"RightRear"` LastHotPressure string `yaml:"LastHotPressure"`
} `yaml:"TiresAero"` LastTempsIMO string `yaml:"LastTempsIMO"`
Chassis struct { TreadRemaining string `yaml:"TreadRemaining"`
Front struct { } `yaml:"RightRear"`
ArbSetting int `yaml:"ArbSetting"` } `yaml:"TiresAero"`
ToeIn string `yaml:"ToeIn"` Chassis struct {
FuelLevel string `yaml:"FuelLevel"` Front struct {
CrossWeight string `yaml:"CrossWeight"` ArbSetting int `yaml:"ArbSetting"`
} `yaml:"Front"` ToeIn string `yaml:"ToeIn"`
LeftFront struct { FuelLevel string `yaml:"FuelLevel"`
CornerWeight string `yaml:"CornerWeight"` CrossWeight string `yaml:"CrossWeight"`
RideHeight string `yaml:"RideHeight"` } `yaml:"Front"`
SpringPerchOffset string `yaml:"SpringPerchOffset"` LeftFront struct {
Camber string `yaml:"Camber"` CornerWeight string `yaml:"CornerWeight"`
} `yaml:"LeftFront"` RideHeight string `yaml:"RideHeight"`
LeftRear struct { SpringPerchOffset string `yaml:"SpringPerchOffset"`
CornerWeight string `yaml:"CornerWeight"` Camber string `yaml:"Camber"`
RideHeight string `yaml:"RideHeight"` } `yaml:"LeftFront"`
SpringPerchOffset string `yaml:"SpringPerchOffset"` LeftRear struct {
Camber string `yaml:"Camber"` CornerWeight string `yaml:"CornerWeight"`
ToeIn string `yaml:"ToeIn"` RideHeight string `yaml:"RideHeight"`
} `yaml:"LeftRear"` SpringPerchOffset string `yaml:"SpringPerchOffset"`
InCarDials struct { Camber string `yaml:"Camber"`
DisplayPage string `yaml:"DisplayPage"` ToeIn string `yaml:"ToeIn"`
BrakePressureBias string `yaml:"BrakePressureBias"` } `yaml:"LeftRear"`
} `yaml:"InCarDials"` InCarDials struct {
RightFront struct { DisplayPage string `yaml:"DisplayPage"`
CornerWeight string `yaml:"CornerWeight"` BrakePressureBias string `yaml:"BrakePressureBias"`
RideHeight string `yaml:"RideHeight"` } `yaml:"InCarDials"`
SpringPerchOffset string `yaml:"SpringPerchOffset"` RightFront struct {
Camber string `yaml:"Camber"` CornerWeight string `yaml:"CornerWeight"`
} `yaml:"RightFront"` RideHeight string `yaml:"RideHeight"`
RightRear struct { SpringPerchOffset string `yaml:"SpringPerchOffset"`
CornerWeight string `yaml:"CornerWeight"` Camber string `yaml:"Camber"`
RideHeight string `yaml:"RideHeight"` } `yaml:"RightFront"`
SpringPerchOffset string `yaml:"SpringPerchOffset"` RightRear struct {
Camber string `yaml:"Camber"` CornerWeight string `yaml:"CornerWeight"`
ToeIn string `yaml:"ToeIn"` RideHeight string `yaml:"RideHeight"`
} `yaml:"RightRear"` SpringPerchOffset string `yaml:"SpringPerchOffset"`
Rear struct { Camber string `yaml:"Camber"`
ArbSetting int `yaml:"ArbSetting"` ToeIn string `yaml:"ToeIn"`
WingSetting int `yaml:"WingSetting"` } `yaml:"RightRear"`
} `yaml:"Rear"` Rear struct {
} `yaml:"Chassis"` ArbSetting int `yaml:"ArbSetting"`
} `yaml:"CarSetup"` WingSetting int `yaml:"WingSetting"`
} } `yaml:"Rear"`
} `yaml:"Chassis"`
// Driver ... } `yaml:"CarSetup"`
type Driver struct { }
CarIdx int `yaml:"CarIdx"`
UserName string `yaml:"UserName"` // Driver ...
AbbrevName string `yaml:"AbbrevName"` type Driver struct {
Initials string `yaml:"Initials"` CarIdx int `yaml:"CarIdx"`
UserID int `yaml:"UserID"` UserName string `yaml:"UserName"`
TeamID int `yaml:"TeamID"` AbbrevName string `yaml:"AbbrevName"`
TeamName string `yaml:"TeamName"` Initials string `yaml:"Initials"`
CarNumber string `yaml:"CarNumber"` UserID int `yaml:"UserID"`
CarNumberRaw int `yaml:"CarNumberRaw"` TeamID int `yaml:"TeamID"`
CarPath string `yaml:"CarPath"` TeamName string `yaml:"TeamName"`
CarClassID int `yaml:"CarClassID"` CarNumber string `yaml:"CarNumber"`
CarID int `yaml:"CarID"` CarNumberRaw int `yaml:"CarNumberRaw"`
CarIsPaceCar int `yaml:"CarIsPaceCar"` CarPath string `yaml:"CarPath"`
CarIsAI int `yaml:"CarIsAI"` CarClassID int `yaml:"CarClassID"`
CarScreenName string `yaml:"CarScreenName"` CarID int `yaml:"CarID"`
CarScreenNameShort string `yaml:"CarScreenNameShort"` CarIsPaceCar int `yaml:"CarIsPaceCar"`
CarClassShortName string `yaml:"CarClassShortName"` CarIsAI int `yaml:"CarIsAI"`
CarClassRelSpeed int `yaml:"CarClassRelSpeed"` CarScreenName string `yaml:"CarScreenName"`
CarClassLicenseLevel int `yaml:"CarClassLicenseLevel"` CarScreenNameShort string `yaml:"CarScreenNameShort"`
CarClassMaxFuelPct string `yaml:"CarClassMaxFuelPct"` CarClassShortName string `yaml:"CarClassShortName"`
CarClassWeightPenalty string `yaml:"CarClassWeightPenalty"` CarClassRelSpeed int `yaml:"CarClassRelSpeed"`
CarClassPowerAdjust string `yaml:"CarClassPowerAdjust"` CarClassLicenseLevel int `yaml:"CarClassLicenseLevel"`
CarClassDryTireSetLimit string `yaml:"CarClassDryTireSetLimit"` CarClassMaxFuelPct string `yaml:"CarClassMaxFuelPct"`
CarClassColor int `yaml:"CarClassColor"` CarClassWeightPenalty string `yaml:"CarClassWeightPenalty"`
CarClassEstLapTime float64 `yaml:"CarClassEstLapTime"` CarClassPowerAdjust string `yaml:"CarClassPowerAdjust"`
IRating int `yaml:"IRating"` CarClassDryTireSetLimit string `yaml:"CarClassDryTireSetLimit"`
LicLevel int `yaml:"LicLevel"` CarClassColor int `yaml:"CarClassColor"`
LicSubLevel int `yaml:"LicSubLevel"` CarClassEstLapTime float64 `yaml:"CarClassEstLapTime"`
LicString string `yaml:"LicString"` IRating int `yaml:"IRating"`
LicColor string `yaml:"LicColor"` LicLevel int `yaml:"LicLevel"`
IsSpectator int `yaml:"IsSpectator"` LicSubLevel int `yaml:"LicSubLevel"`
CarDesignStr string `yaml:"CarDesignStr"` LicString string `yaml:"LicString"`
HelmetDesignStr string `yaml:"HelmetDesignStr"` LicColor string `yaml:"LicColor"`
SuitDesignStr string `yaml:"SuitDesignStr"` IsSpectator int `yaml:"IsSpectator"`
CarNumberDesignStr string `yaml:"CarNumberDesignStr"` CarDesignStr string `yaml:"CarDesignStr"`
CarSponsor1 int `yaml:"CarSponsor_1"` HelmetDesignStr string `yaml:"HelmetDesignStr"`
CarSponsor2 int `yaml:"CarSponsor_2"` SuitDesignStr string `yaml:"SuitDesignStr"`
CurDriverIncidentCount int `yaml:"CurDriverIncidentCount"` CarNumberDesignStr string `yaml:"CarNumberDesignStr"`
TeamIncidentCount int `yaml:"TeamIncidentCount"` CarSponsor1 int `yaml:"CarSponsor_1"`
} CarSponsor2 int `yaml:"CarSponsor_2"`
CurDriverIncidentCount int `yaml:"CurDriverIncidentCount"`
// parseSessionInfo will parse the sessionInfo buffer into the SessionInfoYAML TeamIncidentCount int `yaml:"TeamIncidentCount"`
// struct }
func parseSessionInfo(data []byte) (*SessionInfoYAML, error) {
var sessionInfo SessionInfoYAML // parseSessionInfo will parse the sessionInfo buffer into the SessionInfoYAML
err := yaml.Unmarshal(data, &sessionInfo) // struct
if err != nil { func parseSessionInfo(buf []byte, len int32) (*SessionInfoYAML, error) {
return nil, err // this seems not to work on windows
} // windows := true
var sessionInfo SessionInfoYAML
return &sessionInfo, nil dataBuffer := buf
}
// FOR WINDOWS
// ToString will return a readable string of the struct // if windows {
func (s *SessionInfoYAML) ToString() string { decoder := charmap.Windows1252.NewDecoder()
stringified, _ := json.MarshalIndent(s, "", " ") buf, err := decoder.Bytes(buf)
return string(stringified) if err != nil {
} log.Fatal(err)
}
dataBuffer = []byte(strings.TrimRight(string(buf[:len]), "\x00"))
// }
err = yaml.Unmarshal(dataBuffer, &sessionInfo)
if err != nil {
return nil, err
}
return &sessionInfo, nil
}
// sessionStatusOK will tell us if we are connected to the live data
func sessionStatusOK(status int) bool {
return (status & stConnected) > 0
}
// ToString will return a readable string of the struct
func (s *SessionInfoYAML) ToString() string {
stringified, _ := json.MarshalIndent(s, "", " ")
return string(stringified)
}
+88
View File
@@ -0,0 +1,88 @@
package sharedMem
import (
"fmt"
"io"
)
// Memory is shared memory struct
type Memory struct {
m *shmi
pos int64
}
// Create is create shared memory
func Create(name string, size uint32) (*Memory, error) {
m, err := create(name, size)
if err != nil {
return nil, err
}
return &Memory{m, 0}, nil
}
// Open is open exist shared memory
func Open(name string, size uint32) (*Memory, error) {
m, err := open(name, size)
if err != nil {
return nil, err
}
return &Memory{m, 0}, nil
}
// Close is close & discard shared memory
func (o *Memory) Close() (err error) {
if o.m != nil {
err = o.m.close()
if err == nil {
o.m = nil
}
}
return err
}
// Read is read shared memory (current position)
func (o *Memory) Read(p []byte) (n int, err error) {
n, err = o.ReadAt(p, o.pos)
if err != nil {
return 0, err
}
o.pos += int64(n)
return n, nil
}
// ReadAt is read shared memory (offset)
func (o *Memory) ReadAt(p []byte, off int64) (n int, err error) {
return o.m.readAt(p, off)
}
// Seek is move read/write position at shared memory
func (o *Memory) Seek(offset int64, whence int) (int64, error) {
switch whence {
case io.SeekStart:
offset += int64(0)
case io.SeekCurrent:
offset += o.pos
case io.SeekEnd:
offset += int64(o.m.size)
}
if offset < 0 || offset >= int64(o.m.size) {
return 0, fmt.Errorf("invalid offset")
}
o.pos = offset
return offset, nil
}
// Write is write shared memory (current position)
func (o *Memory) Write(p []byte) (n int, err error) {
n, err = o.WriteAt(p, o.pos)
if err != nil {
return 0, err
}
o.pos += int64(n)
return n, nil
}
// WriteAt is write shared memory (offset)
func (o *Memory) WriteAt(p []byte, off int64) (n int, err error) {
return o.m.writeAt(p, off)
}
+157
View File
@@ -0,0 +1,157 @@
//go:build darwin && cgo
// +build darwin,cgo
package sharedMem
/*
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/errno.h>
int _create(const char* name, int size, int flag) {
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
int fd = shm_open(name, flag, mode);
if (fd < 0) {
return -1;
}
struct stat mapstat;
int ret = fstat(fd, &mapstat);
if (ret != -1 && mapstat.st_size == 0) {
if (ftruncate(fd, size) != 0) {
close(fd);
return -2;
}
} else if (ret == -1) {
close(fd);
return -3;
}
return fd;
}
int Create(const char* name, int size) {
int flag = O_RDWR | O_CREAT;
return _create(name, size, flag);
}
int Open(const char* name, int size) {
int flag = O_RDWR;
return _create(name, size, flag);
}
void* Map(int fd, int size) {
void* p = mmap(
NULL, size,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
return NULL;
}
return p;
}
void Close(int fd, void* p, int size) {
if (p != NULL) {
munmap(p, size);
}
if (fd != 0) {
close(fd);
}
}
void Delete(const char* name) {
shm_unlink(name);
}
*/
import "C"
import (
"fmt"
"io"
"unsafe"
)
type shmi struct {
name string
fd C.int
v unsafe.Pointer
size uint32
parent bool
}
// create shared memory. return shmi object.
// name should not be more than 31 bytes.
func create(name string, size uint32) (*shmi, error) {
name = "/" + name
fd := C.Create(C.CString(name), C.int(size))
if fd < 0 {
return nil, fmt.Errorf("create")
}
v := C.Map(fd, C.int(size))
if v == nil {
C.Close(fd, nil, C.int(size))
C.Delete(C.CString(name))
}
return &shmi{name, fd, v, size, true}, nil
}
// open shared memory. return shmi object.
// name should not be more than 31 bytes.
func open(name string, size uint32) (*shmi, error) {
name = "/" + name
fd := C.Open(C.CString(name), C.int(size))
if fd < 0 {
return nil, fmt.Errorf("open")
}
v := C.Map(fd, C.int(size))
if v == nil {
C.Close(fd, nil, C.int(size))
C.Delete(C.CString(name))
}
return &shmi{name, fd, v, size, false}, nil
}
func (o *shmi) close() error {
if o.v != nil {
C.Close(o.fd, o.v, C.int(o.size))
o.v = nil
}
if o.parent {
C.Delete(C.CString(o.name))
}
return nil
}
// read shared memory. return read size.
func (o *shmi) readAt(p []byte, off int64) (n int, err error) {
if off >= int64(o.size) {
return 0, io.EOF
}
if max := int64(o.size) - off; int64(len(p)) > max {
p = p[:max]
}
return copyPtr2Slice(uintptr(o.v), p, off, o.size), nil
}
// write shared memory. return write size.
func (o *shmi) writeAt(p []byte, off int64) (n int, err error) {
if off >= int64(o.size) {
return 0, io.EOF
}
if max := int64(o.size) - off; int64(len(p)) > max {
p = p[:max]
}
return copySlice2Ptr(p, uintptr(o.v), off, o.size), nil
}
+148
View File
@@ -0,0 +1,148 @@
//go:build linux && cgo
// +build linux,cgo
package sharedMem
/*
#cgo LDFLAGS: -lrt
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int _create(const char* name, int size, int flag) {
mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP;
int fd = shm_open(name, flag, mode);
if (fd < 0) {
return -1;
}
if (ftruncate(fd, size) != 0) {
close(fd);
return -2;
}
return fd;
}
int Create(const char* name, int size) {
int flag = O_RDWR | O_CREAT;
return _create(name, size, flag);
}
int Open(const char* name, int size) {
int flag = O_RDWR;
return _create(name, size, flag);
}
void* Map(int fd, int size) {
void* p = mmap(
NULL, size,
PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
if (p == MAP_FAILED) {
return NULL;
}
return p;
}
void Close(int fd, void* p, int size) {
if (p != NULL) {
munmap(p, size);
}
if (fd != 0) {
close(fd);
}
}
void Delete(const char* name) {
shm_unlink(name);
}
*/
import "C"
import (
"fmt"
"io"
"unsafe"
)
type shmi struct {
name string
fd C.int
v unsafe.Pointer
size uint32
parent bool
}
// create shared memory. return shmi object.
func create(name string, size uint32) (*shmi, error) {
name = "/" + name
fd := C.Create(C.CString(name), C.int(size))
if fd < 0 {
return nil, fmt.Errorf("create")
}
v := C.Map(fd, C.int(size))
if v == nil {
C.Close(fd, nil, C.int(size))
C.Delete(C.CString(name))
}
return &shmi{name, fd, v, size, true}, nil
}
// open shared memory. return shmi object.
func open(name string, size uint32) (*shmi, error) {
name = "/" + name
fd := C.Open(C.CString(name), C.int(size))
if fd < 0 {
return nil, fmt.Errorf("open")
}
v := C.Map(fd, C.int(size))
if v == nil {
C.Close(fd, nil, C.int(size))
C.Delete(C.CString(name))
}
return &shmi{name, fd, v, size, false}, nil
}
func (o *shmi) close() error {
if o.v != nil {
C.Close(o.fd, o.v, C.int(o.size))
o.v = nil
}
if o.parent {
C.Delete(C.CString(o.name))
}
return nil
}
// read shared memory. return read size.
func (o *shmi) readAt(p []byte, off int64) (n int, err error) {
if off >= int64(o.size) {
return 0, io.EOF
}
if max := int64(o.size) - off; int64(len(p)) > max {
p = p[:max]
}
return copyPtr2Slice(uintptr(o.v), p, off, o.size), nil
}
// write shared memory. return write size.
func (o *shmi) writeAt(p []byte, off int64) (n int, err error) {
if off >= int64(o.size) {
return 0, io.EOF
}
if max := int64(o.size) - off; int64(len(p)) > max {
p = p[:max]
}
return copySlice2Ptr(p, uintptr(o.v), off, o.size), nil
}
+85
View File
@@ -0,0 +1,85 @@
//go:build windows && cgo
// +build windows,cgo
package sharedMem
import (
"io"
"log"
"golang.org/x/sys/windows"
)
type shmi struct {
h windows.Handle
v uintptr
size uint32
}
// create shared memory. return shmi object.
func create(name string, size uint32) (*shmi, error) {
fnPtr, _ := windows.UTF16PtrFromString(name)
flProtect := uint32(windows.PAGE_READONLY)
h, errno := windows.CreateFileMapping(
windows.InvalidHandle,
nil,
flProtect,
0,
size,
fnPtr)
if h == 0 {
log.Fatal("could not open memmap file: ", errno)
}
addr, errno := windows.MapViewOfFile(h,
windows.FILE_MAP_READ,
0,
0,
uintptr(size))
if addr == 0 {
log.Printf("error in MapViewOfFile: %v", errno)
}
return &shmi{h, addr, size}, nil
}
// open shared memory. return shmi object.
func open(name string, size uint32) (*shmi, error) {
return create(name, size)
}
func (o *shmi) close() error {
if o.v != uintptr(0) {
windows.UnmapViewOfFile(o.v)
o.v = uintptr(0)
}
if o.h != windows.InvalidHandle {
windows.CloseHandle(o.h)
o.h = windows.InvalidHandle
}
return nil
}
// read shared memory. return read size.
func (o *shmi) readAt(p []byte, off int64) (n int, err error) {
if off >= int64(o.size) {
return 0, io.EOF
}
if max := int64(o.size) - off; int64(len(p)) > max {
p = p[:max]
}
return copyPtr2Slice(o.v, p, off, o.size), nil
}
// write shared memory. return write size.
func (o *shmi) writeAt(p []byte, off int64) (n int, err error) {
if off >= int64(o.size) {
return 0, io.EOF
}
if max := int64(o.size) - off; int64(len(p)) > max {
p = p[:max]
}
return copySlice2Ptr(p, o.v, off, o.size), nil
}
+15
View File
@@ -0,0 +1,15 @@
package sharedMem
import (
"unsafe"
)
func copySlice2Ptr(b []byte, p uintptr, off int64, size uint32) int {
bb := unsafe.Slice((*byte)(*(*unsafe.Pointer)(unsafe.Pointer(&p))), int(size))
return copy(bb[off:], b)
}
func copyPtr2Slice(p uintptr, b []byte, off int64, size uint32) int {
bb := unsafe.Slice((*byte)(*(*unsafe.Pointer)(unsafe.Pointer(&p))), int(size))
return copy(b, bb[off:size])
}
Binary file not shown.
Binary file not shown.
+19
View File
@@ -0,0 +1,19 @@
package utils
import "fmt"
func HexDump(buf []byte) {
fmt.Printf("\n============ HEX DUMP =============\n")
for k := 0; k < len(buf); k++ {
if k%4 == 0 && k > 0 {
fmt.Printf(" ")
}
if k%16 == 0 && k > 0 {
fmt.Printf("\n")
}
fmt.Printf("%02X", buf[k])
}
fmt.Printf("\n============ HEX DUMP =============\n")
}
+252 -177
View File
@@ -1,177 +1,252 @@
package ibtReader package goirsdk
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"io" "io"
"log" "log"
"math" "math"
"strings" "strings"
) "time"
)
const (
VarHeaderSize = 144 const (
IRSDK_char = 0 VarHeaderSize = 144
IRSDK_bool = 1 IRSDK_char = 0
IRSDK_int = 2 IRSDK_bool = 1
IRSDK_bitField = 3 IRSDK_int = 2
IRSDK_float = 4 IRSDK_bitField = 3
IRSDK_double = 5 IRSDK_float = 4
) IRSDK_double = 5
Running IRacingState = iota
// I think I can make an interface if IRSDK types with available types and Paused
// that they need a parser (reads and type coerces I guess) Ended
var ( Failed
VarTypes = map[int]VarType{ Unknown
IRSDK_char: {1, "irsdk_char"}, )
IRSDK_bool: {1, "irsdk_bool"},
IRSDK_int: {4, "irsdk_int"}, // I think I can make an interface if IRSDK types with available types and
IRSDK_bitField: {4, "irsdk_bitField"}, // that they need a parser (reads and type coerces I guess)
IRSDK_float: {4, "irsdk_float"}, var (
IRSDK_double: {8, "irsdk_double"}, VarTypes = map[int]VarType{
} IRSDK_char: {1, "irsdk_char"},
) IRSDK_bool: {1, "irsdk_bool"},
IRSDK_int: {4, "irsdk_int"},
type VarType struct { IRSDK_bitField: {4, "irsdk_bitField"},
Size int // Size is the var type size in bytes IRSDK_float: {4, "irsdk_float"},
Name string // Name is the irsdk var name IRSDK_double: {8, "irsdk_double"},
} }
)
type IBTVar struct {
Type int32 type IRacingState int
Offset int32 type VarType struct {
Count int32 Size int // Size is the var type size in bytes
CountAsTime bool Name string // Name is the irsdk var name
Padding [3]byte }
Name [32]byte
Description [64]byte type IBTVar struct {
Unit [32]byte Type int32
} Offset int32
Count int32
type Var struct { CountAsTime bool
Type int32 Padding [3]byte
Offset int32 Name [32]byte
Count int32 Description [64]byte
CountAsTime bool Unit [32]byte
Name string }
Description string
Unit string type Var struct {
// TODO Type int32
// Create an interface for this value Offset int32
// Represent the IRSDK var types with a struct each that implements the Parse Count int32
// method or something like that I guess CountAsTime bool
Value interface{} Name string
} Description string
Unit string
func (v *IBTVar) ToString() string { // TODO
return fmt.Sprintf( // Create an interface for this value
"Type: %5d (0x%08x)\n"+ // Represent the IRSDK var types with a struct each that implements the Parse
"Offset: %5d (0x%08x)\n"+ // method or something like that I guess
"Count: %5d (0x%08x)\n"+ Value interface{}
"CountAsTime: %5t\n"+ }
"Name: %s\n"+
"Description: %s\n"+ func (v *IBTVar) ToString() string {
"Unit: %s", return fmt.Sprintf(
v.Type, v.Type, v.Offset, v.Offset, v.Count, v.Count, "Type: %5d (0x%08x)\n"+
v.CountAsTime, v.Name, v.Description, v.Unit, "Offset: %5d (0x%08x)\n"+
) "Count: %5d (0x%08x)\n"+
} "CountAsTime: %5t\n"+
"Name: %s\n"+
type varBuffer struct { "Description: %s\n"+
tickCount int "Unit: %s",
bufOffset int v.Type, v.Type, v.Offset, v.Offset, v.Count, v.Count,
} v.CountAsTime, v.Name, v.Description, v.Unit,
)
type TelemetryVars struct { }
LastVersion int
Vars map[string]Var type varBuffer struct {
} TickCount int32
BufOffset int32
func (i *IBT) readVariablerHeaders() error { }
i.Vars = &TelemetryVars{Vars: make(map[string]Var, i.Headers.NumVars)}
type TelemetryVars struct {
var k int32 Tick int32 // Keeps track of the current data buffer tick
for k = 0; k < i.Headers.NumVars; k++ { RecorderTick int32 // Counts from 0 when creating a telemetry file from a replay or live data
rbuf := make([]byte, VarHeaderSize) Vars map[string]Var // Variables content
}
_, err := i.File.ReadAt(rbuf, int64(i.Headers.VarHeaderOffset+k*VarHeaderSize))
if err != nil { func (i *IBT) readVariablerHeaders() error {
return err i.Vars = &TelemetryVars{Vars: make(map[string]Var, i.Headers.NumVars)}
}
var k int32
var dst IBTVar for k = 0; k < i.Headers.NumVars; k++ {
err = binary.Read(bytes.NewBuffer(rbuf[:]), binary.LittleEndian, &dst) rbuf := make([]byte, VarHeaderSize)
if err != nil {
return err _, err := i.File.ReadAt(rbuf, int64(i.Headers.VarHeaderOffset+k*VarHeaderSize))
} if err != nil {
return err
v := Var{ }
Type: dst.Type,
Offset: dst.Offset, _, err = i.FileToExport.WriteAt(rbuf, int64(i.Headers.VarHeaderOffset+k*VarHeaderSize))
Count: dst.Count, if err != nil {
CountAsTime: dst.CountAsTime, log.Fatal(err)
Name: strings.TrimRight(string(dst.Name[:]), "\x00"), }
Description: strings.TrimRight(string(dst.Description[:]), "\x00"), i.FileToExport.Sync()
Unit: strings.TrimRight(string(dst.Unit[:]), "\x00"),
Value: nil, var dst IBTVar
} err = binary.Read(bytes.NewBuffer(rbuf[:]), binary.LittleEndian, &dst)
if err != nil {
i.Vars.Vars[v.Name] = v return err
} }
return nil v := Var{
} Type: dst.Type,
Offset: dst.Offset,
func (i *IBT) readData() error { Count: dst.Count,
// I think that we can add one extra check or verification here CountAsTime: dst.CountAsTime,
// The file headers tells us how many data frames there are, we can probably Name: strings.TrimRight(string(dst.Name[:]), "\x00"),
// cap it at that instead of waiting for the read to fail Description: strings.TrimRight(string(dst.Description[:]), "\x00"),
// Probably wouldn't work on live data tho Unit: strings.TrimRight(string(dst.Unit[:]), "\x00"),
start := i.Headers.BufOffset + i.Tick*i.Headers.BufLen Value: nil,
buf := make([]byte, i.Headers.BufLen) }
_, err := i.File.ReadAt(buf, int64(start))
if err != nil { i.Vars.Vars[v.Name] = v
return err }
}
return nil
for k, v := range i.Vars.Vars { }
rbuf := buf[v.Offset : v.Offset+int32(VarTypes[int(v.Type)].Size)]
func (i *IBT) readData(buf []byte) error {
// Read the value for k, v := range i.Vars.Vars {
switch v.Type { // Slice of the variable value in the buffer
case IRSDK_char: rbuf := buf[v.Offset : v.Offset+int32(VarTypes[int(v.Type)].Size)]
v.Value = string(rbuf[0])
case IRSDK_bool: // Read the value
v.Value = int(rbuf[0]) > 0 switch v.Type {
case IRSDK_int: case IRSDK_char:
v.Value = int(binary.LittleEndian.Uint32(rbuf)) v.Value = string(rbuf[0])
case IRSDK_bitField: case IRSDK_bool:
v.Value = fmt.Sprintf("0x%x", int(binary.LittleEndian.Uint32(rbuf))) v.Value = int(rbuf[0]) > 0
case IRSDK_float: case IRSDK_int:
v.Value = math.Float32frombits(uint32(binary.LittleEndian.Uint32(rbuf))) v.Value = int(binary.LittleEndian.Uint32(rbuf))
case IRSDK_double: case IRSDK_bitField:
v.Value = math.Float64frombits(uint64(binary.LittleEndian.Uint64(rbuf))) 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:
i.Vars.Vars[k] = v v.Value = math.Float64frombits(uint64(binary.LittleEndian.Uint64(rbuf)))
} }
// --------------
i.Tick++
i.Vars.Vars[k] = v
return nil }
}
return nil
func (i *IBT) Update() bool { }
err := i.readData()
if err != nil && err != io.EOF { func (i *IBT) Update(timeout time.Duration) (IRacingState, error) {
log.Fatalf("What happened?\n%v\n", err) if i.winUtils != nil {
} // Put a way to check if the sim is active here
// fmt.Println("NOT CHECKING IF SIM IS ACTIVE - ADD ME")
if err == io.EOF {
return false // WORKING HERE
} // Need to figure out how to grab the latest buffer with data
var vb varBuffer
return true foundTickCount := 0
} for k := 0; k < int(i.Headers.NumBuf); k++ {
rbuf := make([]byte, 16)
// Read 16 bytes, I don't know why, but do need to understand this
_, err := i.File.ReadAt(rbuf, int64(48+k*16))
if err != nil {
return Failed, err
}
var curVb varBuffer
err = binary.Read(bytes.NewBuffer(rbuf[:]), binary.LittleEndian, &curVb)
if err != nil {
return Failed, err
}
if foundTickCount < int(curVb.TickCount) {
foundTickCount = int(curVb.TickCount)
vb = curVb
}
}
i.Vars.Tick = vb.TickCount
start := vb.BufOffset
buf := make([]byte, i.Headers.BufLen)
_, err := i.File.ReadAt(buf, int64(start))
if err != nil {
return Failed, err
}
_, err = i.FileToExport.WriteAt(buf, int64(i.Headers.BufOffset+i.Vars.RecorderTick*i.Headers.BufLen))
if err != nil {
log.Fatalf("Failed to write to file [2]: %v", err)
}
err = i.readData(buf)
if err != nil && err != io.EOF {
return Unknown, err
}
if err == io.EOF {
return Ended, nil
}
i.Vars.RecorderTick++
} else {
// This will get the dataframe corresponding to a given tick
start := i.Headers.BufOffset + i.Vars.Tick*i.Headers.BufLen
buf := make([]byte, i.Headers.BufLen)
_, err := i.File.ReadAt(buf, int64(start))
// Make this happen in a different thread, or have this send to a queue that has a thread
// writing to a file
_, err = i.FileToExport.WriteAt(buf, int64(start))
if err != nil {
log.Fatalf("Failed to write to file [1]: %v\n", err)
}
if err == io.EOF {
return Ended, nil
}
if err != nil {
return Unknown, err
}
err = i.readData(buf)
if err != nil && err != io.EOF {
log.Fatalf("What happened?\n%v\n", err)
}
// This was previously in the read data method, but it probably fits here better
i.Vars.Tick++
}
return Running, nil
}
+59
View File
@@ -0,0 +1,59 @@
// I should rename winutils to something else but what this package does
// is interface some windows stuff that we need for the:
// - Broadcast Channel
// - Valid Data Event windows thing
package winutils
import (
"github.com/ESilva15/goirsdk/sharedMem"
"io"
"time"
)
type Reader interface {
io.Reader
io.ReaderAt
io.ReadCloser
}
type IRacingWinUtils struct {
Utils *utils
}
func Init() (*IRacingWinUtils, error) {
u, err := newUtils()
if err != nil {
return nil, err
}
return &IRacingWinUtils{u}, nil
}
func (u *IRacingWinUtils) Close() {
u.Utils.Close()
}
// OpenMemMap returns a Reader interface that can be used to read the data
// No need to encapsulate it
func OpenMemMap(path string, size uint32) (Reader, error) {
file, err := sharedMem.Open(path, size)
if err != nil {
return nil, err
}
return file, nil
}
// OpenWinEvent will open the named windows event
func (u *IRacingWinUtils) OpenWinEvent(name string) error {
return u.Utils.OpenEvent(name)
}
// OpenWinEvent will open the broadcast channel
func (u *IRacingWinUtils) OpenBroadcastChannel(name string) error {
return u.Utils.OpenBroadcastChannel(name)
}
// CheckValidDataEvent checks if our windows even is telling us we are good to go
func (u *IRacingWinUtils) CheckValidDataEvent(timeout time.Duration) bool {
return u.Utils.CheckValidDataEvent(timeout)
}
+53
View File
@@ -0,0 +1,53 @@
//go:build (linux && cgo) || (darwin && cgo)
// +build linux,cgo darwin,cgo
package winutils
import (
"errors"
"sync"
"time"
)
const (
WAIT_OBJECT_0 = 0
WAIT_TIMEOUT = 258
)
var (
once sync.Once
ErrUnsupportedOS = errors.New("not found")
)
type utils struct {
}
// INITIALIZATION
func newUtils() (*utils, error) {
return nil, ErrUnsupportedOS
}
func (u *utils) Close() {
}
// openEvent opens a windows.Handle for a given event
func (u *utils) OpenEvent(eventName string) error {
return ErrUnsupportedOS
}
// OpenBroadcastChannel opens up a broadcast channel to send commands to iracing
func (u *utils) OpenBroadcastChannel(name string) error {
return ErrUnsupportedOS
}
// INITIALIZATION
// openEvent waits for a good response for some given time
func (u *utils) CheckValidDataEvent(timeout time.Duration) bool {
return false
}
// SendBroadcastMessage sends a message trough the broadcast channel
func (u *utils) SendBroadcastMessage(id, p1, p2 uintptr) error {
return ErrUnsupportedOS
}
+122
View File
@@ -0,0 +1,122 @@
//go:build windows && cgo
// +build windows,cgo
package winutils
import (
"sync"
"time"
"unsafe"
"golang.org/x/sys/windows"
)
const (
WAIT_OBJECT_0 = 0
WAIT_TIMEOUT = 258
)
var (
once sync.Once
)
type utils struct {
user32DLL *windows.LazyDLL
wEvent *windows.Handle
wBroadcastChn uintptr
}
// INITIALIZATION
func newUtils() (*utils, error) {
return &utils{
user32DLL: openUser32DLL(),
}, nil
}
func (u *utils) Close() {
closeEvent(u.wEvent)
// Do we need to unload the user32DLL ???
// Do we need to close the broadcast channel ???
}
// openEvent opens a windows.Handle for a given event
func (u *utils) OpenEvent(eventName string) error {
name, err := windows.UTF16PtrFromString(eventName)
if err != nil {
return err
}
event, err := windows.OpenEvent(windows.SYNCHRONIZE, false, name)
if err != nil {
return err
}
u.wEvent = &event
return nil
}
// loadUser32DLL loads the user32.dll which is used to create some processes
func openUser32DLL() *windows.LazyDLL {
return windows.NewLazyDLL("user32.dll")
}
// OpenBroadcastChannel opens up a broadcast channel to send commands to iracing
func (u *utils) OpenBroadcastChannel(name string) error {
registerWindowsMessageW := u.user32DLL.NewProc("RegisterWindowMessageW")
msgPtr, err := windows.UTF16PtrFromString(name)
if err != nil {
return err
}
ret, _, err := registerWindowsMessageW.Call(uintptr(unsafe.Pointer(msgPtr)))
if ret == 0 {
return err
}
u.wBroadcastChn = ret
return nil
}
// INITIALIZATION
// closeEvent closes a given windows.Handle
func closeEvent(h *windows.Handle) {
windows.CloseHandle(*h)
}
// openEvent waits for a good response for some given time
func (u *utils) CheckValidDataEvent(timeout time.Duration) bool {
t0 := time.Now().UnixNano()
timeoutInt := uint32(timeout / time.Millisecond)
result, err := windows.WaitForSingleObject(*u.wEvent, timeoutInt)
if err != nil {
remaining := timeoutInt - uint32((time.Now().UnixNano()-t0)/1000000)
if remaining > 0 {
time.Sleep(time.Duration(remaining) * time.Millisecond)
}
return false
}
// Check the result of the wait
if result == WAIT_OBJECT_0 {
return true
} else if result == WAIT_TIMEOUT {
return false
}
return false
}
// SendBroadcastMessage sends a message trough the broadcast channel
func (u *utils) SendBroadcastMessage(id, p1, p2 uintptr) error {
sendMsg := u.user32DLL.NewProc("SendNotifyMessageW")
ret, _, err := sendMsg.Call(0xffff, id, p1, p2)
if ret == 1 {
return nil
} else {
return err
}
}