Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: clean up code, fix typo and update docs #299

Merged
merged 7 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Contributing

Hi, thank you for showing interest in this project. I would be grateful to receive feedback and any form of help.
I hope this project can benefit fitness comunity and Go developers in particular.
I hope this project can benefit fitness community and Go developers in particular.

This project is still in the early stages, and there may be changes to some of the APIs as it progresses.
We need more people with different use cases for the FIT SDK for Go to improve it further.
We need more people with different use cases to further improve this project.

If you have trivial fix or improvement, go ahead and create a [pull request][prs] and mention me to review the changes.

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ This project hosts the Go implementation for [The Flexible and Interoperable Dat

The FIT protocol, known for its compact size as a binary file format, is the preferred choice for manufacturers to use in their embedded devices. However, despite its widespread adoption, Garmin has not yet released an official SDK for Go, and existing third-party libraries for decoding and encoding the FIT protocol lack the semantics of the official SDK.

One of the key semantics they are missing is the ability for users to retrieve raw protocol messages; instead, they decode directly into predefined message structs grouped by [common file types](https://developer.garmin.com/fit/file-types). Furthermore, existing third-party libraries do not seem to fully support FIT Protocol V2, and their ability to produce variant options of the FIT protocol is limited. For instance, creating FIT files with compressed timestamps or FIT files with multiple local message types, which significantly reduces the resulting FIT files' size, is missing.
One of the key semantics they are missing is the ability for users to retrieve raw protocol messages; instead, they decode directly into predefined message structs grouped by [common file types](https://developer.garmin.com/fit/file-types) which makes users unable to extend some functionalities. Furthermore, existing third-party libraries do not seem to fully support FIT Protocol V2, and their ability to produce variant options of the FIT protocol is limited. For instance, creating FIT files with compressed timestamps or FIT files with multiple local message types, which significantly reduces the resulting FIT files' size, is missing.

Without diminishing respect for the existing libraries created nearly a decade ago, at a time when the capabilities of Go were limited, we believe a new approach is necessary. This is where this SDK comes in, bridging the gap and enabling Go developers to seamlessly interact with the FIT protocol.

Expand Down
26 changes: 25 additions & 1 deletion cmd/fitconv/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Note:
- Currently, only conversions between FIT and CSV formats are supported. Other formats may be added in the future as needed or upon request.
- When converting from CSV to FIT, any unknown messages and fields are omitted due to the inability to ascertain their correct value types without additional context.

## Usage Examples
## Usage

```sh
go run main.go activity.fit activity2.csv
Expand All @@ -22,6 +22,30 @@ ls
# activity.fit activity.csv activity2.fit activity2.csv
```

### Build or Install

#### Build

```sh
go build -o fitconv main.go
```

#### Install

```sh
go install .
```

#### Run the resulting Binary

```sh
fitconv activity.fit activity2.csv

# Output:
# 📄 "activity.fit" -> "activity.csv"
# 🚀 "activity2.csv" -> "activity2.fit". [Info: 2 unknown messages are skipped]
```

### Options

| Options | Valid For | Description |
Expand Down
12 changes: 6 additions & 6 deletions cmd/fitprint/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,35 +50,35 @@ File Header:
- DataType: ".FIT"
- CRC: 53438

file_id (num: 0, fields[-]: 4, developerFields[+]: 0) [0]:
file_id (num: 0, arch: 1, fields[-]: 4, developerFields[+]: 0) [0]:
- manufacturer (num: 1, type: uint16 | manufacturer): 15 <dynastream>
- type (num: 0, type: enum | file): 4 <activity>
- garmin_product (num: 2, type: uint16 | garmin_product): 9001 <unknown(9001)> <<DynamicField: "product" (type: uint16)>>
- serial_number (num: 3, type: uint32z): 1701
developer_data_id (num: 207, fields[-]: 2, developerFields[+]: 0) [1]:
developer_data_id (num: 207, arch: 1, fields[-]: 2, developerFields[+]: 0) [1]:
- application_id (num: 1, type: byte array): [1 1 2 3 5 8 13 21 34 55 89 144 233 121 98 219]
- developer_data_index (num: 3, type: uint8): 0
field_description (num: 206, fields[-]: 5, developerFields[+]: 0) [2]:
field_description (num: 206, arch: 1, fields[-]: 5, developerFields[+]: 0) [2]:
- developer_data_index (num: 0, type: uint8): 0
- field_definition_number (num: 1, type: uint8): 0
- fit_base_type_id (num: 2, type: uint8 | fit_base_type): 1 <sint8>
- field_name (num: 3, type: string array): ["doughnuts_earned"]
- units (num: 8, type: string array): ["doughnuts"]
record (num: 20, fields[-]: 5, developerFields[+]: 1) [3]:
record (num: 20, arch: 1, fields[-]: 5, developerFields[+]: 1) [3]:
- heart_rate (num: 3, type: uint8): 140 bpm
- cadence (num: 4, type: uint8): 88 rpm
- distance (num: 5, type: uint32): 510 m ((51000 / 100) - 0)
- speed (num: 6, type: uint16): 47.488 m/s ((47488 / 1000) - 0)
- enhanced_speed (num: 73, type: uint32): 47.488 m/s ((47488 / 1000) - 0) <<ExpandedField>>
+ doughnuts_earned (num: 0, type: sint8): 1 doughnuts
record (num: 20, fields[-]: 5, developerFields[+]: 1) [4]:
record (num: 20, arch: 1, fields[-]: 5, developerFields[+]: 1) [4]:
- heart_rate (num: 3, type: uint8): 143 bpm
- cadence (num: 4, type: uint8): 90 rpm
- distance (num: 5, type: uint32): 2080 m ((208000 / 100) - 0)
- speed (num: 6, type: uint16): 36.416 m/s ((36416 / 1000) - 0)
- enhanced_speed (num: 73, type: uint32): 36.416 m/s ((36416 / 1000) - 0) <<ExpandedField>>
+ doughnuts_earned (num: 0, type: sint8): 2 doughnuts
record (num: 20, fields[-]: 5, developerFields[+]: 1) [5]:
record (num: 20, arch: 1, fields[-]: 5, developerFields[+]: 1) [5]:
- heart_rate (num: 3, type: uint8): 144 bpm
- cadence (num: 4, type: uint8): 92 rpm
- distance (num: 5, type: uint32): 3710 m ((371000 / 100) - 0)
Expand Down
10 changes: 6 additions & 4 deletions decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,10 @@ func WithNoComponentExpansion() Option {
// WithLogWriter specifies where the log messages will be written to. By default, the Decoder do not write any log if
// log writer is not specified. The Decoder will only write log messages when it encountered a bad encoded FIT file such as:
// - Field Definition's Size (or Developer Field Definition's Size) is zero.
// - Field Definition's Size (or Developer Field Definition's Size) is less than basetype's size.
// - Field Definition's Size (or Developer Field Definition's Size) is less than basetype's Size.
// e.g. Size 1 bytes but having basetype uint32 (4 bytes).
// - Encountering a Developer Field without prior Field Description Message.
// - Field Definition's Size is more than basetype's Size but field.Array is false.
// - Encountering a Developer Field without prior DeveloperDataId or FieldDescription Message.
func WithLogWriter(w io.Writer) Option {
return fnApply(func(o *options) { o.logWriter = w })
}
Expand All @@ -193,8 +194,8 @@ func WithReadBufferSize(size int) Option {
// fit, err := dec.Decode()
// }
//
// Note: Decoder already implements efficient io.Reader buffering, so there's no need to wrap 'r' using *bufio.Reader
// for optimal performance.
// Note: Decoder already implements efficient io.Reader buffering, so there's no need to wrap 'r' using *bufio.Reader;
// doing so will only reduce performance.
func New(r io.Reader, opts ...Option) *Decoder {
d := &Decoder{
readBuffer: new(readBuffer),
Expand Down Expand Up @@ -833,6 +834,7 @@ func (d *Decoder) decodeDeveloperFields(mesgDef *proto.MessageDefinition, mesg *
continue
}
developerDataId = devDataId
break
}

if developerDataId == nil {
Expand Down
7 changes: 7 additions & 0 deletions encoder/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,14 @@ func NewMessageValidator(opts ...ValidatorOption) MessageValidator {
}

func (v *messageValidator) Reset() {
for i := range v.developerDataIds {
v.developerDataIds[i] = nil // avoid memory leaks
}
v.developerDataIds = v.developerDataIds[:0]

for i := range v.fieldDescriptions {
v.fieldDescriptions[i] = nil // avoid memory leaks
}
v.fieldDescriptions = v.fieldDescriptions[:0]
}

Expand Down
24 changes: 24 additions & 0 deletions encoder/validator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,30 @@ func TestMessageValidatorOption(t *testing.T) {
}
}

func TestValidatorReset(t *testing.T) {
mv := NewMessageValidator().(*messageValidator)

mv.developerDataIds = append(mv.developerDataIds, mesgdef.NewDeveloperDataId(nil))
mv.fieldDescriptions = append(mv.fieldDescriptions, mesgdef.NewFieldDescription(nil))

mv.Reset()

mv.developerDataIds = mv.developerDataIds[:cap(mv.developerDataIds)]
mv.fieldDescriptions = mv.fieldDescriptions[:cap(mv.developerDataIds)]

for i, d := range mv.developerDataIds {
if d != nil {
t.Errorf("developerDataIds[%d]: expected nil: got: %p", i, d)
}
}

for i, f := range mv.fieldDescriptions {
if f != nil {
t.Errorf("fieldDescriptions[%d]: expected nil: got: %p", i, f)
}
}
}

func TestMessageValidatorValidate(t *testing.T) {
tt := []struct {
name string
Expand Down
4 changes: 2 additions & 2 deletions proto/value_unmarshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,8 +230,8 @@ func UnmarshalValue(b []byte, arch byte, baseType basetype.BaseType, profileType
}

// Note: The size may be a multiple of the underlying FIT Base Type size indicating the field contains multiple elements represented as an array.
func size(lenbytes int, typesize byte) byte {
return byte(lenbytes % int(typesize))
func size(lenbytes, typesize int) byte {
return byte(lenbytes % typesize)
}

// trimUTF8NullTerminatedString trims all utf8 null-terminated string including the paddings.
Expand Down