Skip to content

Commit

Permalink
Support new devices. ✅
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelbeutler committed Feb 20, 2025
1 parent 09324cd commit 461b9a3
Show file tree
Hide file tree
Showing 25 changed files with 595 additions and 76 deletions.
10 changes: 9 additions & 1 deletion pkg/decoder/decoder.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package decoder

import "time"
import (
"time"
)

type Decoder interface {
Decode(payload string, port int16, devEui string) (*DecodedUplink, error)
Expand All @@ -17,6 +19,7 @@ const (
FeatureBle Feature = "ble"
FeatureButton Feature = "button"
FeatureConfig Feature = "config"
FeatureMoving Feature = "moving"
)

type DecodedUplink struct {
Expand Down Expand Up @@ -97,3 +100,8 @@ type UplinkFeatureWiFi interface {
// GetAccessPoints returns the list of WiFi access points detected by the device.
GetAccessPoints() []AccessPoint
}

type UplinkFeatureMoving interface {
// IsMoving returns true if the device is moving, otherwise it returns false.
IsMoving() bool
}
2 changes: 2 additions & 0 deletions pkg/decoder/nomadxl/v1/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func (t NomadXLv1Decoder) getConfig(port int16) (common.PayloadConfig, error) {
{Name: "TimeToFix", Start: 37, Length: 1},
},
TargetType: reflect.TypeOf(Port101Payload{}),
Features: []decoder.Feature{decoder.FeatureBattery, decoder.FeatureTemperature, decoder.FeatureBuffered},
}, nil
case 103:
return common.PayloadConfig{
Expand All @@ -85,6 +86,7 @@ func (t NomadXLv1Decoder) getConfig(port int16) (common.PayloadConfig, error) {
}},
},
TargetType: reflect.TypeOf(Port103Payload{}),
Features: []decoder.Feature{decoder.FeatureGNSS},
}, nil
}

