Skip to content

Commit

Permalink
fit: Added support for dynamic subfields
Browse files Browse the repository at this point in the history
  • Loading branch information
mlofjard committed Feb 10, 2024
1 parent 33e5851 commit 6219d57
Show file tree
Hide file tree
Showing 10 changed files with 429 additions and 95 deletions.
3 changes: 1 addition & 2 deletions format/fit/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,4 @@
4. Run `npm install` if it's your first time
5. Run `node index.js t /PathToSDK/Profile.xlsx > ../../mappers/types_generated.go`
6. Run `node index.js m /PathToSDK/Profile.xlsx > ../../mappers/messages_generated.go`
7. Edit `messages_generated.go` and remove the incorrect "Scale" from line ~461
8. Correct spelling of farenheit->fahrenheit and bondary->boundary to please Go linter
8. Correct formating and spelling of farenheit->fahrenheit and bondary->boundary in generated files to please Go linter
87 changes: 57 additions & 30 deletions format/fit/fit.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,15 @@ type fileDescriptionContext struct {
nativeMsgNo uint64
}

type valueType struct {
value uint64
typ string
}

type devFieldDefMap map[uint64]map[uint64]mappers.FieldDef
type localFieldDefMap map[uint64]map[uint64]mappers.FieldDef
type localFieldDefMap map[uint64]map[uint64]mappers.LocalFieldDef
type localMsgIsDevDef map[uint64]bool
type valueMap map[string]valueType

