Skip to content

Commit

Permalink
fix: decoder on component expansion (#66)
Browse files Browse the repository at this point in the history
  • Loading branch information
muktihari authored Dec 26, 2023
1 parent 9774837 commit cca8f0b
Show file tree
Hide file tree
Showing 11 changed files with 340 additions and 221 deletions.
2 changes: 1 addition & 1 deletion cmd/fitconv/fitcsv/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ func (c *FitToCsvConv) writeMesg(mesg proto.Message) {
if c.options.unknownNumber && field.Name == factory.NameUnknown {
name = formatUnknown(int(field.Num))
}
if subField, ok := field.SubFieldSubtitution(&mesg); ok {
if subField := field.SubFieldSubtitution(&mesg); subField != nil {
name, units = subField.Name, subField.Units
}

Expand Down
12 changes: 6 additions & 6 deletions decoder/accumulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func NewAccumulator() *Accumulator {
return &Accumulator{} // No need to make AccumulatedValues as it will be created on append anyway.
}

func (a *Accumulator) Collect(mesgNum typedef.MesgNum, destFieldNum byte, value int64) {
func (a *Accumulator) Collect(mesgNum typedef.MesgNum, destFieldNum byte, value uint32) {
for i := range a.AccumulatedValues {
field := &a.AccumulatedValues[i]
if field.MesgNum == mesgNum && field.DestFieldNum == destFieldNum {
Expand All @@ -33,7 +33,7 @@ func (a *Accumulator) Collect(mesgNum typedef.MesgNum, destFieldNum byte, value
})
}

func (a *Accumulator) Accumulate(mesgNum typedef.MesgNum, destFieldNum byte, value int64, bits byte) int64 {
func (a *Accumulator) Accumulate(mesgNum typedef.MesgNum, destFieldNum byte, value uint32, bits byte) uint32 {
for i := range a.AccumulatedValues {
av := &a.AccumulatedValues[i]
if av.MesgNum == mesgNum && av.DestFieldNum == destFieldNum {
Expand All @@ -48,12 +48,12 @@ func (a *Accumulator) Reset() { a.AccumulatedValues = a.AccumulatedValues[:0] }
type AccumulatedValue struct {
MesgNum typedef.MesgNum
DestFieldNum byte
Last int64
Value int64
Last uint32
Value uint32
}

func (a *AccumulatedValue) Accumulate(value int64, bits byte) int64 {
var mask int64 = (1 << bits) - 1
func (a *AccumulatedValue) Accumulate(value uint32, bits byte) uint32 {
var mask uint32 = (1 << bits) - 1
a.Value += (value - a.Last) & mask
a.Last = value
return a.Value
Expand Down
4 changes: 2 additions & 2 deletions decoder/accumulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ func TestCollect(t *testing.T) {
type value struct {
mesgNum typedef.MesgNum
destFieldNum byte
value int64
expected int64
value uint32
expected uint32
}

tt := []struct {
Expand Down
78 changes: 78 additions & 0 deletions decoder/bits.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package decoder

import (
"github.com/muktihari/fit/profile/basetype"
)

const (
bit = 8
maxBit = 32
size = maxBit / bit
)

// bitsFromValue convert value into 32-bits unsigned integer.
//
// Profile.xlsx (on Bits header's comment) says: Current implementation only supports Bits value of max 32.
func bitsFromValue(value any) (bits uint32, ok bool) {
switch val := value.(type) {
case int8:
return uint32(val), true
case uint8:
return uint32(val), true
case int16:
return uint32(val), true
case uint16:
return uint32(val), true
case int32:
return uint32(val), true
case uint32:
return uint32(val), true
case int64:
return uint32(val), true
case uint64:
return uint32(val), true
case float32:
return uint32(val), true
case float64:
return uint32(val), true
case []byte:
if len(val) > size {
return 0, false
}
for i := range val {
if val[i] == basetype.ByteInvalid { // all values must be valid
return 0, false
}
bits |= uint32(val[i]) << (i * bit) // little-endian
}
return bits, true
}
return 0, false
}

// valueFromBits cast back bits into it's original value.
func valueFromBits(bits uint32, baseType basetype.BaseType) any {
switch baseType {
case basetype.Sint8:
return int8(bits)
case basetype.Uint8, basetype.Uint8z:
return uint8(bits)
case basetype.Sint16:
return int16(bits)
case basetype.Uint16, basetype.Uint16z:
return uint16(bits)
case basetype.Sint32:
return int32(bits)
case basetype.Uint32, basetype.Uint32z:
return uint32(bits)
case basetype.Float32:
return float32(bits)
case basetype.Float64:
return float64(bits)
case basetype.Sint64:
return int64(bits)
case basetype.Uint64, basetype.Uint64z:
return uint64(bits)
}
return baseType.Invalid()
}
73 changes: 73 additions & 0 deletions decoder/bits_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package decoder

import (
"fmt"
"testing"

"github.com/muktihari/fit/profile/basetype"
)

func TestBitsFromValue(t *testing.T) {
tt := []struct {
value any
expected uint32
ok bool
}{
{value: int8(10), expected: 10, ok: true},
{value: uint8(10), expected: 10, ok: true},
{value: int16(10), expected: 10, ok: true},
{value: uint16(10), expected: 10, ok: true},
{value: int32(10), expected: 10, ok: true},
{value: uint32(10), expected: 10, ok: true},
{value: int64(10), expected: 10, ok: true},
{value: uint64(10), expected: 10, ok: true},
{value: float32(10), expected: 10, ok: true},
{value: float64(10), expected: 10, ok: true},
{value: []byte{1, 1, 1}, expected: 1<<0 | 1<<8 | 1<<16, ok: true},
{value: []byte{1, 255, 1}, expected: 0, ok: false},
{value: make([]byte, 33), expected: 0, ok: false},
{value: "string value", expected: 0, ok: false},
}

for _, tc := range tt {
t.Run(fmt.Sprintf("%v (%T)", tc.value, tc.value), func(t *testing.T) {
res, ok := bitsFromValue(tc.value)
if ok != tc.ok {
t.Fatalf("expected ok: %t, got: %t", tc.ok, ok)
}
if res != tc.expected {
t.Fatalf("expected: %d, got: %d", tc.expected, res)
}
})
}
}

func TestValueFromBits(t *testing.T) {
tt := []struct {
sbits uint32
basetype basetype.BaseType
value any
ok bool
}{
{sbits: 10, basetype: basetype.Sint8, value: int8(10), ok: true},
{sbits: 10, basetype: basetype.Uint8, value: uint8(10), ok: true},
{sbits: 10, basetype: basetype.Sint16, value: int16(10), ok: true},
{sbits: 10, basetype: basetype.Uint16, value: uint16(10), ok: true},
{sbits: 10, basetype: basetype.Sint32, value: int32(10), ok: true},
{sbits: 10, basetype: basetype.Uint32, value: uint32(10), ok: true},
{sbits: 10, basetype: basetype.Sint64, value: int64(10), ok: true},
{sbits: 10, basetype: basetype.Uint64, value: uint64(10), ok: true},
{sbits: 10, basetype: basetype.Float32, value: float32(10), ok: true},
{sbits: 10, basetype: basetype.Float64, value: float64(10), ok: true},
{sbits: 10, basetype: basetype.String, value: basetype.StringInvalid, ok: false},
}

for _, tc := range tt {
t.Run(fmt.Sprintf("%s %v (%T)", tc.basetype, tc.value, tc.value), func(t *testing.T) {
res := valueFromBits(tc.sbits, tc.basetype)
if res != tc.value {
t.Fatalf("expected: %v, got: %v", tc.value, res)
}
})
}
}
Loading

0 comments on commit cca8f0b

Please sign in to comment.