Skip to content

Commit

Permalink
Implemented uplink decoders for tag S / L. 👑
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbeutler committed Jul 26, 2024
1 parent c16cde6 commit c49b7f7
Show file tree
Hide file tree
Showing 17 changed files with 479 additions and 184 deletions.
29 changes: 29 additions & 0 deletions examples/basic_tag_s_l/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package main

import (
"encoding/json"
"log"

"github.com/truvami/decoder/pkg/decoder/tagsl/v1"
)

func main() {
log.Println("initializing tag S / L decoder...")
d := tagsl.NewTagSLv1Decoder()

// decode data
log.Println("decoding data...")
data, err := d.Decode("8002cdcd1300744f5e166018040b14341a", 1, "")
if err != nil {
panic(err)
}

// data to json
j, err := json.Marshal(data)
if err != nil {
panic(err)
}

// print json
log.Printf("result: %s\n", j)
}
23 changes: 23 additions & 0 deletions examples/basic_tag_s_l/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package main

import (
"bytes"
"log"
"strings"
"testing"
)

func TestMain(t *testing.T) {
// Create a buffer to capture the output
var buf bytes.Buffer
log.SetOutput(&buf)

// Run the main function
main()

// Check if the expected output is present in the buffer
expectedOutput := `47.041811`
if !strings.Contains(buf.String(), expectedOutput) {
t.Errorf("expected output %q not found", expectedOutput)
}
}
1 change: 0 additions & 1 deletion pkg/decoder/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,5 @@ type PayloadConfig struct {
}

type Decoder interface {
GetConfig(int16) (PayloadConfig, error)
Decode(string, int16, string) (interface{}, error)
}
8 changes: 7 additions & 1 deletion pkg/decoder/helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func convertFieldToType(value interface{}, fieldType reflect.Kind) interface{} {
case reflect.Float64:
return float64(value.(int))
case reflect.String:
return value.(string)
return fmt.Sprintf("%v", value)
case reflect.Bool:
return value.(int)&0x01 == 1
default:
Expand Down Expand Up @@ -109,6 +109,12 @@ func Parse(payloadHex string, config decoder.PayloadConfig) (interface{}, error)
if value == nil && optional {
continue
}

// log.Printf("field: %v", field.Name)
// log.Printf("value: %v", value)
// log.Printf("got: %T", value)
// log.Printf("expect: %v", fieldValue.Type().Kind())

fieldType := convertFieldToType(value, fieldValue.Type().Kind())
fieldValue.Set(reflect.ValueOf(fieldType))
}
Expand Down
40 changes: 36 additions & 4 deletions pkg/decoder/helpers/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func TestHexStringToBytes(t *testing.T) {
}
}