// "Magic" numbers
const (
Expand Down Expand Up @@ -133,7 +139,7 @@ func fitDecodeDefinitionMessage(d *decode.D, drc *dataRecordContext, lmfd localF
isDevMap[drc.localMessageType] = messageNo == developerFieldDescMesgNo

numFields := d.FieldU8("fields")
lmfd[drc.localMessageType] = make(map[uint64]mappers.FieldDef, numFields)
lmfd[drc.localMessageType] = make(map[uint64]mappers.LocalFieldDef, numFields)

d.FieldArray("field_definitions", func(d *decode.D) {
for i := uint64(0); i < numFields; i++ {
Expand All @@ -146,18 +152,21 @@ func fitDecodeDefinitionMessage(d *decode.D, drc *dataRecordContext, lmfd localF
fDefLookup, isSet := mappers.FieldDefMap[messageNo][fieldDefNo]
if isSet {
var foundName = fDefLookup.Name
lmfd[drc.localMessageType][i] = mappers.FieldDef{
Name: foundName,
Type: typ,
Size: size,
Format: fDefLookup.Type,
Unit: fDefLookup.Unit,
Scale: fDefLookup.Scale,
Offset: fDefLookup.Offset,
lmfd[drc.localMessageType][i] = mappers.LocalFieldDef{
Name: foundName,
Type: typ,
Size: size,
Format: fDefLookup.Type,
Unit: fDefLookup.Unit,
Scale: fDefLookup.Scale,
Offset: fDefLookup.Offset,
GlobalFieldDef: fDefLookup,
GlobalMessageNo: messageNo,
GlobalFieldDefNo: fieldDefNo,
}
} else {
var foundName = fmt.Sprintf("UNKNOWN_%d", fieldDefNo)
lmfd[drc.localMessageType][i] = mappers.FieldDef{
lmfd[drc.localMessageType][i] = mappers.LocalFieldDef{
Name: foundName,
Type: typ,
Size: size,
Expand All @@ -181,7 +190,7 @@ func fitDecodeDefinitionMessage(d *decode.D, drc *dataRecordContext, lmfd localF

if isSet {
var foundName = fDefLookup.Name
lmfd[drc.localMessageType][i] = mappers.FieldDef{
lmfd[drc.localMessageType][i] = mappers.LocalFieldDef{
Name: foundName,
Type: fDefLookup.Type,
Size: size,
Expand All @@ -191,7 +200,7 @@ func fitDecodeDefinitionMessage(d *decode.D, drc *dataRecordContext, lmfd localF
}
} else {
var foundName = fmt.Sprintf("UNKNOWN_%d", fieldDefNo)
lmfd[drc.localMessageType][i] = mappers.FieldDef{
lmfd[drc.localMessageType][i] = mappers.LocalFieldDef{
Name: foundName,
Type: "UNKNOWN",
Size: size,
Expand All @@ -202,10 +211,9 @@ func fitDecodeDefinitionMessage(d *decode.D, drc *dataRecordContext, lmfd localF
}
})
}

}

func fieldUint(fieldFn func(string, ...scalar.UintMapper) uint64, expectedSize uint64, fDef mappers.FieldDef, uintFormatter scalar.UintFn, fdc *fileDescriptionContext) {
func fieldUint(fieldFn func(string, ...scalar.UintMapper) uint64, expectedSize uint64, fDef mappers.LocalFieldDef, uintFormatter scalar.UintFn, fdc *fileDescriptionContext, valMap valueMap) {
var val uint64

if fDef.Size != expectedSize {
Expand All @@ -214,11 +222,33 @@ func fieldUint(fieldFn func(string, ...scalar.UintMapper) uint64, expectedSize u
fieldFn(fmt.Sprintf("%s_%d", fDef.Name, i), uintFormatter)
}
} else {
if uintFormatter != nil {
val = fieldFn(fDef.Name, uintFormatter)
if fDef.GlobalFieldDef.HasSubField {
var found = false
if subFieldValueMap, ok := mappers.SubFieldDefMap[fDef.GlobalMessageNo][fDef.GlobalFieldDefNo]; ok {
for k := range subFieldValueMap {
if subFieldDef, ok := subFieldValueMap[k][mappers.TypeDefMap[valMap[k].typ][valMap[k].value].Name]; ok {
subUintFormatter := mappers.GetUintFormatter(mappers.LocalFieldDef{
Name: subFieldDef.Name,
Type: fDef.Type,
Size: fDef.Size,
Format: subFieldDef.Type,
Unit: subFieldDef.Unit,
Scale: subFieldDef.Scale,
Offset: subFieldDef.Offset,
})
val = fieldFn(subFieldDef.Name, subUintFormatter)
found = true
continue
}
}
}
if !found { // SubField conditions could not be resolved
val = fieldFn(fDef.Name, uintFormatter)
}
} else {
val = fieldFn(fDef.Name)
val = fieldFn(fDef.Name, uintFormatter)
}
valMap[fDef.Name] = valueType{value: val, typ: fDef.Format}

// Save developer field definitions
switch fDef.Name {
Expand All @@ -236,22 +266,18 @@ func fieldUint(fieldFn func(string, ...scalar.UintMapper) uint64, expectedSize u
}
}

func fieldSint(fieldFn func(string, ...scalar.SintMapper) int64, expectedSize uint64, fDef mappers.FieldDef, sintFormatter scalar.SintFn) {
func fieldSint(fieldFn func(string, ...scalar.SintMapper) int64, expectedSize uint64, fDef mappers.LocalFieldDef, sintFormatter scalar.SintFn) {
if fDef.Size != expectedSize {
arrayCount := fDef.Size / expectedSize
for i := uint64(0); i < arrayCount; i++ {
fieldFn(fmt.Sprintf("%s_%d", fDef.Name, i), sintFormatter)
}
} else {
if sintFormatter != nil {
fieldFn(fDef.Name, sintFormatter)
} else {
fieldFn(fDef.Name)
}
fieldFn(fDef.Name, sintFormatter)
}
}

func fieldFloat(fieldFn func(string, ...scalar.FltMapper) float64, expectedSize uint64, fDef mappers.FieldDef, floatFormatter scalar.FltFn) {
func fieldFloat(fieldFn func(string, ...scalar.FltMapper) float64, expectedSize uint64, fDef mappers.LocalFieldDef, floatFormatter scalar.FltFn) {
if fDef.Size != expectedSize {
arrayCount := fDef.Size / expectedSize
for i := uint64(0); i < arrayCount; i++ {
Expand All @@ -262,7 +288,7 @@ func fieldFloat(fieldFn func(string, ...scalar.FltMapper) float64, expectedSize
}
}

func fieldString(d *decode.D, fDef mappers.FieldDef, fdc *fileDescriptionContext) {
func fieldString(d *decode.D, fDef mappers.LocalFieldDef, fdc *fileDescriptionContext) {
val := d.FieldUTF8NullFixedLen(fDef.Name, int(fDef.Size), scalar.StrMapDescription{"": "Invalid"})

// Save developer field definitions
Expand All @@ -276,6 +302,7 @@ func fieldString(d *decode.D, fDef mappers.FieldDef, fdc *fileDescriptionContext

func fitDecodeDataMessage(d *decode.D, drc *dataRecordContext, lmfd localFieldDefMap, dmfd devFieldDefMap, isDevMap localMsgIsDevDef) {
var fdc fileDescriptionContext
valMap := make(valueMap, len(lmfd[drc.localMessageType]))
keys := make([]int, len(lmfd[drc.localMessageType]))
i := 0
for k := range lmfd[drc.localMessageType] {
Expand All @@ -295,13 +322,13 @@ func fitDecodeDataMessage(d *decode.D, drc *dataRecordContext, lmfd localFieldDe

switch fDef.Type {
case "enum", "uint8", "uint8z", "byte":
fieldUint(d.FieldU8, 1, fDef, uintFormatter, &fdc)
fieldUint(d.FieldU8, 1, fDef, uintFormatter, &fdc, valMap)
case "uint16", "uint16z":
fieldUint(d.FieldU16, 2, fDef, uintFormatter, &fdc)
fieldUint(d.FieldU16, 2, fDef, uintFormatter, &fdc, valMap)
case "uint32", "uint32z":
fieldUint(d.FieldU32, 4, fDef, uintFormatter, &fdc)
fieldUint(d.FieldU32, 4, fDef, uintFormatter, &fdc, valMap)
case "uint64", "uint64z":
fieldUint(d.FieldU64, 8, fDef, uintFormatter, &fdc)
fieldUint(d.FieldU64, 8, fDef, uintFormatter, &fdc, valMap)
case "sint8":
fieldSint(d.FieldS8, 1, fDef, sintFormatter)
case "sint16":
Expand Down
3 changes: 1 addition & 2 deletions format/fit/fit.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
### Limitations

- "compressed_speed_distance" field on globalMessageNumber 20 is not represented correctly.
- Fields with subcomponents, such as "compressed_speed_distance" field on globalMessageNumber 20 is not represented correctly.
The field is read as 3 separate bytes where the first 12 bits are speed and the last 12 bits are distance.
- There are still lots of UNKOWN fields due to gaps in Garmins SDK Profile documentation. (Currently FIT SDK 21.126)
- Dynamically referenced fields are named incorrectly and lacks scaling, offset and units (just raw values)
- Compressed timestamp messages are not accumulated against last known full timestamp.

### Convert stream of data messages to JSON array
Expand Down
27 changes: 20 additions & 7 deletions format/fit/mappers/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,26 @@ import (
)

type FieldDef struct {
Name string
Type string
Format string
Unit string
Scale float64
Offset int64
Size uint64
Name string
Type string
Unit string
Scale float64
Offset int64
Size uint64
HasSubField bool
}

type LocalFieldDef struct {
Name string
Type string
Format string
Unit string
Scale float64
Offset int64
Size uint64
GlobalFieldDef FieldDef
GlobalMessageNo uint64
GlobalFieldDefNo uint64
}

type fieldDefMap map[uint64]FieldDef
Expand Down
Loading

0 comments on commit 6219d57

Please sign in to comment.