Expand Down
91 changes: 91 additions & 0 deletions pkg/decoder/nomadxl/v1/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package nomadxl
import (
"fmt"
"testing"

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

func TestDecode(t *testing.T) {
Expand Down Expand Up @@ -116,3 +118,92 @@ func TestPayloadTooLong(t *testing.T) {
t.Fatal("expected error payload too long")
}
}

func TestFeatures(t *testing.T) {
tests := []struct {
payload string
port int16
skipValidation bool
}{
{
payload: "00000001fdd5c693000079300001b45d000000000000000000d700000000000000000b3fd724",
port: 101,
},
{
payload: "0000793000020152004B6076000C838C00003994",
port: 103,
},
}

for _, test := range tests {
t.Run(fmt.Sprintf("TestFeaturesWithPort%vAndPayload%v", test.port, test.payload), func(t *testing.T) {
d := NewNomadXLv1Decoder(
WithSkipValidation(test.skipValidation),
)
decodedPayload, _ := d.Decode(test.payload, test.port, "")

// should be able to decode base feature
base, ok := decodedPayload.Data.(decoder.UplinkFeatureBase)
if !ok {
t.Fatalf("expected UplinkFeatureBase, got %T", decodedPayload)
}
// check if it panics
base.GetTimestamp()

if decodedPayload.Is(decoder.FeatureGNSS) {
gnss, ok := decodedPayload.Data.(decoder.UplinkFeatureGNSS)
if !ok {
t.Fatalf("expected UplinkFeatureGNSS, got %T", decodedPayload)
}
if gnss.GetLatitude() == 0 {
t.Fatalf("expected non zero latitude")
}
if gnss.GetLongitude() == 0 {
t.Fatalf("expected non zero longitude")
}
if gnss.GetAltitude() == 0 {
t.Fatalf("expected non zero altitude")
}
// call function to check if it panics
gnss.GetAltitude()
gnss.GetPDOP()
gnss.GetSatellites()
gnss.GetTTF()
}
if decodedPayload.Is(decoder.FeatureBuffered) {
buffered, ok := decodedPayload.Data.(decoder.UplinkFeatureBuffered)
if !ok {
t.Fatalf("expected UplinkFeatureBuffered, got %T", decodedPayload)
}
// call function to check if it panics
buffered.GetBufferLevel()
}
if decodedPayload.Is(decoder.FeatureBattery) {
batteryVoltage, ok := decodedPayload.Data.(decoder.UpLinkFeatureBattery)
if !ok {
t.Fatalf("expected UplinkFeatureBattery, got %T", decodedPayload)
}
if batteryVoltage.GetBatteryVoltage() == 0 {
t.Fatalf("expected non zero battery voltage")
}
}
if decodedPayload.Is(decoder.FeatureWiFi) {
wifi, ok := decodedPayload.Data.(decoder.UplinkFeatureWiFi)
if !ok {
t.Fatalf("expected UplinkFeatureWiFi, got %T", decodedPayload)
}
if wifi.GetAccessPoints() == nil {
t.Fatalf("expected non nil access points")
}
}
if decodedPayload.Is(decoder.FeatureMoving) {
moving, ok := decodedPayload.Data.(decoder.UplinkFeatureMoving)
if !ok {
t.Fatalf("expected UplinkFeatureMoving, got %T", decodedPayload)
}
// call function to check if it panics
moving.IsMoving()
}
})
}
}
25 changes: 25 additions & 0 deletions pkg/decoder/nomadxl/v1/port101.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package nomadxl

import (
"time"

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

// | Byte | Size | Description | Format |
// |-------|------|-----------------------------------------------------|---------------------|
// | 0-7 | 8 | System time (ms since reset) | uint64_t, ms |
Expand Down Expand Up @@ -39,3 +45,22 @@ type Port101Payload struct {
Battery float64 `json:"battery" validate:"gte=1,lte=5"`
BatteryLorawan uint8 `json:"batteryLorawan"`
}

var _ decoder.UplinkFeatureBase = &Port101Payload{}
var _ decoder.UpLinkFeatureBattery = &Port101Payload{}
var _ decoder.UplinkFeatureBuffered = &Port101Payload{}

// GetBatteryVoltage implements decoder.UpLinkFeatureBattery.
func (p Port101Payload) GetBatteryVoltage() float64 {
return p.Battery
}

// GetTimestamp implements decoder.UplinkFeatureBase.
func (p Port101Payload) GetTimestamp() *time.Time {
return nil
}

// GetBufferLevel implements decoder.UplinkFeatureBuffered.
func (p Port101Payload) GetBufferLevel() uint16 {
return 0
}
49 changes: 49 additions & 0 deletions pkg/decoder/nomadxl/v1/port103.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package nomadxl

import (
"time"

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

// | Byte | Size | Description | Format |
// |-------|------|-------------|----------------------|
// | 0-3 | 4 | UTC Date | uint32, DDMMYY |
Expand All @@ -15,3 +21,46 @@ type Port103Payload struct {
Longitude float64 `json:"longitude"`
Altitude float64 `json:"altitude"`
}

var _ decoder.UplinkFeatureBase = &Port103Payload{}
var _ decoder.UplinkFeatureGNSS = &Port103Payload{}

// GetAccuracy implements decoder.UplinkFeatureGNSS.
func (p Port103Payload) GetAccuracy() *float64 {
return nil

Check warning on line 30 in pkg/decoder/nomadxl/v1/port103.go

View check run for this annotation

Codecov / codecov/patch

pkg/decoder/nomadxl/v1/port103.go#L29-L30

Added lines #L29 - L30 were not covered by tests
}

// GetAltitude implements decoder.UplinkFeatureGNSS.
func (p Port103Payload) GetAltitude() float64 {
return p.Altitude
}

// GetLatitude implements decoder.UplinkFeatureGNSS.
func (p Port103Payload) GetLatitude() float64 {
return p.Latitude
}

// GetLongitude implements decoder.UplinkFeatureGNSS.
func (p Port103Payload) GetLongitude() float64 {
return p.Longitude
}

// GetPDOP implements decoder.UplinkFeatureGNSS.
func (p Port103Payload) GetPDOP() *float64 {
return nil
}

// GetSatellites implements decoder.UplinkFeatureGNSS.
func (p Port103Payload) GetSatellites() *uint8 {
return nil
}

// GetTTF implements decoder.UplinkFeatureGNSS.
func (p Port103Payload) GetTTF() *float64 {
return nil
}

// GetTimestamp implements decoder.UplinkFeatureBase.
func (p Port103Payload) GetTimestamp() *time.Time {
return nil
}
9 changes: 7 additions & 2 deletions pkg/decoder/nomadxs/v1/decoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,9 @@ func (t NomadXSv1Decoder) getConfig(port int16) (common.PayloadConfig, error) {
return float32(v.(int))
}},
},
TargetType: reflect.TypeOf(Port1Payload{}),
TargetType: reflect.TypeOf(Port1Payload{}),
StatusByteIndex: common.ToIntPointer(0),
Features: []decoder.Feature{decoder.FeatureGNSS, decoder.FeatureMoving, decoder.FeatureTemperature},
}, nil
case 4:
return common.PayloadConfig{
Expand All @@ -112,6 +114,7 @@ func (t NomadXSv1Decoder) getConfig(port int16) (common.PayloadConfig, error) {
{Name: "LightUpperThreshold", Start: 34, Length: 2},
},
TargetType: reflect.TypeOf(Port4Payload{}),
Features: []decoder.Feature{decoder.FeatureConfig},
}, nil
case 15:
return common.PayloadConfig{
Expand All @@ -121,7 +124,9 @@ func (t NomadXSv1Decoder) getConfig(port int16) (common.PayloadConfig, error) {
return float64(v.(int)) / 1000
}},
},
TargetType: reflect.TypeOf(Port15Payload{}),
TargetType: reflect.TypeOf(Port15Payload{}),
StatusByteIndex: common.ToIntPointer(0),
Features: []decoder.Feature{decoder.FeatureBattery},
}, nil
}

