1
1
mirror of https://github.com/wader/fq.git synced 2024-09-11 20:07:11 +03:00

fit: Added support for dynamic subfields

This commit is contained in:
Mikael Lofjärd 2024-02-09 20:20:17 +01:00
parent 33e5851d03
commit 6219d57c35
10 changed files with 429 additions and 95 deletions

View File

@ -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

View File

@ -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 (
@ -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++ {
@ -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,
@ -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,
@ -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,
@ -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 {
@ -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 {
@ -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++ {
@ -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
@ -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] {
@ -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":

View File

@ -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

View File

@ -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

View File

@ -1,10 +1,207 @@
package mappers
var SubFieldDefMap = map[uint64]map[uint64]map[string]map[string]FieldDef{
0: {
2: {
"manufacturer": {
"garmin": {Name: "garmin_product", Type: "garmin_product"},
"dynastream": {Name: "garmin_product", Type: "garmin_product"},
"dynastream_oem": {Name: "garmin_product", Type: "garmin_product"},
"tacx": {Name: "garmin_product", Type: "garmin_product"},
},
},
},
18: {
10: {
"sport": {
"cycling": {Name: "total_strokes", Unit: "strokes"},
"swimming": {Name: "total_strokes", Unit: "strokes"},
"rowing": {Name: "total_strokes", Unit: "strokes"},
"stand_up_paddleboarding": {Name: "total_strokes", Unit: "strokes"},
},
},
18: {
"sport": {
"running": {Name: "avg_running_cadence", Unit: "strides/min"},
},
},
19: {
"sport": {
"running": {Name: "max_running_cadence", Unit: "strides/min"},
},
},
},
19: {
10: {
"sport": {
"cycling": {Name: "total_strokes", Unit: "strokes"},
"swimming": {Name: "total_strokes", Unit: "strokes"},
"rowing": {Name: "total_strokes", Unit: "strokes"},
"stand_up_paddleboarding": {Name: "total_strokes", Unit: "strokes"},
},
},
17: {
"sport": {
"running": {Name: "avg_running_cadence", Unit: "strides/min"},
},
},
18: {
"sport": {
"running": {Name: "max_running_cadence", Unit: "strides/min"},
},
},
},
21: {
3: {
"event": {
"radar_threat_alert": {Name: "radar_threat_alert"},
},
},
15: {
"event": {
"auto_activity_detect": {Name: "auto_activity_detect_start_timestamp", Type: "date_time", Unit: "s"},
},
},
},
23: {
1: {
"source_type": {
"local": {Name: "local_device_type", Type: "local_device_type"},
},
},
4: {
"manufacturer": {
"garmin": {Name: "garmin_product", Type: "garmin_product"},
"dynastream": {Name: "garmin_product", Type: "garmin_product"},
"dynastream_oem": {Name: "garmin_product", Type: "garmin_product"},
"tacx": {Name: "garmin_product", Type: "garmin_product"},
},
},
},
27: {
2: {
"duration_type": {
"reps": {Name: "duration_reps"},
},
},
4: {
"target_type": {
"swim_stroke": {Name: "target_stroke_type", Type: "swim_stroke"},
},
},
5: {
"target_type": {
"power": {Name: "custom_target_power_low", Type: "workout_power", Unit: "% or watts"},
},
},
6: {
"target_type": {
"power": {Name: "custom_target_power_high", Type: "workout_power", Unit: "% or watts"},
},
},
20: {
"secondary_target_type": {
"swim_stroke": {Name: "secondary_target_stroke_type", Type: "swim_stroke"},
},
},
21: {
"secondary_target_type": {
"power": {Name: "secondary_custom_target_power_low", Type: "workout_power", Unit: "% or watts"},
},
},
22: {
"secondary_target_type": {
"power": {Name: "secondary_custom_target_power_high", Type: "workout_power", Unit: "% or watts"},
},
},
},
28: {
1: {
"manufacturer": {
"garmin": {Name: "garmin_product", Type: "garmin_product"},
"dynastream": {Name: "garmin_product", Type: "garmin_product"},
"dynastream_oem": {Name: "garmin_product", Type: "garmin_product"},
"tacx": {Name: "garmin_product", Type: "garmin_product"},
},
},
},
38: {
3: {
"count_type": {
"max_per_file_type": {Name: "max_per_file_type"},
},
},
},
55: {
3: {
"activity_type": {
"cycling": {Name: "strokes", Unit: "strokes", Scale: 2},
"swimming": {Name: "strokes", Unit: "strokes", Scale: 2},
},
},
},
72: {
2: {
"manufacturer": {
"garmin": {Name: "garmin_product", Type: "garmin_product"},
"dynastream": {Name: "garmin_product", Type: "garmin_product"},
"dynastream_oem": {Name: "garmin_product", Type: "garmin_product"},
"tacx": {Name: "garmin_product", Type: "garmin_product"},
},
},
},
106: {
1: {
"manufacturer": {
"garmin": {Name: "garmin_product", Type: "garmin_product"},
"dynastream": {Name: "garmin_product", Type: "garmin_product"},
"dynastream_oem": {Name: "garmin_product", Type: "garmin_product"},
"tacx": {Name: "garmin_product", Type: "garmin_product"},
},
},
},
142: {
10: {
"sport": {
"cycling": {Name: "total_strokes", Unit: "strokes"},
},
},
},
159: {
1: {
"mode": {
"analog": {Name: "analog_layout", Type: "analog_watchface_layout"},
},
},
},
167: {
1: {
"sensor_type": {
"gyroscope": {Name: "gyro_cal_factor", Unit: "deg/s"},
},
},
},
210: {
1: {
"sensor_type": {
"barometer": {Name: "baro_cal_factor", Unit: "Pa"},
},
},
},
258: {
20: {
"heart_rate_source_type": {
"local": {Name: "heart_rate_local_device_type", Type: "local_device_type"},
},
},
},
}
var FieldDefMap = map[uint64]fieldDefMap{
0: {
0: {Name: "type", Type: "file"},
1: {Name: "manufacturer", Type: "manufacturer"},
2: {Name: "product"},
2: {Name: "product", HasSubField: true},
3: {Name: "serial_number"},
4: {Name: "time_created", Type: "date_time"},
5: {Name: "number"},
@ -180,15 +377,15 @@ var FieldDefMap = map[uint64]fieldDefMap{
7: {Name: "total_elapsed_time", Unit: "s", Scale: 1000},
8: {Name: "total_timer_time", Unit: "s", Scale: 1000},
9: {Name: "total_distance", Unit: "m", Scale: 100},
10: {Name: "total_cycles", Unit: "cycles"},
10: {Name: "total_cycles", Unit: "cycles", HasSubField: true},
11: {Name: "total_calories", Unit: "kcal"},
13: {Name: "total_fat_calories", Unit: "kcal"},
14: {Name: "avg_speed", Unit: "m/s", Scale: 1000},
15: {Name: "max_speed", Unit: "m/s", Scale: 1000},
16: {Name: "avg_heart_rate", Unit: "bpm"},
17: {Name: "max_heart_rate", Unit: "bpm"},
18: {Name: "avg_cadence", Unit: "rpm"},
19: {Name: "max_cadence", Unit: "rpm"},
18: {Name: "avg_cadence", Unit: "rpm", HasSubField: true},
19: {Name: "max_cadence", Unit: "rpm", HasSubField: true},
20: {Name: "avg_power", Unit: "watts"},
21: {Name: "max_power", Unit: "watts"},
22: {Name: "total_ascent", Unit: "m"},
@ -336,15 +533,15 @@ var FieldDefMap = map[uint64]fieldDefMap{
7: {Name: "total_elapsed_time", Unit: "s", Scale: 1000},
8: {Name: "total_timer_time", Unit: "s", Scale: 1000},
9: {Name: "total_distance", Unit: "m", Scale: 100},
10: {Name: "total_cycles", Unit: "cycles"},
10: {Name: "total_cycles", Unit: "cycles", HasSubField: true},
11: {Name: "total_calories", Unit: "kcal"},
12: {Name: "total_fat_calories", Unit: "kcal"},
13: {Name: "avg_speed", Unit: "m/s", Scale: 1000},
14: {Name: "max_speed", Unit: "m/s", Scale: 1000},
15: {Name: "avg_heart_rate", Unit: "bpm"},
16: {Name: "max_heart_rate", Unit: "bpm"},
17: {Name: "avg_cadence", Unit: "rpm"},
18: {Name: "max_cadence", Unit: "rpm"},
17: {Name: "avg_cadence", Unit: "rpm", HasSubField: true},
18: {Name: "max_cadence", Unit: "rpm", HasSubField: true},
19: {Name: "avg_power", Unit: "watts"},
20: {Name: "max_power", Unit: "watts"},
21: {Name: "total_ascent", Unit: "m"},
@ -539,7 +736,7 @@ var FieldDefMap = map[uint64]fieldDefMap{
0: {Name: "event", Type: "event"},
1: {Name: "event_type", Type: "event_type"},
2: {Name: "data16"},
3: {Name: "data"},
3: {Name: "data", HasSubField: true},
4: {Name: "event_group"},
7: {Name: "score"},
8: {Name: "opponent_score"},
@ -549,7 +746,7 @@ var FieldDefMap = map[uint64]fieldDefMap{
12: {Name: "rear_gear"},
13: {Name: "device_index", Type: "device_index"},
14: {Name: "activity_type", Type: "activity_type"},
15: {Name: "start_timestamp", Type: "date_time", Unit: "s"},
15: {Name: "start_timestamp", Type: "date_time", Unit: "s", HasSubField: true},
21: {Name: "radar_threat_level_max", Type: "radar_threat_level_type"},
22: {Name: "radar_threat_count"},
23: {Name: "radar_threat_avg_approach_speed", Unit: "m/s", Scale: 10},
@ -558,10 +755,10 @@ var FieldDefMap = map[uint64]fieldDefMap{
23: {
253: {Name: "timestamp", Type: "date_time", Unit: "s"},
0: {Name: "device_index", Type: "device_index"},
1: {Name: "device_type"},
1: {Name: "device_type", HasSubField: true},
2: {Name: "manufacturer", Type: "manufacturer"},
3: {Name: "serial_number"},
4: {Name: "product"},
4: {Name: "product", HasSubField: true},
5: {Name: "software_version", Scale: 100},
6: {Name: "hardware_version"},
7: {Name: "cum_operating_time", Unit: "s"},
@ -590,11 +787,11 @@ var FieldDefMap = map[uint64]fieldDefMap{
254: {Name: "message_index", Type: "message_index"},
0: {Name: "wkt_step_name"},
1: {Name: "duration_type", Type: "wkt_step_duration"},
2: {Name: "duration_value"},
2: {Name: "duration_value", HasSubField: true},
3: {Name: "target_type", Type: "wkt_step_target"},
4: {Name: "target_value"},
5: {Name: "custom_target_value_low"},
6: {Name: "custom_target_value_high"},
4: {Name: "target_value", HasSubField: true},
5: {Name: "custom_target_value_low", HasSubField: true},
6: {Name: "custom_target_value_high", HasSubField: true},
7: {Name: "intensity", Type: "intensity"},
8: {Name: "notes"},
9: {Name: "equipment", Type: "workout_equipment"},
@ -603,13 +800,13 @@ var FieldDefMap = map[uint64]fieldDefMap{
12: {Name: "exercise_weight", Unit: "kg", Scale: 100},
13: {Name: "weight_display_unit", Type: "fit_base_unit"},
19: {Name: "secondary_target_type", Type: "wkt_step_target"},
20: {Name: "secondary_target_value"},
21: {Name: "secondary_custom_target_value_low"},
22: {Name: "secondary_custom_target_value_high"},
20: {Name: "secondary_target_value", HasSubField: true},
21: {Name: "secondary_custom_target_value_low", HasSubField: true},
22: {Name: "secondary_custom_target_value_high", HasSubField: true},
},
28: {
0: {Name: "manufacturer", Type: "manufacturer"},
1: {Name: "product"},
1: {Name: "product", HasSubField: true},
2: {Name: "serial_number"},
3: {Name: "time_created", Type: "date_time"},
4: {Name: "completed"},
@ -688,7 +885,7 @@ var FieldDefMap = map[uint64]fieldDefMap{
0: {Name: "file", Type: "file"},
1: {Name: "mesg_num", Type: "mesg_num"},
2: {Name: "count_type", Type: "mesg_count"},
3: {Name: "count"},
3: {Name: "count", HasSubField: true},
},
39: {
254: {Name: "message_index", Type: "message_index"},
@ -724,7 +921,7 @@ var FieldDefMap = map[uint64]fieldDefMap{
0: {Name: "device_index", Type: "device_index"},
1: {Name: "calories", Unit: "kcal"},
2: {Name: "distance", Unit: "m", Scale: 100},
3: {Name: "cycles", Unit: "cycles", Scale: 2},
3: {Name: "cycles", Unit: "cycles", Scale: 2, HasSubField: true},
4: {Name: "active_time", Unit: "s", Scale: 1000},
5: {Name: "activity_type", Type: "activity_type"},
6: {Name: "activity_subtype", Type: "activity_subtype"},
@ -754,7 +951,7 @@ var FieldDefMap = map[uint64]fieldDefMap{
253: {Name: "timestamp", Type: "date_time"},
0: {Name: "type", Type: "file"},
1: {Name: "manufacturer", Type: "manufacturer"},
2: {Name: "product"},
2: {Name: "product", HasSubField: true},
3: {Name: "serial_number"},
4: {Name: "time_created", Type: "date_time"},
},
@ -818,7 +1015,7 @@ var FieldDefMap = map[uint64]fieldDefMap{
},
106: {
0: {Name: "manufacturer", Type: "manufacturer"},
1: {Name: "product"},
1: {Name: "product", HasSubField: true},
},
127: {
0: {Name: "bluetooth_enabled"},
@ -887,7 +1084,7 @@ var FieldDefMap = map[uint64]fieldDefMap{
7: {Name: "total_elapsed_time", Unit: "s", Scale: 1000},
8: {Name: "total_timer_time", Unit: "s", Scale: 1000},
9: {Name: "total_distance", Unit: "m", Scale: 100},
10: {Name: "total_cycles", Unit: "cycles"},
10: {Name: "total_cycles", Unit: "cycles", HasSubField: true},
11: {Name: "total_calories", Unit: "kcal"},
12: {Name: "total_fat_calories", Unit: "kcal"},
13: {Name: "avg_speed", Unit: "m/s", Scale: 1000},
@ -1031,7 +1228,7 @@ var FieldDefMap = map[uint64]fieldDefMap{
159: {
254: {Name: "message_index", Type: "message_index"},
0: {Name: "mode", Type: "watchface_mode"},
1: {Name: "layout"},
1: {Name: "layout", HasSubField: true},
},
160: {
253: {Name: "timestamp", Type: "date_time", Unit: "s"},
@ -1088,7 +1285,7 @@ var FieldDefMap = map[uint64]fieldDefMap{
167: {
253: {Name: "timestamp", Type: "date_time", Unit: "s"},
0: {Name: "sensor_type", Type: "sensor_type"},
1: {Name: "calibration_factor"},
1: {Name: "calibration_factor", HasSubField: true},
2: {Name: "calibration_divisor", Unit: "counts"},
3: {Name: "level_shift"},
4: {Name: "offset_cal"},
@ -1227,7 +1424,7 @@ var FieldDefMap = map[uint64]fieldDefMap{
210: {
253: {Name: "timestamp", Type: "date_time", Unit: "s"},
0: {Name: "sensor_type", Type: "sensor_type"},
1: {Name: "calibration_factor"},
1: {Name: "calibration_factor", HasSubField: true},
2: {Name: "calibration_divisor", Unit: "counts"},
3: {Name: "level_shift"},
4: {Name: "offset_cal"},
@ -1306,7 +1503,7 @@ var FieldDefMap = map[uint64]fieldDefMap{
17: {Name: "repeat_dive_interval", Unit: "s", Scale: 1},
18: {Name: "safety_stop_time", Unit: "s", Scale: 1},
19: {Name: "heart_rate_source_type", Type: "source_type"},
20: {Name: "heart_rate_source"},
20: {Name: "heart_rate_source", HasSubField: true},
21: {Name: "travel_gas", Type: "message_index"},
22: {Name: "ccr_low_setpoint_switch_mode", Type: "ccr_setpoint_switch_mode"},
23: {Name: "ccr_low_setpoint", Unit: "percent", Scale: 100},

View File

@ -34,7 +34,7 @@ var invalidFloat = map[string]float64{
"float64": 0xFFFFFFFFFFFFFFFF,
}
func GetUintFormatter(fDef FieldDef) scalar.UintFn {
func GetUintFormatter(fDef LocalFieldDef) scalar.UintFn {
return scalar.UintFn(func(s scalar.Uint) (scalar.Uint, error) {
if s.Actual == invalidUint[fDef.Type] {
s.Description = "Invalid"
@ -61,7 +61,7 @@ func GetUintFormatter(fDef FieldDef) scalar.UintFn {
})
}
func GetSintFormatter(fDef FieldDef) scalar.SintFn {
func GetSintFormatter(fDef LocalFieldDef) scalar.SintFn {
return scalar.SintFn(func(s scalar.Sint) (scalar.Sint, error) {
if s.Actual == invalidSint[fDef.Type] {
s.Description = "Invalid"
@ -87,7 +87,7 @@ func GetSintFormatter(fDef FieldDef) scalar.SintFn {
})
}
func GetFloatFormatter(fDef FieldDef) scalar.FltFn {
func GetFloatFormatter(fDef LocalFieldDef) scalar.FltFn {
return scalar.FltFn(func(s scalar.Flt) (scalar.Flt, error) {
if s.Actual == invalidFloat[fDef.Type] {
s.Description = "Invalid"

View File

@ -57,7 +57,7 @@ $ fq -d fit dv activity.fit
0x0020| d2 c6 56 3f | ..V? | time_created: 1062651602 0x2b-0x2f (4)
0x0020| 01| .| manufacturer: "garmin" (1) 0x2f-0x31 (2)
0x0030|00 |. |
0x0030| 63 08 | c. | product: 2147 0x31-0x33 (2)
0x0030| 63 08 | c. | garmin_product: "edge25" (2147) 0x31-0x33 (2)
0x0030| ff ff | .. | number: 65535 (Invalid) 0x33-0x35 (2)
0x0030| 04 | . | type: "activity" (4) 0x35-0x36 (1)
| | | [2]{}: data_record 0x36-0x42 (12)
@ -227,7 +227,7 @@ $ fq -d fit dv activity.fit
0x00a0| ff| .| UNKNOWN_16: 4294967295 (Invalid) 0xaf-0xb3 (4)
0x00b0|ff ff ff |... |
0x00b0| 01 00 | .. | manufacturer: "garmin" (1) 0xb3-0xb5 (2)
0x00b0| 63 08 | c. | product: 2147 0xb5-0xb7 (2)
0x00b0| 63 08 | c. | garmin_product: "edge25" (2147) 0xb5-0xb7 (2)
0x00b0| 4a 01 | J. | software_version: 3.3 (330) 0xb7-0xb9 (2)
0x00b0| ff ff | .. | battery_voltage: 65535 (Invalid) 0xb9-0xbb (2)
0x00b0| 00 | . | device_index: "creator" (0) 0xbb-0xbc (1)
@ -251,7 +251,7 @@ $ fq -d fit dv activity.fit
0x00d0| ff ff ff ff | .... | UNKNOWN_15: 4294967295 (Invalid) 0xd1-0xd5 (4)
0x00d0| ff ff ff ff | .... | UNKNOWN_16: 4294967295 (Invalid) 0xd5-0xd9 (4)
0x00d0| 01 00 | .. | manufacturer: "garmin" (1) 0xd9-0xdb (2)
0x00d0| 63 08 | c. | product: 2147 0xdb-0xdd (2)
0x00d0| 63 08 | c. | garmin_product: "edge25" (2147) 0xdb-0xdd (2)
0x00d0| 4a 01 | J. | software_version: 3.3 (330) 0xdd-0xdf (2)
0x00d0| ff| .| battery_voltage: 65535 (Invalid) 0xdf-0xe1 (2)
0x00e0|ff |. |
@ -19783,7 +19783,7 @@ $ fq -d fit dv activity.fit
0x68c0| ff ff ff ff | .... | UNKNOWN_15: 4294967295 (Invalid) 0x68c1-0x68c5 (4)
0x68c0| ff ff ff ff | .... | UNKNOWN_16: 4294967295 (Invalid) 0x68c5-0x68c9 (4)
0x68c0| 01 00 | .. | manufacturer: "garmin" (1) 0x68c9-0x68cb (2)
0x68c0| 63 08 | c. | product: 2147 0x68cb-0x68cd (2)
0x68c0| 63 08 | c. | garmin_product: "edge25" (2147) 0x68cb-0x68cd (2)
0x68c0| 4a 01 | J. | software_version: 3.3 (330) 0x68cd-0x68cf (2)
0x68c0| ff| .| battery_voltage: 65535 (Invalid) 0x68cf-0x68d1 (2)
0x68d0|ff |. |
@ -19809,7 +19809,7 @@ $ fq -d fit dv activity.fit
0x68e0| ff ff ff ff | .... | UNKNOWN_16: 4294967295 (Invalid) 0x68eb-0x68ef (4)
0x68e0| 01| .| manufacturer: "garmin" (1) 0x68ef-0x68f1 (2)
0x68f0|00 |. |
0x68f0| 63 08 | c. | product: 2147 0x68f1-0x68f3 (2)
0x68f0| 63 08 | c. | garmin_product: "edge25" (2147) 0x68f1-0x68f3 (2)
0x68f0| 4a 01 | J. | software_version: 3.3 (330) 0x68f3-0x68f5 (2)
0x68f0| ff ff | .. | battery_voltage: 65535 (Invalid) 0x68f5-0x68f7 (2)
0x68f0| 01 | . | device_index: 1 0x68f7-0x68f8 (1)
@ -36970,7 +36970,7 @@ $ fq -d fit dv activity.fit
0xc4c0| ff ff ff ff | .... | UNKNOWN_15: 4294967295 (Invalid) 0xc4c3-0xc4c7 (4)
0xc4c0| ff ff ff ff | .... | UNKNOWN_16: 4294967295 (Invalid) 0xc4c7-0xc4cb (4)
0xc4c0| 01 00 | .. | manufacturer: "garmin" (1) 0xc4cb-0xc4cd (2)
0xc4c0| 63 08 | c. | product: 2147 0xc4cd-0xc4cf (2)
0xc4c0| 63 08 | c. | garmin_product: "edge25" (2147) 0xc4cd-0xc4cf (2)
0xc4c0| 4a| J| software_version: 3.3 (330) 0xc4cf-0xc4d1 (2)
0xc4d0|01 |. |
0xc4d0| ff ff | .. | battery_voltage: 65535 (Invalid) 0xc4d1-0xc4d3 (2)
@ -36996,7 +36996,7 @@ $ fq -d fit dv activity.fit
0xc4e0| ff ff ff| ...| UNKNOWN_16: 4294967295 (Invalid) 0xc4ed-0xc4f1 (4)
0xc4f0|ff |. |
0xc4f0| 01 00 | .. | manufacturer: "garmin" (1) 0xc4f1-0xc4f3 (2)
0xc4f0| 63 08 | c. | product: 2147 0xc4f3-0xc4f5 (2)
0xc4f0| 63 08 | c. | garmin_product: "edge25" (2147) 0xc4f3-0xc4f5 (2)
0xc4f0| 4a 01 | J. | software_version: 3.3 (330) 0xc4f5-0xc4f7 (2)
0xc4f0| ff ff | .. | battery_voltage: 65535 (Invalid) 0xc4f7-0xc4f9 (2)
0xc4f0| 01 | . | device_index: 1 0xc4f9-0xc4fa (1)

View File

@ -62,7 +62,7 @@ $ fq -d fit dv activity_invalid_strings.fit
0x0030|20 40 | @ |
0x0030| ff ff ff ff | .... | UNKNOWN_7: 4294967295 (Invalid) 0x32-0x36 (4)
0x0030| 01 00 | .. | manufacturer: "garmin" (1) 0x36-0x38 (2)
0x0030| 1d 0f | .. | product: 3869 0x38-0x3a (2)
0x0030| 1d 0f | .. | garmin_product: "fr55" (3869) 0x38-0x3a (2)
0x0030| ff ff | .. | number: 65535 (Invalid) 0x3a-0x3c (2)
0x0030| 04 | . | type: "activity" (4) 0x3c-0x3d (1)
| | | [2]{}: data_record 0x3d-0x4c (15)
@ -468,7 +468,7 @@ $ fq -d fit dv activity_invalid_strings.fit
0x01a0| 00 00 00 00 | .... | UNKNOWN_24: 0 (Invalid) 0x1a6-0x1aa (4)
0x01a0| ff ff ff ff | .... | UNKNOWN_31: 4294967295 (Invalid) 0x1aa-0x1ae (4)
0x01a0| 01 00| ..| manufacturer: "garmin" (1) 0x1ae-0x1b0 (2)
0x01b0|1d 0f |.. | product: 3869 0x1b0-0x1b2 (2)
0x01b0|1d 0f |.. | garmin_product: "fr55" (3869) 0x1b0-0x1b2 (2)
0x01b0| 87 03 | .. | software_version: 9.03 (903) 0x1b2-0x1b4 (2)
0x01b0| ff ff | .. | battery_voltage: 65535 (Invalid) 0x1b4-0x1b6 (2)
0x01b0| ff ff | .. | UNKNOWN_13: 65535 (Invalid) 0x1b6-0x1b8 (2)
@ -648,7 +648,7 @@ $ fq -d fit dv activity_invalid_strings.fit
0x0320| 00 00 00 00 | .... | UNKNOWN_24: 0 (Invalid) 0x322-0x326 (4)
0x0320| ff ff ff ff | .... | UNKNOWN_31: 4294967295 (Invalid) 0x326-0x32a (4)
0x0320| 01 00 | .. | manufacturer: "garmin" (1) 0x32a-0x32c (2)
0x0320| 55 0f | U. | product: 3925 0x32c-0x32e (2)
0x0320| 55 0f | U. | garmin_product: 3925 0x32c-0x32e (2)
0x0320| 6d 09| m.| software_version: 24.13 (2413) 0x32e-0x330 (2)
0x0330|ff ff |.. | battery_voltage: 65535 (Invalid) 0x330-0x332 (2)
0x0330| ff ff | .. | UNKNOWN_13: 65535 (Invalid) 0x332-0x334 (2)
@ -40572,7 +40572,7 @@ $ fq -d fit dv activity_invalid_strings.fit
0x5630| 00 00 00 00 | .... | UNKNOWN_24: 0 (Invalid) 0x5631-0x5635 (4)
0x5630| ff ff ff ff | .... | UNKNOWN_31: 4294967295 (Invalid) 0x5635-0x5639 (4)
0x5630| 01 00 | .. | manufacturer: "garmin" (1) 0x5639-0x563b (2)
0x5630| 1d 0f | .. | product: 3869 0x563b-0x563d (2)
0x5630| 1d 0f | .. | garmin_product: "fr55" (3869) 0x563b-0x563d (2)
0x5630| 87 03 | .. | software_version: 9.03 (903) 0x563d-0x563f (2)
0x5630| ff| .| battery_voltage: 65535 (Invalid) 0x563f-0x5641 (2)
0x5640|ff |. |
@ -40751,7 +40751,7 @@ $ fq -d fit dv activity_invalid_strings.fit
0x57b0|00 |. |
0x57b0| ff ff ff ff | .... | UNKNOWN_31: 4294967295 (Invalid) 0x57b1-0x57b5 (4)
0x57b0| 01 00 | .. | manufacturer: "garmin" (1) 0x57b5-0x57b7 (2)
0x57b0| 55 0f | U. | product: 3925 0x57b7-0x57b9 (2)
0x57b0| 55 0f | U. | garmin_product: 3925 0x57b7-0x57b9 (2)
0x57b0| 6d 09 | m. | software_version: 24.13 (2413) 0x57b9-0x57bb (2)
0x57b0| ff ff | .. | battery_voltage: 65535 (Invalid) 0x57bb-0x57bd (2)
0x57b0| ff ff | .. | UNKNOWN_13: 65535 (Invalid) 0x57bd-0x57bf (2)
@ -42460,8 +42460,8 @@ $ fq -d fit dv activity_invalid_strings.fit
0x5e20| 01 | . | sub_sport: "treadmill" (1) 0x5e2e-0x5e2f (1)
0x5e20| 88| .| avg_heart_rate: 136 (bpm) 0x5e2f-0x5e30 (1)
0x5e30|8f |. | max_heart_rate: 143 (bpm) 0x5e30-0x5e31 (1)
0x5e30| 49 | I | avg_cadence: 73 (rpm) 0x5e31-0x5e32 (1)
0x5e30| 4e | N | max_cadence: 78 (rpm) 0x5e32-0x5e33 (1)
0x5e30| 49 | I | avg_running_cadence: 73 (strides/min) 0x5e31-0x5e32 (1)
0x5e30| 4e | N | max_running_cadence: 78 (strides/min) 0x5e32-0x5e33 (1)
0x5e30| ff | . | total_training_effect: 255 (Invalid) 0x5e33-0x5e34 (1)
0x5e30| ff | . | event_group: 255 (Invalid) 0x5e34-0x5e35 (1)
0x5e30| 00 | . | trigger: "activity_end" (0) 0x5e35-0x5e36 (1)

View File

@ -0,0 +1,3 @@
node_modules/
package-lock.json

View File

@ -18,9 +18,11 @@ const inTypes = workSheetsFromFile[0].data;
const inMessages = workSheetsFromFile[1].data;
let currentType = '';
let dynamicFieldName = '';
let currentFDefNo = null;
const outTypes = {};
const outMessages = {};
const outFormatters = {};
const outSubFields = {};
for (let li = 1; li < inTypes.length; li++) {
const row = inTypes[li];
@ -28,7 +30,6 @@ for (let li = 1; li < inTypes.length; li++) {
if (row[0]) {
currentType = row[0];
outTypes[currentType] = { type: row[1], fields: [] };
outFormatters[currentType] = { type: row[1] };
} else {
if (row[4] && row[4].indexOf("Deprecated") > -1) {
continue;
@ -40,13 +41,15 @@ for (let li = 1; li < inTypes.length; li++) {
}
for (let li = 1; li < inMessages.length; li++) {
const row = inMessages[li];
const refFields = {}
if (row[0]) {
currentType = row[0];
currentMsgNum = outTypes.mesg_num.fields.indexOf(currentType);
outMessages[currentMsgNum] = { msgNum: currentMsgNum, type: currentType, fields: {} };
outSubFields[currentMsgNum] = { msgNum: currentMsgNum, fields: {} }
} else {
if (row[1] == undefined) {
if (row[2] == undefined) {
continue;
}
const fDefNo = row[1];
@ -56,8 +59,24 @@ for (let li = 1; li < inMessages.length; li++) {
const offset = row[7];
const unit = row[8];
outMessages[currentMsgNum].fields[name] = { fDefNo, name, type, unit, scale, offset };
outFormatters[type] = { ...outFormatters[type], unit, scale, offset };
if (fDefNo != null) {
outMessages[currentMsgNum].fields[name] = { fDefNo, name, type, unit, scale, offset };
currentFDefNo = fDefNo
dynamicFieldName = name
} else {
const refField = row[11].split(",")[0]
const refVals = row[12].split(",")
if (!Object.hasOwnProperty.call(refFields, refField)) {
refFields[refField] = {}
}
refVals.forEach(element => {
refFields[refField][element] = { name, type, unit, scale, offset }
});
outMessages[currentMsgNum].fields[dynamicFieldName]["hasSub"] = true;
outSubFields[currentMsgNum].fields[currentFDefNo] = refFields
}
}
}
@ -67,8 +86,8 @@ if (command == "t") {
console.log("package mappers");
console.log("");
console.log("var TypeDefMap = map[string]typeDefMap{");
console.log("var TypeDefMap = map[string]typeDefMap{");
for (const key in outTypes) {
if (Object.hasOwnProperty.call(outTypes, key)) {
const element = outTypes[key];
@ -90,11 +109,75 @@ if (command == "t") {
if (command == "m") {
console.log("package mappers");
console.log("");
console.log("var FieldDefMap = map[uint64]fieldDefMap{");
const baseTypes = ["bool", "byte", "enum", "uint8", "uint8z", "sint8", "sint16", "uint16", "uint16z", "sint32",
"uint32", "uint32z", "float32", "float64", "sint64", "uint64", "uint64z", "string"];
console.log("var SubFieldDefMap = map[uint64]map[uint64]map[string]map[string]FieldDef{");
for (const key in outSubFields) {
if (Object.hasOwnProperty.call(outSubFields, key)) {
const element = outSubFields[key];
if (Object.keys(element.fields).length == 0) {
continue
}
console.log(`\t${key}: {`);
for (const fieldKey in element.fields) {
const field = element.fields[fieldKey];
console.log(`\t\t${fieldKey}: {`);
for (const refFieldKey in field) {
const refField = field[refFieldKey];
console.log(`\t\t\t"${refFieldKey}": {`);
for (const refValKey in refField) {
const subField = refField[refValKey];
if (subField) {
let type = "";
let unit = "";
let scale = "";
let offset = "";
if (baseTypes.indexOf(subField.type) == -1) {
type = `, Type: \"${subField.type}\"`;
}
if (subField.unit) {
unit = `, Unit: \"${subField.unit}\"`;
}
if (subField.scale) {
if (typeof (subField.scale) == "string") {
// ignore multi scale (for component fields) for now
const testScale = subField.scale.split(",");
if (testScale.length == 1) {
scale = `, Scale: ${testScale[0]}`;
}
} else {
scale = `, Scale: ${subField.scale}`;
}
}
if (subField.offset) {
offset = `, Offset: ${subField.offset}`
}
console.log(`\t\t\t\t"${refValKey}": {Name: \"${subField.name}\"${type}${unit}${scale}${offset}},`);
}
}
console.log(`\t\t\t},`);
}
console.log(`\t\t},`);
}
console.log(`\t},`);
}
}
console.log("}");
console.log("");
console.log("var FieldDefMap = map[uint64]fieldDefMap{");
for (const key in outMessages) {
if (Object.hasOwnProperty.call(outMessages, key)) {
const element = outMessages[key];
@ -108,6 +191,7 @@ if (command == "m") {
let unit = "";
let scale = "";
let offset = "";
let hasSub = "";
if (baseTypes.indexOf(field.type) == -1) {
type = `, Type: \"${field.type}\"`;
@ -118,14 +202,26 @@ if (command == "m") {
}
if (field.scale) {
scale = `, Scale: ${field.scale}`;
if (typeof (field.scale) == "string") {
// ignore multi scale (for component fields) for now
const testScale = field.scale.split(",");
if (testScale.length == 1) {
scale = `, Scale: ${testScale[0]}`;
}
} else {
scale = `, Scale: ${field.scale}`;
}
}
if (field.offset) {
offset = `, Offset: ${field.offset}`
}
console.log(`\t\t${field.fDefNo}: {Name: \"${field.name}\"${type}${unit}${scale}${offset}},`);
if (field.hasSub) {
hasSub = `, HasSubField: ${field.hasSub}`
}
console.log(`\t\t${field.fDefNo}: {Name: \"${field.name}\"${type}${unit}${scale}${offset}${hasSub}},`);
}
}