-
Notifications
You must be signed in to change notification settings - Fork 3
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
Changes from all commits
3099b04
832b4fa
1001379
02d438e
33529e6
072fbd2
d3e09c5
1e54eca
2168082
a3dc7a4
a06baea
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, | ||
} | ||
} | ||
|
||
// 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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{ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package draginolht65n | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. pretty much everything is already covered by |
||
|
||
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) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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" | ||
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tysm for fixing |
||
payload = append(payload, dAddrLE[:]...) | ||
|
||
// DLSettings byte: | ||
|
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 | ||
|
||
|
There was a problem hiding this comment.
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