Skip to content

Commit

Permalink
chore: update Go version to 1.23.0 and enhance decoder interface with…
Browse files Browse the repository at this point in the history
… new methods
  • Loading branch information
michaelbeutler committed Feb 4, 2025
1 parent 67d28e9 commit 55d887e
Show file tree
Hide file tree
Showing 27 changed files with 1,116 additions and 27 deletions.
21 changes: 21 additions & 0 deletions examples/advanced_tag_s_l/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

import (
"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...")
location, _, err := d.DecodePosition("0002c420ff005ed85a12b4180719142607", 1, "")
if err != nil {
panic(err)
}

log.Printf("latitude: %f, longitude: %f, altitude: %f\n", location.GetLatitude(), location.GetLongitude(), *location.GetAltitude())
}
23 changes: 23 additions & 0 deletions examples/advanced_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 := `46.407935`
if !strings.Contains(buf.String(), expectedOutput) {
t.Errorf("expected output %q not found", expectedOutput)
}
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module github.com/truvami/decoder

go 1.22.0
go 1.23.0

require (
github.com/go-playground/validator v9.31.0+incompatible
Expand Down
85 changes: 81 additions & 4 deletions pkg/common/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"net"

"reflect"
"strings"
Expand All @@ -12,6 +13,51 @@ import (
"github.com/go-playground/validator"
)

type PositionSource int32

const (
PositionSource_GNSS PositionSource = 0
PositionSource_LORA PositionSource = 1
PositionSource_WIFI PositionSource = 2
PositionSource_BLE PositionSource = 3
)

type Position interface {
GetLatitude() float64
GetLongitude() float64
GetAltitude() *float64
GetSource() PositionSource
GetCapturedAt() *time.Time
GetBuffered() bool
}

type WifiAccessPoint struct {
MacAddress net.HardwareAddr
Rssi int8
}

type WifiLocation interface {
GetAccessPoints() []WifiAccessPoint
}

func AppendAccessPoint(accessPoints []WifiAccessPoint, mac string, rssi int8) []WifiAccessPoint {
if hw, err := net.ParseMAC(addColonToMac(mac)); err == nil {
accessPoints = append(accessPoints, WifiAccessPoint{
MacAddress: hw,
Rssi: rssi,
})
}
return accessPoints
}

func addColonToMac(mac string) string {
if len(mac) != 12 {
return mac
}
return mac[:2] + ":" + mac[2:4] + ":" + mac[4:6] + ":" + mac[6:8] + ":" + mac[8:10] + ":" + mac[10:12]

}

func HexStringToBytes(hexString string) ([]byte, error) {
bytes, err := hex.DecodeString(hexString)
if err != nil {
Expand Down Expand Up @@ -109,11 +155,13 @@ func UnwrapError(err error) []error {
}

// DecodeLoRaWANPayload decodes the payload based on the provided configuration and populates the target struct
func Parse(payloadHex string, config *PayloadConfig) (interface{}, error) {
func Parse[T any](payloadHex string, config *PayloadConfig) (T, error) {
var nilValue T

// Convert hex payload to bytes
payloadBytes, err := HexStringToBytes(payloadHex)
if err != nil {
return nil, err
return nilValue, err
}

// Create an instance of the target struct
Expand All @@ -131,7 +179,7 @@ func Parse(payloadHex string, config *PayloadConfig) (interface{}, error) {
// Extract the field value from the payload
value, err := extractFieldValue(payloadBytes, start, length, optional, hex)
if err != nil {
return nil, err
return nilValue, err
}

// Convert value to appropriate type and set it in the target struct
Expand Down Expand Up @@ -165,7 +213,7 @@ func Parse(payloadHex string, config *PayloadConfig) (interface{}, error) {
}
}

return targetValue.Interface(), errors.Join(errs...)
return targetValue.Interface().(T), errors.Join(errs...)
}

func ParseTimestamp(timestamp int) time.Time {
Expand Down Expand Up @@ -301,3 +349,32 @@ func uintToBytes(value uint64, length int) []byte {
}
return buf
}

// ToMap converts a struct to a map using the struct's tags.
//
// ToMap uses tags on struct fields to decide which fields to add to the
// returned map.
func ToMap(in interface{}, tag string) (map[string]interface{}, error) {
out := make(map[string]interface{})

v := reflect.ValueOf(in)
if v.Kind() == reflect.Ptr {
v = v.Elem()
}

// we only accept structs
if v.Kind() != reflect.Struct {
return nil, fmt.Errorf("ToMap only accepts structs; got %T", v)
}

typ := v.Type()
for i := 0; i < v.NumField(); i++ {
// gets us a StructField
fi := typ.Field(i)
if tagv := fi.Tag.Get(tag); tagv != "" {
// set key of map to value in struct field
out[tagv] = v.Field(i).Interface()
}
}
return out, nil
}
112 changes: 103 additions & 9 deletions pkg/common/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package common

import (
"fmt"
"net"
"reflect"
"testing"
)
Expand Down Expand Up @@ -80,17 +81,14 @@ func TestParse(t *testing.T) {

for _, test := range tests {
t.Run(test.payload, func(t *testing.T) {
decodedData, err := Parse(test.payload, &test.config)
decodedData, err := Parse[Port1Payload](test.payload, &test.config)
if err != nil {
t.Fatalf("error decoding payload: %v", err)
}

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

// Check the decoded data against the expected data using reflect.DeepEqual
if !reflect.DeepEqual(payload, test.expected) {
t.Fatalf("decoded data does not match expected data expected: %+v got: %+v", test.expected, payload)
if !reflect.DeepEqual(decodedData, test.expected) {
t.Fatalf("decoded data does not match expected data expected: %+v got: %+v", test.expected, decodedData)
}
})
}
Expand Down Expand Up @@ -179,7 +177,7 @@ func TestConvertFieldToType(t *testing.T) {
}

func TestInvalidPayload(t *testing.T) {
_, err := Parse("", &PayloadConfig{
_, err := Parse[interface{}]("", &PayloadConfig{
Fields: []FieldConfig{
{Name: "Moving", Start: 0, Length: 1},
},
Expand All @@ -189,7 +187,7 @@ func TestInvalidPayload(t *testing.T) {
t.Fatal("expected field out of bounds")
}

_, err = Parse("01", &PayloadConfig{
_, err = Parse[interface{}]("01", &PayloadConfig{
Fields: []FieldConfig{
{Name: "Moving", Start: 0, Length: 2},
},
Expand All @@ -199,7 +197,7 @@ func TestInvalidPayload(t *testing.T) {
t.Fatal("expected field out of bounds")
}

_, err = Parse("01", &PayloadConfig{
_, err = Parse[interface{}]("01", &PayloadConfig{
Fields: []FieldConfig{
{Name: "Moving", Start: 10, Length: 1},
},
Expand Down Expand Up @@ -261,3 +259,99 @@ func TestUintToBinaryArray(t *testing.T) {
})
}
}
func TestAppendAccessPoint(t *testing.T) {
tests := []struct {
accessPoints []WifiAccessPoint
mac string
rssi int8
expected []WifiAccessPoint
}{
{
accessPoints: []WifiAccessPoint{},
mac: "001122334455",
rssi: -50,
expected: []WifiAccessPoint{
{
MacAddress: net.HardwareAddr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55},
Rssi: -50,
},
},
},
{
accessPoints: []WifiAccessPoint{
{
MacAddress: net.HardwareAddr{0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB},
Rssi: -60,
},
},
mac: "001122334455",
rssi: -50,
expected: []WifiAccessPoint{
{
MacAddress: net.HardwareAddr{0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB},
Rssi: -60,
},
{
MacAddress: net.HardwareAddr{0x00, 0x11, 0x22, 0x33, 0x44, 0x55},
Rssi: -50,
},
},
},
{
accessPoints: []WifiAccessPoint{},
mac: "invalidmac",
rssi: -50,
expected: []WifiAccessPoint{},
},
}

for _, test := range tests {
t.Run(fmt.Sprintf("%v_%v", test.mac, test.rssi), func(t *testing.T) {
result := AppendAccessPoint(test.accessPoints, test.mac, test.rssi)
if !reflect.DeepEqual(result, test.expected) {
t.Fatalf("expected: %v got: %v", test.expected, result)
}
})
}
}

func TestAddColonToMac(t *testing.T) {
tests := []struct {
input string
expected string
}{
{
input: "001122334455",
expected: "00:11:22:33:44:55",
},
{
input: "AABBCCDDEEFF",
expected: "AA:BB:CC:DD:EE:FF",
},
{
input: "1234567890AB",
expected: "12:34:56:78:90:AB",
},
{
input: "0011223344",
expected: "0011223344", // Invalid length, should return input as is
},
{
input: "00112233445566",
expected: "00112233445566", // Invalid length, should return input as is
},
{
input: "8c59c3c99fc0",
expected: "8c:59:c3:c9:9f:c0",
},
}

for _, test := range tests {
t.Run(test.input, func(t *testing.T) {
result := addColonToMac(test.input)
if result != test.expected {
t.Fatalf("expected: %v got: %v", test.expected, result)
}
})
}
}
4 changes: 4 additions & 0 deletions pkg/decoder/decoder.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package decoder

import "github.com/truvami/decoder/pkg/common"

type Decoder interface {
Decode(string, int16, string) (interface{}, interface{}, error)
DecodePosition(string, int16, string) (common.Position, interface{}, error)
DecodeWifi(string, int16, string) (common.WifiLocation, interface{}, error)
}
29 changes: 28 additions & 1 deletion pkg/decoder/nomadxl/v1/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,33 @@ type NomadXLv1Decoder struct {
skipValidation bool
}

// DecodeWifi implements decoder.Decoder.
func (t *NomadXLv1Decoder) DecodeWifi(string, int16, string) (common.WifiLocation, interface{}, error) {
panic("unimplemented")
}

// DecodePosition implements decoder.Decoder.
func (t *NomadXLv1Decoder) DecodePosition(data string, port int16, devEui string) (common.Position, interface{}, error) {
config, err := t.getConfig(port)
if err != nil {
return nil, nil, err
}

if t.autoPadding {
data = common.HexNullPad(&data, &config)
}

if !t.skipValidation {
err := common.ValidateLength(&data, &config)
if err != nil {
return nil, nil, err
}
}

decodedData, err := common.Parse[common.Position](data, &config)
return decodedData, nil, err
}

func NewNomadXLv1Decoder(options ...Option) decoder.Decoder {
nomadXLv1Decoder := &NomadXLv1Decoder{}

Expand Down Expand Up @@ -108,6 +135,6 @@ func (t NomadXLv1Decoder) Decode(data string, port int16, devEui string) (interf
}
}

decodedData, err := common.Parse(data, &config)
decodedData, err := common.Parse[interface{}](data, &config)
return decodedData, nil, err
}
Loading

0 comments on commit 55d887e

Please sign in to comment.