Skip to content

Commit

Permalink
Feature refactor processor and parser (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
teamjorge authored Aug 21, 2024
1 parent 2318ce7 commit 4bc4a47
Show file tree
Hide file tree
Showing 18 changed files with 547 additions and 218 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ This package will not parse real-time telemetry as that requires opening a memor
* Quick parsing of file metadata.
* Grouping of *ibt* files into the sessions where they originate from.
* Great test coverage and code documentation.
* Freedom to use it your own way. Most of what is needed are public functions/methods.
* Freedom to use it your own way. Most functions/methods has been made public.

## Examples

Please see the `examples` folder for detailed usage instructions.
Please see the [`examples`](https://github.com/teamjorge/ibt/tree/main/examples) folder for detailed usage instructions.

To try the examples locally, please clone to repository:

Expand All @@ -49,6 +49,6 @@ go run examples/track_temp/main.go

# Or to run it with your own telemetry files

go run examples/track_temp/main.go /path/to/telem/files/*ibt
go run examples/track_temp/main.go /path/to/telem/files/*.ibt
```

18 changes: 18 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# examples

All examples allow you to run with the supplied `ibt` file or with your own.

For example:


```shell
go run examples/[Example Folder]/main.go

# Or

go run examples/[Example Folder]/main.go /path/to/telem/files/*.ibt
```

Available examples:

* [track temperature](./track_temp/README.md) -
42 changes: 42 additions & 0 deletions examples/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package examples

import (
"flag"
"fmt"
"path/filepath"

"github.com/teamjorge/ibt"
)

// Use the default testing file if no ibt files were provided
// Provided files can use wildcards, for example ./telemetry/*.ibt
func getExampleFilePattern() string {
flag.Parse()

var filePattern string
if flag.Arg(0) == "" {
filePattern = ".testing/valid_test_file.ibt"
} else {
filePattern = flag.Arg(0)
}

return filePattern
}

func ParseExampleStubs() (ibt.StubGroup, error) {
filePattern := getExampleFilePattern()

// Find all files for parsing in case it had included a wildcard
files, err := filepath.Glob(filePattern)
if err != nil {
return nil, fmt.Errorf("could not glob the given input files: %v", err)
}

// Parse the files into stubs
stubs, err := ibt.ParseStubs(files...)
if err != nil {
return nil, fmt.Errorf("failed to parse stubs for %v. error - %v", files, err)
}

return stubs, nil
}
17 changes: 17 additions & 0 deletions examples/track_temp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# track_temp

The `track_temp` example shows a simple processor summarising the track temperature on each lap of the provided `ibt` files.

## Running

From the root of the repository:

```shell
go run examples/track_temp/main.go

# Or with your own files

go run examples/track_temp/main.go /path/to/telem/files/*.ibt
```

Using the included `ibt` file will yield only a single lap and it's value. However, if you have telemetry consisting of a few laps and/or files from a longer session, you should have a nicely summarised per-lap output.
110 changes: 16 additions & 94 deletions examples/track_temp/main.go
Original file line number Diff line number Diff line change
@@ -1,113 +1,35 @@
package main

import (
"flag"
"fmt"
"context"
"log"
"os"
"path/filepath"
"sort"

"github.com/teamjorge/ibt"
"github.com/teamjorge/ibt/headers"
"github.com/teamjorge/ibt/utilities"
"golang.org/x/exp/maps"
"github.com/teamjorge/ibt/examples"
)

func main() {
flag.Parse()

// Use the default testing file if no ibt files were provided
// Provided files can use wildcards, for example ./telemetry/*.ibt
var filePattern string
if flag.Arg(0) == "" {
filePattern = ".testing/valid_test_file.ibt"
} else {
filePattern = flag.Arg(0)
}

// Find all files for parsing in case it had included a wildcard
files, err := filepath.Glob(filePattern)
if err != nil {
log.Fatalf("could not glob the given input files: %v", err)
}

// Parse the files into stubs
stubs, err := ibt.ParseStubs(files...)
stubs, err := examples.ParseExampleStubs()
if err != nil {
log.Fatalf("failed to parse stubs for %v. error - %v", files, err)
log.Fatal(err)
}

// Group the stubs into iRacing sessions
stubGroups := stubs.Group()

for _, stubGroup := range stubGroups {
for _, stub := range stubGroup {
stubFile, err := os.Open(stub.Filename())
if err != nil {
log.Fatalf("failed to open stub file %s for reading: %v", stub.Filename(), err)
}
// We group the stubs by iRacing session. This allows us to summarise results for
// an entire session, instead of just a single ibt file.
groups := stubs.Group()

// Create the instance(s) of your processor(s)
processor := NewTrackTempProcessor()
for groupIdx, group := range groups {
// Create the instance(s) of your processor(s) for this group
processor := newTrackTempProcessor()

// Process the available telemetry for the ibt file. This is currently only utilising the Track Temp processor,
// but can include as many as you want.
if err := ibt.Process(stubFile, *stub.Headers(), processor); err != nil {
log.Fatalf("failed to process telemetry for stub %s: %v", stub.Filename(), err)
}

// Print the summarised track temperature
processor.Print()
// Process the available telemetry for the ibt file. This is currently only utilising the Track Temp processor,
// but can include as many as you want.
if err := ibt.Process(context.Background(), group, processor); err != nil {
log.Fatalf("failed to process telemetry for group %d: %v", groupIdx, err)
}
}

}

type TrackTempProcessor struct {
tempMap map[int]float32
}

func NewTrackTempProcessor() *TrackTempProcessor {
t := new(TrackTempProcessor)

t.tempMap = make(map[int]float32)

return t
}

// Display name of the processor
func (t *TrackTempProcessor) Name() string { return "Track Temp" }

// Method used for processing every tick of telemetry
func (t *TrackTempProcessor) Process(input map[string]headers.VarHeader, hasNext bool, session *headers.Session) error {
TrackTempProcessor := input["TrackTempCrew"].Value.(float32)
lap := input["Lap"].Value.(int)

t.tempMap[lap] = TrackTempProcessor

return nil
}

// Utility function for create a result that can be joined with other processors.
//
// This will convert the results to map[int]interface{}, where the keys will refer to laps.
// Result is not yet required by any interfaces, but is useful when using multiple processors
// that summarise telemetry based by lap.
func (t *TrackTempProcessor) Result() map[int]interface{} {
return utilities.CreateGenericMap(t.tempMap)
}

// Columns required for the processor
func (t *TrackTempProcessor) Whitelist() []string { return []string{"Lap", "TrackTempCrew"} }

// Print the summarised Track Temperature
func (t *TrackTempProcessor) Print() {
fmt.Println("Track Temp:")
laps := maps.Keys(t.tempMap)
sort.Ints(laps)

for _, lap := range laps {
fmt.Printf("%03d - %.3f\n", lap, t.tempMap[lap])
// Print the summarised track temperature
processor.Print()
}
}
69 changes: 69 additions & 0 deletions examples/track_temp/processors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package main

import (
"fmt"
"sort"

"github.com/teamjorge/ibt"
"github.com/teamjorge/ibt/headers"
"github.com/teamjorge/ibt/utilities"
"golang.org/x/exp/maps"
)

// TrackTempProcessors tracks the track temperature for each lap of the ibt file
type trackTempProcessor struct {
tempMap map[int]float32
}

// NewTrackTempProcessor creates and initialises a new trackTempProcessor
func newTrackTempProcessor() *trackTempProcessor {
t := new(trackTempProcessor)

// tempMap will store a temperature value against a lap number
t.tempMap = make(map[int]float32)

return t
}

// Display name of the processor
func (t *trackTempProcessor) Name() string { return "Track Temp" }

// Method used for processing every tick of telemetry
func (t *trackTempProcessor) Process(input ibt.Tick, hasNext bool, session *headers.Session) error {
trackTemp, err := ibt.GetVariableValue[float32](input, "TrackTempCrew")
if err != nil {
return err
}

lap, err := ibt.GetVariableValue[int](input, "Lap")
if err != nil {
return err
}

t.tempMap[lap] = trackTemp

return nil
}

// Utility function for create a result that can be joined with other processors.
//
// This will convert the results to map[int]interface{}, where the keys will refer to laps.
// Result is not yet required by any interfaces, but is useful when using multiple processors
// that summarise telemetry based by lap.
func (t *trackTempProcessor) Result() map[int]interface{} {
return utilities.CreateGenericMap(t.tempMap)
}

// Columns required for the processor
func (t *trackTempProcessor) Whitelist() []string { return []string{"Lap", "TrackTempCrew"} }

// Print the summarised Track Temperature
func (t *trackTempProcessor) Print() {
fmt.Println("Track Temp:")
laps := maps.Keys(t.tempMap)
sort.Ints(laps)

for _, lap := range laps {
fmt.Printf("%03d - %.3f\n", lap, t.tempMap[lap])
}
}
4 changes: 2 additions & 2 deletions headers/var.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ type VarHeader struct {
// Rtype is the variable value type.
//
// Possible values:
// 0: String
// 0: Uint8
// 1: Boolean
// 2: Int
// 3: Byte
// 3: String
// 4: Float32
// 5: Float64
Rtype int `json:"rtype,omitempty"`
Expand Down
37 changes: 4 additions & 33 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package ibt

import (
"github.com/teamjorge/ibt/headers"
"golang.org/x/exp/maps"
)

// Parser is used to iterate and process telemetry variables for a given ibt file and it's headers.
Expand All @@ -27,7 +26,7 @@ func NewParser(reader headers.Reader, header headers.Header, whitelist ...string
p := new(Parser)

p.reader = reader
p.whitelist = computeVars(header.VarHeader(), whitelist...)
p.whitelist = whitelist

p.length = header.TelemetryHeader().BufLen
p.bufferOffset = header.TelemetryHeader().BufOffset
Expand All @@ -43,7 +42,7 @@ func NewParser(reader headers.Reader, header headers.Header, whitelist ...string
// a nil and false will be returned.
//
// Should expected variable values be missing, please ensure that they are added to the Parser whitelist.
func (p *Parser) Next() (map[string]headers.VarHeader, bool) {
func (p *Parser) Next() (Tick, bool) {
start := p.bufferOffset + (p.current * p.length)
currentBuf := p.read(start)
if currentBuf == nil {
Expand All @@ -54,13 +53,12 @@ func (p *Parser) Next() (map[string]headers.VarHeader, bool) {
nextStart := p.bufferOffset + ((p.current + 1) * p.length)
nextBuf := p.read(nextStart)

newVars := make(map[string]headers.VarHeader)
newVars := make(Tick)

for _, variable := range p.whitelist {
item := p.varHeader[variable]
val := readVarValue(currentBuf, item)
item.Value = val
newVars[variable] = item
newVars[variable] = val
}

p.current++
Expand All @@ -79,30 +77,3 @@ func (p *Parser) read(start int) []byte {

return buf
}

// compareVars will retrieve vars when * is used and ensure a unique list
//
// Variables that are not found in the VarHeader will automatically be excluded.
func computeVars(vars map[string]headers.VarHeader, whitelist ...string) []string {
if len(whitelist) == 0 {
return headers.AvailableVars(vars)
}

for _, col := range whitelist {
if col == "*" {
return headers.AvailableVars(vars)
}
}

// de-duplicate the columns
varMap := make(map[string]struct{})

for _, col := range whitelist {
// ensure it's a valid column
if _, ok := vars[col]; ok {
varMap[col] = struct{}{}
}
}

return maps.Keys(varMap)
}
Loading

0 comments on commit 4bc4a47

Please sign in to comment.