diff --git a/README.md b/README.md index 4abdcdc..7f00548 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,113 @@ +# TODO +- [x] Read live telemetry (from a live session or replay) +- [x] Read data from a stored `.ibt` file +- [x] Allow to export the data to an `.ibt` file +- [x] Allow to export the session info data to a `.yaml` file +- [ ] Add the message broadcasting system +- [ ] Explore a more convenient API for fetching the data for the SDK user +- [ ] Change the pattern in which the data is fetched from the telemetry and +how it is exported into `.ibt` files + + # 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. +This project is a simple Go SDK for the popular iRacing racing simulator. +It has the capabilites to: +- Read live data (live session or replay) +- Read data from a `.ibt` telemetry file + +It should run on Linux, MacOS and Windows. With the caveat that live sessions +only happen on Windows (that I know about), therefore Linux and MacOS can only +read data from telemetry files. -## 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. +## Usage +The SDK instance is created by calling `goirsdk.Init(Reader, exportTelem, exportYAML)` +- `Reader` is a variable that implements the interface: +```go +type Reader interface { + io.Reader + io.ReaderAt + io.ReadCloser +} +``` +To read data from a `.ibt` file, the user should pass the `*os.File` of it, and +to read live telemetry the user should pass nil + +- `exportTelem` should be an empty string if the user doesn't want to export +the data, otherwise pass a string with the path for the destination telemetry +file + +- `exportYAML` is just like the exportTelem but for the session info `yaml` data + +### Example +```go +package main + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/ESilva15/goirsdk" +) + +func msToKph(v float32) int { + return int((3600 * v) / 1000) +} + +func main() { + // Open the data source file + file, err := os.Open("/path/to/ibtFile") + if err != nil { + log.Fatalf("Failed to open IBT file: %v", err) + } + + // Instantiate our iRacing SDK instance + irsdk, err := goirsdk.Init(file, "", "") + if err != nil { + log.Fatalf("Failed to create iRacing interface: %v", err) + } + defer irsdk.Close() + + // Set up a loop to iterate our data + mainLoopTicker := time.NewTicker(time.Second / 60) + defer mainLoopTicker.Stop() + + for { + // Update the data that the SDK is holding with the next tick + _, err := irsdk.Update(100 * time.Millisecond) + if err != nil { + log.Printf("could not update data: %v", err) + continue + } + + // Vehicle Movement data gathered from the names we can find on the + // telemetry_docs.pdf file + // - I wish to make this less verbose if possible + if _, ok := irsdk.Vars.Vars["Gear"]; !ok { + log.Fatal("Field `Gear` doesn't exist") + } + + if _, ok := irsdk.Vars.Vars["RPM"]; !ok { + log.Fatal("Field `RPM` doesn't exist") + } + + if _, ok := irsdk.Vars.Vars["Speed"]; !ok { + log.Fatal("Field `Speed` doesn't exist") + } + + gear := int32(irsdk.Vars.Vars["Gear"].Value.(int)) + rpm := int32(irsdk.Vars.Vars["RPM"].Value.(float32)) + speed := int32(msToKph(irsdk.Vars.Vars["Speed"].Value.(float32))) + + fmt.Printf("\033[?25l\033[2J\033[H") + fmt.Printf("Gear: %d, RPM: %d, Speed: %d", gear, rpm, speed) + + <-mainLoopTicker.C + } +} +``` ## SharedMem diff --git a/examples/example.go b/examples/example.go new file mode 100644 index 0000000..5869085 --- /dev/null +++ b/examples/example.go @@ -0,0 +1,67 @@ +package main + +import ( + "fmt" + "log" + "os" + "time" + + "github.com/ESilva15/goirsdk" +) + +func msToKph(v float32) int { + return int((3600 * v) / 1000) +} + +func main() { + // Open the data source file + file, err := os.Open("/path/to/ibtFile") + if err != nil { + log.Fatalf("Failed to open IBT file: %v", err) + } + + // Instantiate our iRacing SDK instance + irsdk, err := goirsdk.Init(file, "", "") + if err != nil { + log.Fatalf("Failed to create iRacing interface: %v", err) + } + defer irsdk.Close() + + // Set up a loop to iterate our data + mainLoopTicker := time.NewTicker(time.Second / 60) + defer mainLoopTicker.Stop() + + for { + // Update the data that the SDK is holding with the next tick + _, err := irsdk.Update(100 * time.Millisecond) + if err != nil { + log.Printf("could not update data: %v", err) + continue + } + + // Vehicle Movement data gathered from the names we can find on the + // telemetry_docs.pdf file + // - I wish to make this less verbose if possible + if _, ok := irsdk.Vars.Vars["Gear"]; !ok { + log.Fatal("Field `Gear` doesn't exist") + } + + if _, ok := irsdk.Vars.Vars["RPM"]; !ok { + log.Fatal("Field `RPM` doesn't exist") + } + + if _, ok := irsdk.Vars.Vars["Speed"]; !ok { + log.Fatal("Field `Speed` doesn't exist") + } + + gear := int32(irsdk.Vars.Vars["Gear"].Value.(int)) + rpm := int32(irsdk.Vars.Vars["RPM"].Value.(float32)) + speed := int32(msToKph(irsdk.Vars.Vars["Speed"].Value.(float32))) + + fmt.Printf("\033[?25l\033[2J\033[H") + fmt.Printf("Gear: %d, RPM: %d, Speed: %d", gear, rpm, speed) + + <-mainLoopTicker.C + } +} + diff --git a/irsdk.go b/irsdk.go index b8f9f63..e2e13cb 100644 --- a/irsdk.go +++ b/irsdk.go @@ -2,8 +2,6 @@ package goirsdk import ( - // "github.com/ESilva15/goirsdk/logger" - "fmt" "os" @@ -79,21 +77,27 @@ func (i *IBT) exportYAML() error { } func (i *IBT) exportIBT(data []byte, offset int64) error { - log := logger.GetInstance() + log := logger.GetInstance() _, err := i.IBTExport.WriteAt(data, offset) - if err != nil { - i.IBTExport.Close() - i.IBTExport = nil - log.Println("Won't attempt to export anymore") - return err - } + if err != nil { + i.IBTExport.Close() + i.IBTExport = nil + log.Println("Won't attempt to export anymore") + return err + } return nil } // Init serves to initialize and get a hold of a IBT struct +// f -> is the source data, pass nil for the SDK to read live data or a +// *os.File to read from a file +// exportTelem -> is a string with the path to export the telemetry data, pass +// an empty string to not export any data +// exportTelem -> is a string with the path to export the session info data, pass +// an empty string to not export any data func Init(f Reader, exportTelem string, exportYAML string) (*IBT, error) { // log := logger.GetInstance() @@ -172,6 +176,7 @@ func Init(f Reader, exportTelem string, exportYAML string) (*IBT, error) { return &ibt, nil } +// Close cleans up our irsdk instance func (i *IBT) Close() { if i.winUtils != nil { // If its not live data, the user is the one with ownership of the handle diff --git a/variables.go b/variables.go index c1fff56..ad1ab4c 100644 --- a/variables.go +++ b/variables.go @@ -167,6 +167,8 @@ func (i *IBT) readData(buf []byte) error { return nil } +// Update will read the next data chunk from the telemetry data, works for both the +// live and offline data func (i *IBT) Update(timeout time.Duration) (IRacingState, error) { if i.winUtils != nil { // Put a way to check if the sim is active here