type GNSSPayload struct {
type Port1Payload struct {
Moving bool `json:"moving"`
Lat float64 `json:"gps_lat"`
Lon float64 `json:"gps_lon"`
Expand Down Expand Up @@ -54,7 +54,7 @@ func TestParse(t *testing.T) {
{Name: "Minute", Start: 15, Length: 1},
{Name: "Second", Start: 16, Length: 1},
},
TargetType: reflect.TypeOf(GNSSPayload{}),
TargetType: reflect.TypeOf(Port1Payload{}),
}

tests := []struct {
Expand All @@ -65,7 +65,7 @@ func TestParse(t *testing.T) {
{
payload: "8002cdcd1300744f5e166018040b14341a",
config: config,
expected: GNSSPayload{
expected: Port1Payload{
Moving: false,
Lat: 47.041811,
Lon: 7.622494,
Expand All @@ -88,7 +88,7 @@ func TestParse(t *testing.T) {
}

// Type assert to Payload
payload := decodedData.(GNSSPayload)
payload := decodedData.(Port1Payload)

// Check the decoded data against the expected data using reflect.DeepEqual
if !reflect.DeepEqual(payload, test.expected) {
Expand Down Expand Up @@ -179,3 +179,35 @@ func TestConvertFieldToType(t *testing.T) {
})
}
}

func TestInvalidPayload(t *testing.T) {
_, err := Parse("", decoder.PayloadConfig{
Fields: []decoder.FieldConfig{
{Name: "Moving", Start: 0, Length: 1},
},
TargetType: reflect.TypeOf(Port1Payload{}),
})
if err == nil {
t.Fatal("expected field out of bounds")
}

_, err = Parse("01", decoder.PayloadConfig{
Fields: []decoder.FieldConfig{
{Name: "Moving", Start: 0, Length: 2},
},
TargetType: reflect.TypeOf(Port1Payload{}),
})
if err == nil {
t.Fatal("expected field out of bounds")
}

_, err = Parse("01", decoder.PayloadConfig{
Fields: []decoder.FieldConfig{
{Name: "Moving", Start: 10, Length: 1},
},
TargetType: reflect.TypeOf(Port1Payload{}),
})
if err == nil {
t.Fatal("expected field start out of bounds")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,36 @@ func NewTagSLv1Decoder() decoder.Decoder {
return TagSLv1Decoder{}
}

func (t TagSLv1Decoder) GetConfig(port int16) (decoder.PayloadConfig, error) {
// https://docs.truvami.com/docs/payloads/tag-S
// https://docs.truvami.com/docs/payloads/tag-L
func (t TagSLv1Decoder) getConfig(port int16) (decoder.PayloadConfig, error) {
switch port {
case 1:
return decoder.PayloadConfig{
Fields: []decoder.FieldConfig{
{Name: "Moving", Start: 0, Length: 1},
{Name: "Lat", Start: 1, Length: 4, Transform: func(v interface{}) interface{} {
{Name: "Latitude", Start: 1, Length: 4, Transform: func(v interface{}) interface{} {
return float64(v.(int)) / 1000000
}},
{Name: "Lon", Start: 5, Length: 4, Transform: func(v interface{}) interface{} {
{Name: "Longitude", Start: 5, Length: 4, Transform: func(v interface{}) interface{} {
return float64(v.(int)) / 1000000
}},
{Name: "Alt", Start: 9, Length: 2},
{Name: "Altitude", Start: 9, Length: 2},
{Name: "Year", Start: 11, Length: 1},
{Name: "Month", Start: 12, Length: 1},
{Name: "Day", Start: 13, Length: 1},
{Name: "Hour", Start: 14, Length: 1},
{Name: "Minute", Start: 15, Length: 1},
{Name: "Second", Start: 16, Length: 1},
},
TargetType: reflect.TypeOf(GNSSPayload{}),
TargetType: reflect.TypeOf(Port1Payload{}),
}, nil
case 2:
return decoder.PayloadConfig{
Fields: []decoder.FieldConfig{
{Name: "Moving", Start: 0, Length: 1},
},
TargetType: reflect.TypeOf(Port2Payload{}),
}, nil
case 3:
return decoder.PayloadConfig{
Expand All @@ -55,7 +64,26 @@ func (t TagSLv1Decoder) GetConfig(port int16) (decoder.PayloadConfig, error) {
{Name: "Mac6", Start: 39, Length: 6, Optional: true},
{Name: "Rssi6", Start: 45, Length: 1, Optional: true},
},
TargetType: reflect.TypeOf(BlePayload{}),
TargetType: reflect.TypeOf(Port3Payload{}),
}, nil
case 4:
return decoder.PayloadConfig{
Fields: []decoder.FieldConfig{
{Name: "LocalizationIntervalWhileMoving", Start: 0, Length: 4},
{Name: "LocalizationIntervalWhileSteady", Start: 4, Length: 4},
{Name: "HeartbeatInterval", Start: 8, Length: 4},
{Name: "GPSTimeoutWhileWaitingForFix", Start: 12, Length: 2},
{Name: "AccelerometerWakeupThreshold", Start: 14, Length: 2},
{Name: "AccelerometerDelay", Start: 16, Length: 2},
{Name: "DeviceState", Start: 18, Length: 1},
{Name: "FirmwareVersionMajor", Start: 19, Length: 1},
{Name: "FirmwareVersionMinor", Start: 20, Length: 1},
{Name: "FirmwareVersionPatch", Start: 21, Length: 1},
{Name: "HardwareVersionType", Start: 22, Length: 1},
{Name: "HardwareVersionRevision", Start: 23, Length: 1},
{Name: "BatteryKeepAliveMessageInterval", Start: 24, Length: 4},
},
TargetType: reflect.TypeOf(Port4Payload{}),
}, nil
case 5:
return decoder.PayloadConfig{
Expand All @@ -76,15 +104,32 @@ func (t TagSLv1Decoder) GetConfig(port int16) (decoder.PayloadConfig, error) {
{Name: "Mac7", Start: 43, Length: 6, Optional: true},
{Name: "Rssi7", Start: 49, Length: 1, Optional: true},
},
TargetType: reflect.TypeOf(WifiPayload{}),
TargetType: reflect.TypeOf(Port5Payload{}),
}, nil
case 6:
return decoder.PayloadConfig{
Fields: []decoder.FieldConfig{
{Name: "ButtonPressed", Start: 0, Length: 1},
},
TargetType: reflect.TypeOf(Port6Payload{}),
}, nil
case 15:
return decoder.PayloadConfig{
Fields: []decoder.FieldConfig{
{Name: "LowBattery", Start: 0, Length: 1},
{Name: "BatteryVoltage", Start: 1, Length: 2, Transform: func(v interface{}) interface{} {
return float64(v.(int)) / 1000
}},
},
TargetType: reflect.TypeOf(Port15Payload{}),
}, nil
}

return decoder.PayloadConfig{}, fmt.Errorf("port %v not supported", port)
}

func (t TagSLv1Decoder) Decode(data string, port int16, devEui string) (interface{}, error) {
config, err := t.GetConfig(port)
config, err := t.getConfig(port)
if err != nil {
return nil, err
}
Expand All @@ -96,58 +141,3 @@ func (t TagSLv1Decoder) Decode(data string, port int16, devEui string) (interfac

return decodedData, nil
}

type GNSSPayload struct {
Moving bool `json:"moving"`
Lat float64 `json:"gps_lat"`
Lon float64 `json:"gps_lon"`
Alt float64 `json:"gps_alt"`
Year int `json:"year"`
Month int `json:"month"`
Day int `json:"day"`
Hour int `json:"hour"`
Minute int `json:"minute"`
Second int `json:"second"`
TS int64 `json:"ts"`
}

type AccessPoint struct {
MAC string `json:"mac"`
Rssi float32 `json:"rssi"`
}

type WifiPayload struct {
Moving bool `json:"moving"`
Mac1 string `json:"mac1"`
Rssi1 int8 `json:"rssi1"`
Mac2 string `json:"mac2"`
Rssi2 int8 `json:"rssi2"`
Mac3 string `json:"mac3"`
Rssi3 int8 `json:"rssi3"`
Mac4 string `json:"mac4"`
Rssi4 int8 `json:"rssi4"`
Mac5 string `json:"mac5"`
Rssi5 int8 `json:"rssi5"`
Mac6 string `json:"mac6"`
Rssi6 int8 `json:"rssi6"`
Mac7 string `json:"mac7"`
Rssi7 int8 `json:"rssi7"`
}

type BlePayload struct {
ScanPointer uint16 `json:"scanPointer"`
TotalMessages uint8 `json:"totalMessages"`
CurrentMessage uint8 `json:"currentMessage"`
Mac1 string `json:"mac1"`
Rssi1 int8 `json:"rssi1"`
Mac2 string `json:"mac2"`
Rssi2 int8 `json:"rssi2"`
Mac3 string `json:"mac3"`
Rssi3 int8 `json:"rssi3"`
Mac4 string `json:"mac4"`
Rssi4 int8 `json:"rssi4"`
Mac5 string `json:"mac5"`
Rssi5 int8 `json:"rssi5"`
Mac6 string `json:"mac6"`
Rssi6 int8 `json:"rssi6"`
}
Loading

0 comments on commit c49b7f7

Please sign in to comment.