Expand Down
94 changes: 94 additions & 0 deletions pkg/decoder/nomadxs/v1/decoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"testing"

helpers "github.com/truvami/decoder/pkg/common"
"github.com/truvami/decoder/pkg/decoder"
)

func TestDecode(t *testing.T) {
Expand Down Expand Up @@ -311,3 +312,96 @@ func TestPayloadTooLong(t *testing.T) {
t.Fatal("expected error payload too long")
}
}

func TestFeatures(t *testing.T) {
tests := []struct {
payload string
port int16
skipValidation bool
}{
{
payload: "0002c420ff005ed85a12b4180719142607240001ffbaffc2fc6f00d71d2e",
port: 1,
},
{
payload: "0000007800000708000151800078012c05dc000100010100000258000002580500000000",
port: 4,
},
{
payload: "010df6",
port: 15,
},
}

for _, test := range tests {
t.Run(fmt.Sprintf("TestFeaturesWithPort%vAndPayload%v", test.port, test.payload), func(t *testing.T) {
d := NewNomadXSv1Decoder(
WithSkipValidation(test.skipValidation),
)
decodedPayload, _ := d.Decode(test.payload, test.port, "")

// should be able to decode base feature
base, ok := decodedPayload.Data.(decoder.UplinkFeatureBase)
if !ok {
t.Fatalf("expected UplinkFeatureBase, got %T", decodedPayload)
}
// check if it panics
base.GetTimestamp()

if decodedPayload.Is(decoder.FeatureGNSS) {
gnss, ok := decodedPayload.Data.(decoder.UplinkFeatureGNSS)
if !ok {
t.Fatalf("expected UplinkFeatureGNSS, got %T", decodedPayload)
}
if gnss.GetLatitude() == 0 {
t.Fatalf("expected non zero latitude")
}
if gnss.GetLongitude() == 0 {
t.Fatalf("expected non zero longitude")
}
if gnss.GetAltitude() == 0 {
t.Fatalf("expected non zero altitude")
}
// call function to check if it panics
gnss.GetAltitude()
gnss.GetPDOP()
gnss.GetSatellites()
gnss.GetTTF()
}
if decodedPayload.Is(decoder.FeatureBuffered) {
buffered, ok := decodedPayload.Data.(decoder.UplinkFeatureBuffered)
if !ok {
t.Fatalf("expected UplinkFeatureBuffered, got %T", decodedPayload)
}
// call function to check if it panics
buffered.GetBufferLevel()
}
if decodedPayload.Is(decoder.FeatureBattery) {
batteryVoltage, ok := decodedPayload.Data.(decoder.UpLinkFeatureBattery)
if !ok {
t.Fatalf("expected UplinkFeatureBattery, got %T", decodedPayload)
}
if batteryVoltage.GetBatteryVoltage() == 0 {
t.Fatalf("expected non zero battery voltage")
}
}
if decodedPayload.Is(decoder.FeatureWiFi) {
wifi, ok := decodedPayload.Data.(decoder.UplinkFeatureWiFi)
if !ok {
t.Fatalf("expected UplinkFeatureWiFi, got %T", decodedPayload)
}
if wifi.GetAccessPoints() == nil {
t.Fatalf("expected non nil access points")
}
}
if decodedPayload.Is(decoder.FeatureMoving) {
moving, ok := decodedPayload.Data.(decoder.UplinkFeatureMoving)
if !ok {
t.Fatalf("expected UplinkFeatureMoving, got %T", decodedPayload)
}
// call function to check if it panics
moving.IsMoving()
}
})
}
}
Loading

0 comments on commit 461b9a3

Please sign in to comment.