Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add dragino sensor #25

Merged
merged 11 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions cmd/cli/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@ import (
"context"
"time"

"github.com/viam-modules/gateway/gateway"
"go.viam.com/rdk/logging"
"go.viam.com/rdk/resource"

"gateway/gateway"
)

func main() {
Expand Down Expand Up @@ -38,7 +37,7 @@ func realMain() error {
return err
}

for _ = range 10 {
for range 10 {
time.Sleep(time.Second)
r, err := g.Readings(ctx, nil)
if err != nil {
Expand Down
137 changes: 137 additions & 0 deletions draginolht65n/sensor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Package draginolht65n implements the dragino_lth65n model
package draginolht65n

import (
"context"
"net/http"
"time"

"github.com/viam-modules/gateway/node"
"go.viam.com/rdk/components/sensor"
"go.viam.com/rdk/logging"
"go.viam.com/rdk/resource"
)

const (
decoderFilename = "LHT65NChirpstack4decoder.js"
decoderURL = "https://raw.githubusercontent.com/dragino/dragino-end-node-decoder/refs/heads/main/LHT65N/" +
"LHT65N Chirpstack 4.0 decoder.txt"
)

// Model represents a dragino-LHT65N lorawan node model.
var Model = node.LorawanFamily.WithModel("dragino-LHT65N")

// Config defines the dragino-LHT65N's config.
type Config struct {
JoinType string `json:"join_type,omitempty"`
Interval *float64 `json:"uplink_interval_mins"`
DevEUI string `json:"dev_eui,omitempty"`
AppKey string `json:"app_key,omitempty"`
AppSKey string `json:"app_s_key,omitempty"`
NwkSKey string `json:"network_s_key,omitempty"`
DevAddr string `json:"dev_addr,omitempty"`
Gateways []string `json:"gateways"`
}

func init() {
resource.RegisterComponent(
sensor.API,
Model,
resource.Registration[sensor.Sensor, *Config]{
Constructor: newLHT65N,
})
}

func (conf *Config) getNodeConfig(decoderFilePath string) node.Config {
return node.Config{
JoinType: conf.JoinType,
DecoderPath: decoderFilePath,
Interval: conf.Interval,
DevEUI: conf.DevEUI,
AppKey: conf.AppKey,
AppSKey: conf.AppSKey,
NwkSKey: conf.NwkSKey,
DevAddr: conf.DevAddr,
Gateways: conf.Gateways,
}
}
Comment on lines +45 to +57
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically we have other sensors create their own node.Config, then we can reuse all of the validation used in the node implementation


// Validate ensures all parts of the config are valid.
func (conf *Config) Validate(path string) ([]string, error) {
nodeConf := conf.getNodeConfig("fixed")
deps, err := nodeConf.Validate(path)
if err != nil {
return nil, err
}
return deps, nil
}

// LHT65N defines a lorawan node device.
type LHT65N struct {
resource.Named
logger logging.Logger

node node.Node
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the dragino LHT65N wraps a node implementation, so we can easily reuse all of the node functionality

decoderPath string
}

func newLHT65N(
ctx context.Context,
deps resource.Dependencies,
conf resource.Config,
logger logging.Logger,
) (sensor.Sensor, error) {
httpClient := &http.Client{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is the only time we use the httpClient could we just create it in the WriteDecoderFileFromURL instead of here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to create the httpClient outside of the function for the mock in testing to work :(

Timeout: time.Second * 25,
}
decoderFilePath, err := node.WriteDecoderFileFromURL(ctx, decoderFilename, decoderURL, httpClient, logger)
if err != nil {
return nil, err
}

n := &LHT65N{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of wrapping the node object I think you could just create a node.Node here, then you could reuse the node's decoderPath field and the Readings/close functions. Just something to consider not 100% sure it would work.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried that initially but ran into some issues with reconfigure. I can't reuse node.Node because the reconfigure function is still using a resource.Config that looks for a node.Config with a decoderPath set. Wrapping the object + adding the helpers lets me get around the issue.

Named: conf.ResourceName().AsNamed(),
logger: logger,
node: node.NewSensor(conf, logger),
decoderPath: decoderFilePath,
}

err = n.Reconfigure(ctx, deps, conf)
if err != nil {
return nil, err
}

return n, nil
}

// Reconfigure reconfigure's the node.
func (n *LHT65N) Reconfigure(ctx context.Context, deps resource.Dependencies, conf resource.Config) error {
cfg, err := resource.NativeConfig[*Config](conf)
if err != nil {
return err
}

nodeCfg := cfg.getNodeConfig(n.decoderPath)

err = n.node.ReconfigureWithConfig(ctx, deps, &nodeCfg)
if err != nil {
return err
}

err = node.CheckCaptureFrequency(conf, *cfg.Interval, n.logger)
if err != nil {
return err
}

return nil
}

// Close removes the device from the gateway.
func (n *LHT65N) Close(ctx context.Context) error {
return n.node.Close(ctx)
}

// Readings returns the node's readings.
func (n *LHT65N) Readings(ctx context.Context, extra map[string]interface{}) (map[string]interface{}, error) {
return n.node.Readings(ctx, extra)
}
34 changes: 34 additions & 0 deletions draginolht65n/sensor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package draginolht65n
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pretty much everything is already covered by node_test.go. Once we add more dragino specific fields we will probably add more to here.


import (
"testing"

"github.com/viam-modules/gateway/node"
"go.viam.com/test"
)

const (
// OTAA test values.
testDevEUI = "0123456789ABCDEF"
testAppKey = "0123456789ABCDEF0123456789ABAAAA"

// Gateway dependency.
testGatewayName = "gateway"
)

var testInterval = 5.0

func TestConfigValidate(t *testing.T) {
// valid config
conf := &Config{
Interval: &testInterval,
JoinType: node.JoinTypeOTAA,
DevEUI: testDevEUI,
AppKey: testAppKey,
Gateways: []string{testGatewayName},
}
deps, err := conf.Validate("")
test.That(t, err, test.ShouldBeNil)
test.That(t, len(deps), test.ShouldEqual, 1)
test.That(t, deps[0], test.ShouldEqual, testGatewayName)
}
4 changes: 2 additions & 2 deletions gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ import (
"encoding/hex"
"errors"
"fmt"
"gateway/node"
"os"
"path/filepath"
"strconv"
Expand All @@ -27,6 +26,7 @@ import (
"time"
"unsafe"

"github.com/viam-modules/gateway/node"
"go.viam.com/rdk/components/board"
"go.viam.com/rdk/components/sensor"
"go.viam.com/rdk/data"
Expand All @@ -50,7 +50,7 @@ var (
)

// Model represents a lorawan gateway model.
var Model = resource.NewModel("viam", "lorawan", "sx1302-gateway")
var Model = node.LorawanFamily.WithModel("sx1302-gateway")

// LoggingRoutineStarted is a global variable to track if the captureCOutputToLogs goroutine has
// started for each gateway. If the gateway build errors and needs to build again, we only want to start
Expand Down
7 changes: 3 additions & 4 deletions gateway/gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"context"
"encoding/json"
"fmt"
"gateway/node"
"testing"

"github.com/viam-modules/gateway/node"
"go.viam.com/rdk/components/sensor"
"go.viam.com/rdk/data"
"go.viam.com/rdk/logging"
Expand All @@ -17,7 +17,7 @@ import (

// setupTestGateway creates a test gateway with a configured test device.
func setupTestGateway(t *testing.T) *gateway {
//Create a temp device data file for testing
// Create a temp device data file for testing
file := createDataFile(t)

testDevices := make(map[string]*node.Node)
Expand All @@ -36,7 +36,7 @@ func setupTestGateway(t *testing.T) *gateway {
}
}

// creates a test gateway with device info populated in the file
// creates a test gateway with device info populated in the file.
func setupFileAndGateway(t *testing.T) *gateway {
g := setupTestGateway(t)

Expand Down Expand Up @@ -450,7 +450,6 @@ func TestUpdateDeviceInfo(t *testing.T) {
test.That(t, device.NodeName, test.ShouldEqual, testNodeName)
test.That(t, device.AppSKey, test.ShouldResemble, newAppSKey)
test.That(t, device.Addr, test.ShouldResemble, newDevAddr)

}

func TestClose(t *testing.T) {
Expand Down
5 changes: 4 additions & 1 deletion gateway/join.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import (
"encoding/json"
"errors"
"fmt"
"gateway/node"
"io"
"math/rand"
"os"
"time"

"github.com/viam-modules/gateway/node"
"go.thethings.network/lorawan-stack/v3/pkg/crypto"
"go.thethings.network/lorawan-stack/v3/pkg/crypto/cryptoservices"
"go.thethings.network/lorawan-stack/v3/pkg/ttnpb"
Expand Down Expand Up @@ -160,8 +160,11 @@ func (g *gateway) generateJoinAccept(ctx context.Context, jr joinRequest, d *nod

payload := make([]byte, 0)
payload = append(payload, 0x20)
//nolint:all
payload = append(payload, jnLE[:]...)
//nolint:all
payload = append(payload, netIDLE[:]...)
//nolint:all
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tysm for fixing

payload = append(payload, dAddrLE[:]...)

// DLSettings byte:
Expand Down
4 changes: 1 addition & 3 deletions gateway/join_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@ import (
"path/filepath"
"testing"

"gateway/node"

"github.com/viam-modules/gateway/node"
"go.thethings.network/lorawan-stack/v3/pkg/crypto"
"go.thethings.network/lorawan-stack/v3/pkg/types"
"go.viam.com/rdk/logging"
Expand Down Expand Up @@ -153,7 +152,6 @@ func TestParseJoinRequestPacket(t *testing.T) {

err = g.Close(context.Background())
test.That(t, err, test.ShouldBeNil)

}

func TestGenerateJoinAccept(t *testing.T) {
Expand Down
3 changes: 1 addition & 2 deletions gateway/uplink.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ import (
"reflect"
"time"

"gateway/node"

"github.com/robertkrimen/otto"
"github.com/viam-modules/gateway/node"
"go.thethings.network/lorawan-stack/v3/pkg/crypto"
"go.thethings.network/lorawan-stack/v3/pkg/types"
)
Expand Down
4 changes: 2 additions & 2 deletions gateway/uplink_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@ package gateway

import (
"context"
"gateway/node"
"testing"

"github.com/viam-modules/gateway/node"
"go.viam.com/rdk/logging"
"go.viam.com/test"
)

// setupTestGateway creates a test gateway with a configured test device.
func setupUplinkGateway(t *testing.T) *gateway {
//Create a temp device data file for testing
// Create a temp device data file for testing
file := createDataFile(t)

testDevices := make(map[string]*node.Node)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module gateway
module github.com/viam-modules/gateway

go 1.23

Expand Down
7 changes: 4 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
package main

import (
"gateway/gateway"
"gateway/node"

"github.com/viam-modules/gateway/draginolht65n"
"github.com/viam-modules/gateway/gateway"
"github.com/viam-modules/gateway/node"
"go.viam.com/rdk/components/sensor"
"go.viam.com/rdk/module"
"go.viam.com/rdk/resource"
Expand All @@ -14,5 +14,6 @@ func main() {
module.ModularMain(
resource.APIModel{API: sensor.API, Model: gateway.Model},
resource.APIModel{API: sensor.API, Model: node.Model},
resource.APIModel{API: sensor.API, Model: draginolht65n.Model},
)
}
10 changes: 8 additions & 2 deletions meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,14 @@
"api": "rdk:component:sensor",
"model": "viam:lorawan:node",
"markdown_link": "README.md#configure-the-viamsensornode",
"short_description": "A sensor compoenent for a LoRaWAN node."
}
"short_description": "A sensor component for a LoRaWAN node."
},
{
"api": "rdk:component:sensor",
"model": "viam:lorawan:dragino-LHT65N",
"markdown_link": "README.md#configure-the-viamsensornode",
"short_description": "A sensor component for a dragino LHT65N LoRaWAN node."
}
],
"build": {
"path": "module.tar.gz",
Expand Down
Loading