Skip to content

Commit

Permalink
refactor!: mesgdef update, fix & other improvements (#51)
Browse files Browse the repository at this point in the history
* refactor: mesgdef now use time.Time and take pointer to mesg as parameter

* generate: mesgdef

* update: files affected by refactor

* chore: update constants and factory on registration

* generate: factory and typedef
  • Loading branch information
muktihari committed Dec 19, 2023
1 parent 770e087 commit dcd7496
Show file tree
Hide file tree
Showing 132 changed files with 45,816 additions and 1,984 deletions.
13 changes: 7 additions & 6 deletions cmd/fitactivity/combiner/combiner.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package combiner
import (
"errors"
"fmt"
"time"

"github.com/muktihari/fit/cmd/fitactivity/finder"
"github.com/muktihari/fit/factory"
Expand Down Expand Up @@ -53,7 +54,7 @@ func Combine(fits ...proto.Fit) (*proto.Fit, error) {
return nil, fmt.Errorf("could not find last session index, fit index: %d: %w", i-1, ErrNoSessionFound)
}
sessionMesg := fits[i-1].Messages[sessionInfo.SessionIndex]
session := mesgdef.NewSession(sessionMesg)
session := mesgdef.NewSession(&sessionMesg)

if i-1 == 0 {
// remove target session from result, session will be added later depend on sequence order.
Expand All @@ -64,7 +65,7 @@ func Combine(fits ...proto.Fit) (*proto.Fit, error) {
if nextSessionInfo.SessionIndex == -1 {
return nil, fmt.Errorf("could not find next first session index, fit index: %d: %w", i, ErrNoSessionFound)
}
nextSession := mesgdef.NewSession(fits[i].Messages[nextSessionInfo.SessionIndex])
nextSession := mesgdef.NewSession(&fits[i].Messages[nextSessionInfo.SessionIndex])

if session.Sport != nextSession.Sport {
return nil, fmt.Errorf("last session's sport: %s, next first session's sport %s, fit index: %d: %w",
Expand Down Expand Up @@ -134,10 +135,10 @@ func Combine(fits ...proto.Fit) (*proto.Fit, error) {

// combineSession combines s2 into s1.
func combineSession(s1, s2 *mesgdef.Session) {
s1EndTime := uint32(s1.StartTime) + (s1.TotalElapsedTime / 1000)
elapsedTimeGap := uint32(s2.StartTime) - s1EndTime
if elapsedTimeGap <= uint32(s2.StartTime) { // only if not overflow
s1.TotalElapsedTime += elapsedTimeGap
s1EndTime := s1.StartTime.Add(time.Duration(s1.TotalElapsedTime/1000) * time.Second)
elapsedTimeGap := s2.StartTime.Sub(s1EndTime)
if elapsedTimeGap >= 0 {
s1.TotalElapsedTime += uint32(elapsedTimeGap.Seconds())
}

s1.TotalElapsedTime += s2.TotalElapsedTime
Expand Down
32 changes: 30 additions & 2 deletions cmd/fitconv/fitcsv/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,19 @@ func (c *FitToCsvConv) devFieldName(devFieldDef *proto.DeveloperFieldDefinition)
if fieldName == nil {
break
}
return typeconv.ToString[string](fieldName.Value)

strbuf := new(strings.Builder)
if vals, ok := sliceAny(fieldName.Value); ok {
for i := range vals {
strbuf.WriteString(format(vals[i]))
if i < len(vals)-1 {
strbuf.WriteByte('|')
}
}
return strbuf.String()
} else {
return format(fieldName.Value)
}
}
}

Expand Down Expand Up @@ -354,7 +366,18 @@ func (c *FitToCsvConv) writeMesg(mesg proto.Message) {

c.buf.WriteString(devField.Name)
c.buf.WriteByte(',')
c.buf.WriteString(format(devField.Value))

if vals, ok := sliceAny(devField.Value); ok { // array
for i := range vals {
c.buf.WriteString(format(vals[i]))
if i < len(vals)-1 {
c.buf.WriteByte('|')
}
}
} else {
c.buf.WriteString(format(devField.Value))
}

c.buf.WriteByte(',')
c.buf.WriteString(devField.Units)
c.buf.WriteByte(',')
Expand Down Expand Up @@ -428,6 +451,11 @@ func sliceAny(val any) (vals []any, isSlice bool) {
vals = append(vals, vs[i])
}
return
case []string:
for i := range vs {
vals = append(vals, vs[i])
}
return
}

return nil, false
Expand Down
45 changes: 28 additions & 17 deletions decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"errors"
"fmt"
"io"
"strings"
"sync"

"github.com/muktihari/fit/factory"
Expand Down Expand Up @@ -346,13 +347,12 @@ func (d *Decoder) decodeHeader() error {
d.fileHeader.CRC = binary.LittleEndian.Uint16(b[11:13])
}

if d.fileHeader.CRC == 0x0000 { // do not need to check header's crc integrity.
if d.fileHeader.CRC == 0x0000 || !d.options.shouldChecksum { // do not need to check header's crc integrity.
return nil
}

_, _ = d.crc16.Write(append([]byte{size}, b[:11]...))

if d.options.shouldChecksum && d.crc16.Sum16() != d.fileHeader.CRC { // check header integrity
if d.crc16.Sum16() != d.fileHeader.CRC { // check header integrity
return ErrCRCChecksumMismatch
}

Expand Down Expand Up @@ -481,17 +481,17 @@ func (d *Decoder) decodeMessageData(header byte) error {

// FileId Message
if d.fileId == nil && mesg.Num == mesgnum.FileId {
d.fileId = mesgdef.NewFileId(mesg)
d.fileId = mesgdef.NewFileId(&mesg)
}

// Prerequisites for decoding developer fields
switch mesg.Num {
case mesgnum.DeveloperDataId:
// These messages must occur before any related field description messages are written to the proto.
d.developerDataIds = append(d.developerDataIds, mesgdef.NewDeveloperDataId(mesg))
d.developerDataIds = append(d.developerDataIds, mesgdef.NewDeveloperDataId(&mesg))
case mesgnum.FieldDescription:
// These messages must occur in the file before any related developer data is written to the proto.
d.fieldDescriptions = append(d.fieldDescriptions, mesgdef.NewFieldDescription(mesg))
d.fieldDescriptions = append(d.fieldDescriptions, mesgdef.NewFieldDescription(&mesg))
}

if len(mesgDef.DeveloperFieldDefinitions) != 0 {
Expand Down Expand Up @@ -627,7 +627,11 @@ func (d *Decoder) decodeDeveloperFields(mesgDef *proto.MessageDefinition, mesg *
}

if fieldDescription == nil {
continue // Can't interpret this DeveloperField, no FieldDescription found.
// Can't interpret this DeveloperField, no FieldDescription found. Just read acquired bytes and move forward.
if err := d.read(make([]byte, devFieldDef.Size)); err != nil {
return fmt.Errorf("no field description found, unable to read acquired bytes: %w", err)
}
continue
}

developerField := proto.DeveloperField{
Expand All @@ -639,14 +643,19 @@ func (d *Decoder) decodeDeveloperFields(mesgDef *proto.MessageDefinition, mesg *
Type: fieldDescription.FitBaseTypeId,
}

if len(fieldDescription.FieldName) > 0 {
developerField.Name = fieldDescription.FieldName[0]
}

if len(fieldDescription.Units) > 0 {
developerField.Units = fieldDescription.Units[0]
}

developerField.Name = strings.Join(fieldDescription.FieldName, "|")
developerField.Units = strings.Join(fieldDescription.Units, "|")

// TODO: We still don't know how []string should be handled in the developer field.
// For the Field, we have "Array" (bool) for determining if the value is an array.
// However, we could not find any reference on how to use the DeveloperField's Array (uint8).
//
// For example:
// - "suuntoplus_plugin_owner_id" is []string, 1 - 10 strings, 1 - 64 characters each. (ref: https://apizone.suunto.com/fit-description).
// but still it does not specify DeveloperField's Array.
//
// Until we discover a better implementation, let's determine it by using multiplication of the size.
// Consequently, all strings will be treated as arrays since its size is 1.
var isArray bool
if devFieldDef.Size > developerField.Type.Size() && devFieldDef.Size%developerField.Type.Size() == 0 {
isArray = true
Expand Down Expand Up @@ -682,8 +691,10 @@ func (d *Decoder) read(b []byte) error {
if err != nil {
return err
}
_, _ = d.crc16.Write(b)
return err
if d.options.shouldChecksum {
_, _ = d.crc16.Write(b)
}
return nil
}

// readByte is shorthand for read([1]byte).
Expand Down
50 changes: 39 additions & 11 deletions decoder/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (

"github.com/google/go-cmp/cmp"
"github.com/muktihari/fit/factory"
"github.com/muktihari/fit/kit"
"github.com/muktihari/fit/kit/datetime"
"github.com/muktihari/fit/kit/hash/crc16"
"github.com/muktihari/fit/listener"
Expand Down Expand Up @@ -239,7 +240,7 @@ func TestPeekFileId(t *testing.T) {
return len(b), nil
})
}(),
fileId: mesgdef.NewFileId(fit.Messages[0]),
fileId: mesgdef.NewFileId(&fit.Messages[0]),
},
{
name: "peek file id decode header return error",
Expand All @@ -248,7 +249,7 @@ func TestPeekFileId(t *testing.T) {
return 0, io.EOF
})
}(),
fileId: mesgdef.NewFileId(fit.Messages[0]),
fileId: mesgdef.NewFileId(&fit.Messages[0]),
err: io.EOF,
},
{
Expand All @@ -263,7 +264,7 @@ func TestPeekFileId(t *testing.T) {
return len(b), nil
})
}(),
fileId: mesgdef.NewFileId(fit.Messages[0]),
fileId: mesgdef.NewFileId(&fit.Messages[0]),
err: io.EOF,
},
}
Expand Down Expand Up @@ -1215,14 +1216,14 @@ func TestDecodeDeveloperFields(t *testing.T) {
name: "decode developer fields happy flow",
r: fnReaderOK,
fieldDescription: mesgdef.NewFieldDescription(
factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields(
kit.Ptr(factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields(
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)),
),
)),
),
mesgDef: &proto.MessageDefinition{
Header: proto.MesgDefinitionMask,
Expand All @@ -1241,14 +1242,14 @@ func TestDecodeDeveloperFields(t *testing.T) {
name: "decode developer fields missing developer data index 1",
r: fnReaderOK,
fieldDescription: mesgdef.NewFieldDescription(
factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields(
kit.Ptr(factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields(
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)),
),
)),
),
mesgDef: &proto.MessageDefinition{
Header: proto.MesgDefinitionMask,
Expand All @@ -1267,14 +1268,40 @@ func TestDecodeDeveloperFields(t *testing.T) {
name: "decode developer fields missing field description number",
r: fnReaderOK,
fieldDescription: mesgdef.NewFieldDescription(
factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields(
kit.Ptr(factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields(
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)),
),
)),
),
mesgDef: &proto.MessageDefinition{
Header: proto.MesgDefinitionMask,
MesgNum: mesgnum.Record,
DeveloperFieldDefinitions: []proto.DeveloperFieldDefinition{
{
Num: 1,
DeveloperDataIndex: 0,
Size: 1,
},
},
},
mesg: &proto.Message{},
},
{
name: "decode developer fields missing field description number but unable to read acquired bytes",
r: fnReaderErr,
fieldDescription: mesgdef.NewFieldDescription(
kit.Ptr(factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields(
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)),
)),
),
mesgDef: &proto.MessageDefinition{
Header: proto.MesgDefinitionMask,
Expand All @@ -1288,19 +1315,20 @@ func TestDecodeDeveloperFields(t *testing.T) {
},
},
mesg: &proto.Message{},
err: io.EOF,
},
{
name: "decode developer fields got io.EOF",
r: fnReaderErr,
fieldDescription: mesgdef.NewFieldDescription(
factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields(
kit.Ptr(factory.CreateMesgOnly(mesgnum.FieldDescription).WithFields(
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionDeveloperDataIndex).WithValue(uint8(0)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldDefinitionNumber).WithValue(uint8(0)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFieldName).WithValue("Heart Rate"),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeMesgNum).WithValue(uint16(mesgnum.Record)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionNativeFieldNum).WithValue(uint8(fieldnum.RecordHeartRate)),
factory.CreateField(mesgnum.FieldDescription, fieldnum.FieldDescriptionFitBaseTypeId).WithValue(uint8(basetype.Uint8)),
),
)),
),
mesgDef: &proto.MessageDefinition{
Header: proto.MesgDefinitionMask,
Expand Down
4 changes: 2 additions & 2 deletions encoder/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,9 @@ func (v *messageValidator) Validate(mesg *proto.Message) error {

switch mesg.Num {
case mesgnum.DeveloperDataId:
v.developerDataIds = append(v.developerDataIds, mesgdef.NewDeveloperDataId(*mesg))
v.developerDataIds = append(v.developerDataIds, mesgdef.NewDeveloperDataId(mesg))
case mesgnum.FieldDescription:
v.fieldDescriptions = append(v.fieldDescriptions, mesgdef.NewFieldDescription(*mesg))
v.fieldDescriptions = append(v.fieldDescriptions, mesgdef.NewFieldDescription(mesg))
}

if len(mesg.DeveloperFields) == 0 {
Expand Down
5 changes: 3 additions & 2 deletions factory/exported_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit dcd7496

Please sign in to comment.