From 3099b04f9e06eb19cca352761b1c5f13f796e87f Mon Sep 17 00:00:00 2001 From: John Date: Thu, 13 Feb 2025 18:03:05 -0500 Subject: [PATCH 01/11] add dragino sensor --- draginolht65n/LHT65NChirpstack4decoder.js | 332 ++++++++++++++++++++++ draginolht65n/sensor.go | 136 +++++++++ draginolht65n/sensor_test.go | 36 +++ gateway/gateway_test.go | 5 +- gateway/join.go | 6 +- gateway/join_test.go | 1 - gateway/uplink_test.go | 2 +- main.go | 2 + meta.json | 8 +- node/node.go | 180 ++++-------- node/node_test.go | 70 ++--- node/node_utils.go | 157 ++++++++++ 12 files changed, 768 insertions(+), 167 deletions(-) create mode 100644 draginolht65n/LHT65NChirpstack4decoder.js create mode 100644 draginolht65n/sensor.go create mode 100644 draginolht65n/sensor_test.go create mode 100644 node/node_utils.go diff --git a/draginolht65n/LHT65NChirpstack4decoder.js b/draginolht65n/LHT65NChirpstack4decoder.js new file mode 100644 index 0000000..24932ca --- /dev/null +++ b/draginolht65n/LHT65NChirpstack4decoder.js @@ -0,0 +1,332 @@ +function decodeUplink(input) { + return { + data: Decode(input.fPort, input.bytes, input.variables) + }; +} + +function Str1(str2){ + var str3 =""; + for (var i=0;i>16 | bytes[1+i])/100).toFixed(2)); +} + else if(Ext=='2') +{ + bb=parseFloat(((bytes[0+i]<<24>>16 | bytes[1+i])/100).toFixed(2)); +} + else if(Ext=='4') +{ + var Exti_pin_level=bytes[0+i] ? "High":"Low"; + var Exti_status=bytes[1+i] ? "True":"False"; + bb=Exti_pin_level+Exti_status; +} + else if(Ext=='5') +{ + bb=bytes[0+i]<<8 | bytes[1+i]; +} + else if(Ext=='6') +{ + bb=(bytes[0+i]<<8 | bytes[1+i])/1000; +} + else if(Ext=='7') +{ + bb=bytes[0+i]<<8 | bytes[1+i]; +} + else if((Ext=='8')||(Ext=='14')) +{ + bb=bytes[0+i]<<8 | bytes[1+i]; +} +else if(Ext=='11') +{ + bb=parseFloat(((bytes[0+i]<<24>>16 | bytes[1+i])/100).toFixed(2)); +} + var cc= parseFloat(((bytes[2+i]<<24>>16 | bytes[3+i])/100).toFixed(2)); + var dd= parseFloat((((bytes[4+i]<<8 | bytes[5+i])&0xFFF)/10).toFixed(1)); + var ee= getMyDate((bytes[7+i]<<24 | bytes[8+i]<<16 | bytes[9+i]<<8 | bytes[10+i]).toString(10)); + var string='['+bb+','+cc+','+dd+','+ee+']'+','; + + return string; +} + +function getzf(c_num){ + if(parseInt(c_num) < 10) + c_num = '0' + c_num; + + return c_num; +} + +function getMyDate(str){ + var c_Date; + if(str > 9999999999) + c_Date = new Date(parseInt(str)); + else + c_Date = new Date(parseInt(str) * 1000); + + var c_Year = c_Date.getFullYear(), + c_Month = c_Date.getMonth()+1, + c_Day = c_Date.getDate(), + c_Hour = c_Date.getHours(), + c_Min = c_Date.getMinutes(), + c_Sen = c_Date.getSeconds(); + var c_Time = c_Year +'-'+ getzf(c_Month) +'-'+ getzf(c_Day) +' '+ getzf(c_Hour) +':'+ getzf(c_Min) +':'+getzf(c_Sen); + + return c_Time; +} + +function Decode(fPort, bytes, variables) { +var Ext= bytes[6]&0x0F; +var poll_message_status=((bytes[6]>>6)&0x01); +var retransmission_Status=((bytes[6]>>7)&0x01); +var Connect=(bytes[6]&0x80)>>7; +var decode = {}; +var data = {}; +if((fPort==3)&&((bytes[2]==0x01)||(bytes[2]==0x02)||(bytes[2]==0x03)||(bytes[2]==0x04))){ + var array1=[] + var bytes1="0x" + var str1=Str1(bytes) + var str2=str1.substring(0,6) + var str3=str1.substring(6,) + var reg=/.{4}/g; + var rs=str3.match(reg); + rs.push(str3.substring(rs.join('').length)); + rs.pop() + var new_arr = [...rs] + var data1=new_arr + decode.bat=parseInt(bytes1+str2.substring(0,4)& 0x3FFF) + if (parseInt(bytes1+str2.substring(4,))==1){ + decode.sensor="ds18b20" + } + else if(parseInt(bytes1+str2.substring(4,))==2){ + decode.sensor="tmp117" + } + else if(parseInt(bytes1+str2.substring(4,))==3){ + decode.sensor="gxht30" + } + else if(parseInt(bytes1+str2.substring(4,))==4){ + decode.sensor="sht31" + } + for (var i=0;i>4&0x0f)+'.'+(bytes[2]&0x0f); + var bat= (bytes[5]<<8 | bytes[6])/1000; + + return { + SENSOR_MODEL:sensor, + FIRMWARE_VERSION:firm_ver, + FREQUENCY_BAND:freq_band, + SUB_BAND:sub_band, + BAT:bat, + }; +} +if (retransmission_Status==0) +{ +switch (poll_message_status) { +case 0: +{ +if(Ext==0x09) +{ + decode.TempC_DS=parseFloat(((bytes[0]<<24>>16 | bytes[1])/100).toFixed(2)); + decode.Bat_status=bytes[4]>>6; +} +else +{ + decode.BatV= ((bytes[0]<<8 | bytes[1]) & 0x3FFF)/1000; + decode.Bat_status=bytes[0]>>6; +} + +if(Ext!=0x0f) +{ + decode.TempC_SHT=parseFloat(((bytes[2]<<24>>16 | bytes[3])/100).toFixed(2)); + decode.Hum_SHT=parseFloat((((bytes[4]<<8 | bytes[5])&0xFFF)/10).toFixed(1)); +} +if(Connect=='1') +{ + decode.No_connect="Sensor no connection"; +} + +if(Ext=='0') +{ + decode.Ext_sensor ="No external sensor"; +} +else if(Ext=='1') +{ + decode.Ext_sensor ="Temperature Sensor"; + decode.TempC_DS=parseFloat(((bytes[7]<<24>>16 | bytes[8])/100).toFixed(2)); +} +else if(Ext=='2') +{ + decode.Ext_sensor ="Temperature Sensor"; + decode.TempC_TMP117=parseFloat(((bytes[7]<<24>>16 | bytes[8])/100).toFixed(2)); +} +else if(Ext=='4') +{ + decode.Work_mode="Interrupt Sensor send"; + decode.Exti_pin_level=bytes[7] ? "High":"Low"; + decode.Exti_status=bytes[8] ? "True":"False"; + decode.Exit_count= bytes[9]<<16 | bytes[10]<<8 | bytes[11]; + decode.Exit_duration= bytes[12]<<16 | bytes[13]<<8 | bytes[14]; +} +else if(Ext=='5') +{ + decode.Work_mode="Illumination Sensor"; + decode.ILL_lx=bytes[7]<<8 | bytes[8]; +} +else if(Ext=='6') +{ + decode.Work_mode="ADC Sensor"; + decode.ADC_V=(bytes[7]<<8 | bytes[8])/1000; +} +else if(Ext=='7') +{ + decode.Work_mode="Interrupt Sensor count"; + decode.Exit_count=bytes[7]<<8 | bytes[8]; +} +else if(Ext=='8') +{ + decode.Work_mode="Interrupt Sensor count"; + decode.Exit_count=bytes[7]<<24 | bytes[8]<<16 | bytes[9]<<8 | bytes[10]; +} +else if(Ext=='9') +{ + decode.Work_mode="DS18B20 & timestamp"; + decode.Systimestamp=(bytes[7]<<24 | bytes[8]<<16 | bytes[9]<<8 | bytes[10] ); +} +else if(Ext=='11') +{ + decode.Work_mode="SHT31 Sensor"; + decode.Ext_TempC_SHT=parseFloat(((bytes[7]<<24>>16 | bytes[8])/100).toFixed(2)); + decode.Ext_Hum_SHT=parseFloat((((bytes[9]<<8 | bytes[10])&0xFFF)/10).toFixed(1)); +} +else if(Ext=='14') +{ + decode.Work_mode="PIR Sensor"; + decode.Exti_pin_level=bytes[7] & 0x01 ? "Activity":"No activity"; + decode.Move_count=bytes[8]<<16 | bytes[9]<<8 | bytes[10]; +} +else if(Ext=='15') +{ + decode.Work_mode="DS18B20ID"; + decode.ID=str_pad(bytes[2])+str_pad(bytes[3])+str_pad(bytes[4])+str_pad(bytes[5])+str_pad(bytes[7])+str_pad(bytes[8])+str_pad(bytes[9])+str_pad(bytes[10]); +} +} + if((bytes.length==11)||(bytes.length==15)) + { + return decode; + } +break; + +case 1: + { + for(var i=0;i expectedFreq { - n.logger.Warnf( - `"configured capture frequency (%v) is greater than the frequency (%v) - of expected uplink interval for node %v: lower capture frequency to avoid duplicate data"`, - captureFreq, - expectedFreq, - n.NodeName) - } - return nil } @@ -254,6 +206,7 @@ func getGateway(ctx context.Context, deps resource.Dependencies) (sensor.Sensor, var dep resource.Resource // Assuming there's only one dep. + // TODO: expand to multiple gateways for _, val := range deps { dep = val } @@ -311,24 +264,3 @@ func (n *Node) Readings(ctx context.Context, extra map[string]interface{}) (map[ } return map[string]interface{}{}, errors.New("node does not have gateway") } - -// getCaptureFrequencyHzFromConfig extract the capture_frequency_hz from the device config. -func getCaptureFrequencyHzFromConfig(c resource.Config) (float64, error) { - var captureFreqHz float64 - var captureMethodFound bool - for _, assocResourceCfg := range c.AssociatedResourceConfigs { - if captureMethodsMapInterface := assocResourceCfg.Attributes["capture_methods"]; captureMethodsMapInterface != nil { - captureMethodFound = true - for _, captureMethodsInterface := range captureMethodsMapInterface.([]interface{}) { - captureMethods := captureMethodsInterface.(map[string]interface{}) - if captureMethods["method"].(string) == "Readings" { - captureFreqHz = captureMethods["capture_frequency_hz"].(float64) - } - } - } - } - if captureMethodFound && captureFreqHz <= 0 { - return 0.0, errors.New("zero or negative capture frequency") - } - return captureFreqHz, nil -} diff --git a/node/node_test.go b/node/node_test.go index 93a511c..91fda67 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -57,27 +57,29 @@ func TestConfigValidate(t *testing.T) { conf := &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeOTAA, + JoinType: JoinTypeOTAA, DevEUI: testDevEUI, AppKey: testAppKey, + Gateways: []string{testGatewayName}, } deps, err := conf.Validate("") test.That(t, err, test.ShouldBeNil) - test.That(t, deps, test.ShouldBeNil) + test.That(t, len(deps), test.ShouldEqual, 1) + test.That(t, deps[0], test.ShouldEqual, testGatewayName) // Test missing decoder path conf = &Config{ Interval: &testInterval, } _, err = conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errDecoderPathRequired)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrDecoderPathRequired)) // Test missing interval conf = &Config{ DecoderPath: testDecoderPath, } _, err = conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errIntervalRequired)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrIntervalRequired)) zeroInterval := 0.0 // Test zero interval @@ -86,7 +88,7 @@ func TestConfigValidate(t *testing.T) { Interval: &zeroInterval, } _, err = conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errIntervalZero)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrIntervalZero)) // Test invalid join type conf = &Config{ @@ -95,7 +97,7 @@ func TestConfigValidate(t *testing.T) { JoinType: "INVALID", } _, err = conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errInvalidJoinType)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrInvalidJoinType)) } func TestValidateOTAAAttributes(t *testing.T) { @@ -103,49 +105,49 @@ func TestValidateOTAAAttributes(t *testing.T) { conf := &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeOTAA, + JoinType: JoinTypeOTAA, AppKey: testAppKey, } _, err := conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errDevEUIRequired)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrDevEUIRequired)) // Test invalid DevEUI length conf = &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeOTAA, + JoinType: JoinTypeOTAA, DevEUI: "0123456", // Not 8 bytes AppKey: testAppKey, } _, err = conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errDevEUILength)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrDevEUILength)) // Test missing AppKey conf = &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeOTAA, + JoinType: JoinTypeOTAA, DevEUI: testDevEUI, } _, err = conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errAppKeyRequired)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrAppKeyRequired)) // Test invalid AppKey length conf = &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeOTAA, + JoinType: JoinTypeOTAA, DevEUI: testDevEUI, AppKey: "0123456", // Not 16 bytes } _, err = conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errAppKeyLength)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrAppKeyLength)) // Test valid OTAA config conf = &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeOTAA, + JoinType: JoinTypeOTAA, DevEUI: testDevEUI, AppKey: testAppKey, } @@ -158,76 +160,76 @@ func TestValidateABPAttributes(t *testing.T) { conf := &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeABP, + JoinType: JoinTypeABP, NwkSKey: testNwkSKey, DevAddr: testDevAddr, } _, err := conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errAppSKeyRequired)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrAppSKeyRequired)) // Test invalid AppSKey length conf = &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeABP, + JoinType: JoinTypeABP, AppSKey: "0123456", // Not 16 bytes NwkSKey: testNwkSKey, DevAddr: testDevAddr, } _, err = conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errAppSKeyLength)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrAppSKeyLength)) // Test missing NwkSKey conf = &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeABP, + JoinType: JoinTypeABP, AppSKey: testAppSKey, DevAddr: testDevAddr, } _, err = conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errNwkSKeyRequired)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrNwkSKeyRequired)) // Test invalid NwkSKey length conf = &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeABP, + JoinType: JoinTypeABP, AppSKey: testAppSKey, NwkSKey: "0123456", // Not 16 bytes DevAddr: testDevAddr, } _, err = conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errNwkSKeyLength)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrNwkSKeyLength)) // Test missing DevAddr conf = &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeABP, + JoinType: JoinTypeABP, AppSKey: testAppSKey, NwkSKey: testNwkSKey, } _, err = conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errDevAddrRequired)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrDevAddrRequired)) // Test invalid DevAddr length conf = &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeABP, + JoinType: JoinTypeABP, AppSKey: testAppSKey, NwkSKey: testNwkSKey, DevAddr: "0123", // Not 4 bytes } _, err = conf.Validate("") - test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", errDevAddrLength)) + test.That(t, err, test.ShouldBeError, resource.NewConfigValidationError("", ErrDevAddrLength)) // Test valid ABP config conf = &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeABP, + JoinType: JoinTypeABP, AppSKey: testAppSKey, NwkSKey: testNwkSKey, DevAddr: testDevAddr, @@ -250,7 +252,7 @@ func TestNewNode(t *testing.T) { ConvertedAttributes: &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeOTAA, + JoinType: JoinTypeOTAA, DevEUI: testDevEUI, AppKey: testAppKey, }, @@ -262,7 +264,7 @@ func TestNewNode(t *testing.T) { node := n.(*Node) test.That(t, node.NodeName, test.ShouldEqual, "test-node") - test.That(t, node.JoinType, test.ShouldEqual, joinTypeOTAA) + test.That(t, node.JoinType, test.ShouldEqual, JoinTypeOTAA) test.That(t, node.DecoderPath, test.ShouldEqual, testDecoderPath) // Test with valid ABP config @@ -271,7 +273,7 @@ func TestNewNode(t *testing.T) { ConvertedAttributes: &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeABP, + JoinType: JoinTypeABP, AppSKey: testAppSKey, NwkSKey: testNwkSKey, DevAddr: testDevAddr, @@ -284,7 +286,7 @@ func TestNewNode(t *testing.T) { node = n.(*Node) test.That(t, node.NodeName, test.ShouldEqual, "test-node-abp") - test.That(t, node.JoinType, test.ShouldEqual, joinTypeABP) + test.That(t, node.JoinType, test.ShouldEqual, JoinTypeABP) test.That(t, node.DecoderPath, test.ShouldEqual, testDecoderPath) // Verify ABP byte arrays @@ -310,7 +312,7 @@ func TestReadings(t *testing.T) { ConvertedAttributes: &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeOTAA, + JoinType: JoinTypeOTAA, DevEUI: testDevEUI, AppKey: testAppKey, }, @@ -330,7 +332,7 @@ func TestReadings(t *testing.T) { ConvertedAttributes: &Config{ DecoderPath: testDecoderPath, Interval: &testInterval, - JoinType: joinTypeOTAA, + JoinType: JoinTypeOTAA, DevEUI: testDevEUI, AppKey: testAppKey, }, diff --git a/node/node_utils.go b/node/node_utils.go new file mode 100644 index 0000000..860634c --- /dev/null +++ b/node/node_utils.go @@ -0,0 +1,157 @@ +// Package node implements the node model +package node + +import ( + "context" + "embed" + "encoding/hex" + "errors" + "os" + "path/filepath" + "time" + + "go.viam.com/rdk/logging" + "go.viam.com/rdk/resource" +) + +// NewSensor creates a new Node struct. This can be used by external implementers. +func NewSensor(conf resource.Config, logger logging.Logger) Node { + return Node{ + Named: conf.ResourceName().AsNamed(), + logger: logger, + NodeName: conf.ResourceName().AsNamed().Name().Name, + } +} + +// ReconfigureWithConfig runs the reconfigure logic of a Node using the native Node Config. +// For Specialized sensor implementations this function should be used within Reconfigure. +func (n *Node) ReconfigureWithConfig(ctx context.Context, deps resource.Dependencies, cfg *Config) error { + switch cfg.JoinType { + case "OTAA", "": + appKey, err := hex.DecodeString(cfg.AppKey) + if err != nil { + return err + } + n.AppKey = appKey + + devEui, err := hex.DecodeString(cfg.DevEUI) + if err != nil { + return err + } + n.DevEui = devEui + case "ABP": + devAddr, err := hex.DecodeString(cfg.DevAddr) + if err != nil { + return err + } + + n.Addr = devAddr + + appSKey, err := hex.DecodeString(cfg.AppSKey) + if err != nil { + return err + } + + n.AppSKey = appSKey + } + + n.DecoderPath = cfg.DecoderPath + n.JoinType = cfg.JoinType + + if n.JoinType == "" { + n.JoinType = "OTAA" + } + + gateway, err := getGateway(ctx, deps) + if err != nil { + return err + } + + cmd := make(map[string]interface{}) + + // send the device to the gateway. + cmd["register_device"] = n + + _, err = gateway.DoCommand(ctx, cmd) + if err != nil { + return err + } + + n.gateway = gateway + return nil +} + +// getCaptureFrequencyHzFromConfig extract the capture_frequency_hz from the device config. +func getCaptureFrequencyHzFromConfig(c resource.Config) (float64, error) { + var captureFreqHz float64 + var captureMethodFound bool + for _, assocResourceCfg := range c.AssociatedResourceConfigs { + if captureMethodsMapInterface := assocResourceCfg.Attributes["capture_methods"]; captureMethodsMapInterface != nil { + captureMethodFound = true + for _, captureMethodsInterface := range captureMethodsMapInterface.([]interface{}) { + captureMethods := captureMethodsInterface.(map[string]interface{}) + if captureMethods["method"].(string) == "Readings" { + captureFreqHz = captureMethods["capture_frequency_hz"].(float64) + } + } + } + } + if captureMethodFound && captureFreqHz <= 0 { + return 0.0, errors.New("zero or negative capture frequency") + } + return captureFreqHz, nil +} + +// CheckCaptureFrequency check the resource's capture frequency +// and reports to the user whether a safe value has been configured. +func CheckCaptureFrequency(c resource.Config, interval float64, logger logging.Logger) (bool, error) { + // Warn if user's configured capture frequency is more than the expected uplink interval. + captureFreq, err := getCaptureFrequencyHzFromConfig(c) + if err != nil { + return false, err + } + + intervalSeconds := (time.Duration(interval) * time.Minute).Seconds() + expectedFreq := 1 / intervalSeconds + + if captureFreq > expectedFreq { + logger.Warnf( + `"configured capture frequency (%v) is greater than the frequency (%v) + of expected uplink interval for node %v: lower capture frequency to avoid duplicate data"`, + captureFreq, + expectedFreq, + c.ResourceName().AsNamed().Name().Name) + return false, nil + } + return true, nil +} + +// WriteDecoderFile writes an embeded decoderFile into the data folder of the module. +func WriteDecoderFile(decoderFilename string, decoderFile embed.FS) (string, error) { + // Create or open the file used to save device data across restarts. + moduleDataDir := os.Getenv("VIAM_MODULE_DATA") + filePath := filepath.Join(moduleDataDir, decoderFilename) + + // check if the decoder was already written + //nolint:gosec + if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) { + // the decoder hasn't been written yet, so lets write it. + // load the decoder from the binary + decoder, err := decoderFile.ReadFile(decoderFilename) + if err != nil { + return "", err + } + file, err := os.OpenFile(filePath, os.O_RDWR|os.O_CREATE, 0o666) + if err != nil { + return "", err + } + _, err = file.Write(decoder) + if err != nil { + return "", err + } + } else if err != nil { + // an actual error happened, and the file may or may not exist. + return "", err + } + return filePath, nil +} From 832b4fa10db822b2759764b1454369852004155d Mon Sep 17 00:00:00 2001 From: John Date: Thu, 13 Feb 2025 18:12:21 -0500 Subject: [PATCH 02/11] unlint i guess --- gateway/join.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/gateway/join.go b/gateway/join.go index e50cabe..934d784 100644 --- a/gateway/join.go +++ b/gateway/join.go @@ -160,9 +160,9 @@ func (g *gateway) generateJoinAccept(ctx context.Context, jr joinRequest, d *nod payload := make([]byte, 0) payload = append(payload, 0x20) - payload = append(payload, Disa...) - payload = append(payload, le chan...) - payload = append(payload, els 48-...) + payload = append(payload, jnLE[:]...) + payload = append(payload, netIDLE[:]...) + payload = append(payload, dAddrLE[:]...) // DLSettings byte: // Bit 7: OptNeg (0) From 1001379b92c6f13610a23a4082b4462a047e94c9 Mon Sep 17 00:00:00 2001 From: John Date: Thu, 13 Feb 2025 18:14:49 -0500 Subject: [PATCH 03/11] another lint --- node/node.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/node/node.go b/node/node.go index 9d031e5..7999ede 100644 --- a/node/node.go +++ b/node/node.go @@ -34,9 +34,10 @@ var ( ) const ( - // Join types. + // JoinTypeOTAA is the OTAA Join type. JoinTypeOTAA = "OTAA" - JoinTypeABP = "ABP" + // JoinTypeABP is the ABP Join type. + JoinTypeABP = "ABP" ) var noReadings = map[string]interface{}{"": "no readings available yet"} From 02d438eee3928882efdd2f55e4642aa05144c43c Mon Sep 17 00:00:00 2001 From: John Date: Fri, 14 Feb 2025 11:44:19 -0500 Subject: [PATCH 04/11] pr comments --- draginolht65n/sensor.go | 9 ++------- meta.json | 4 ++-- node/node_utils.go | 1 - 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/draginolht65n/sensor.go b/draginolht65n/sensor.go index 949ef7e..38b12b2 100644 --- a/draginolht65n/sensor.go +++ b/draginolht65n/sensor.go @@ -84,10 +84,8 @@ func newLHT65N( return nil, err } n := &LHT65N{ - Named: conf.ResourceName().AsNamed(), - logger: logger, - // This returns the name given to the resource by user - // may be different if using remotes + Named: conf.ResourceName().AsNamed(), + logger: logger, node: node.NewSensor(conf, logger), decoderPath: decoderFilePath, } @@ -108,9 +106,6 @@ func (n *LHT65N) Reconfigure(ctx context.Context, deps resource.Dependencies, co } nodeCfg := cfg.getNodeConfig(n.decoderPath) - if len(deps) == 0 { - n.logger.Info("yo no deps") - } err = n.node.ReconfigureWithConfig(ctx, deps, &nodeCfg) if err != nil { diff --git a/meta.json b/meta.json index a8214f5..5f3a71e 100644 --- a/meta.json +++ b/meta.json @@ -15,13 +15,13 @@ "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 compoenent for a dragino LHT65N LoRaWAN node." + "short_description": "A sensor component for a dragino LHT65N LoRaWAN node." } ], "build": { diff --git a/node/node_utils.go b/node/node_utils.go index 860634c..638711b 100644 --- a/node/node_utils.go +++ b/node/node_utils.go @@ -128,7 +128,6 @@ func CheckCaptureFrequency(c resource.Config, interval float64, logger logging.L // WriteDecoderFile writes an embeded decoderFile into the data folder of the module. func WriteDecoderFile(decoderFilename string, decoderFile embed.FS) (string, error) { - // Create or open the file used to save device data across restarts. moduleDataDir := os.Getenv("VIAM_MODULE_DATA") filePath := filepath.Join(moduleDataDir, decoderFilename) From 33529e65bb1343a999dc050b78db8835e27fc814 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 14 Feb 2025 13:49:26 -0500 Subject: [PATCH 05/11] add url curl logic --- node/node_utils.go | 56 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/node/node_utils.go b/node/node_utils.go index 638711b..1993541 100644 --- a/node/node_utils.go +++ b/node/node_utils.go @@ -6,6 +6,8 @@ import ( "embed" "encoding/hex" "errors" + "io" + "net/http" "os" "path/filepath" "time" @@ -126,7 +128,7 @@ func CheckCaptureFrequency(c resource.Config, interval float64, logger logging.L return true, nil } -// WriteDecoderFile writes an embeded decoderFile into the data folder of the module. +// WriteDecoderFile writes an embedded decoderFile into the data folder of the module. func WriteDecoderFile(decoderFilename string, decoderFile embed.FS) (string, error) { moduleDataDir := os.Getenv("VIAM_MODULE_DATA") filePath := filepath.Join(moduleDataDir, decoderFilename) @@ -154,3 +156,55 @@ func WriteDecoderFile(decoderFilename string, decoderFile embed.FS) (string, err } return filePath, nil } + +// GetFileFromURL writes a decoder file from a url into the data folder of the module. +func GetFileFromURL(ctx context.Context, decoderFilename, url string, logger logging.Logger) (string, error) { + url = "https://raw.githubusercontent.com/dragino/dragino-end-node-decoder/refs/heads/main/LHT65N/LHT65N Chirpstack 4.0 decoder.txt" + + moduleDataDir := os.Getenv("VIAM_MODULE_DATA") + filePath := filepath.Join(moduleDataDir, decoderFilename) + + // check if the decoder was already written + //nolint:gosec + if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) { + // the decoder hasn't been written yet, so lets write it. + // create an http request + req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) + if err != nil { + return "", err + } + + logger.Info("Getting decoder") + httpClient := &http.Client{ + Timeout: time.Second * 25, + } + res, err := httpClient.Do(req) + if err != nil { + return "", err + } + // check that the request was successful. + if res.StatusCode != http.StatusOK { + return "", err + } + // get the decoder data. + decoderData, err := io.ReadAll(res.Body) + if err != nil { + return "", err + } + //nolint:errcheck + defer res.Body.Close() + logger.Infof("Writing decoder to file %s", filePath) + //nolint:all + err = os.WriteFile(filePath, decoderData, 0755) + if err != nil { + return "", err + } + + return filePath, nil + } else if err != nil { + // an actual error happened, and the file may or may not exist. + return "", err + } + + return filePath, nil +} From 072fbd249a8691f0aca76233c0b72aba7b06864a Mon Sep 17 00:00:00 2001 From: John Date: Fri, 14 Feb 2025 13:50:03 -0500 Subject: [PATCH 06/11] try fixing linter --- cmd/cli/cmd.go | 5 ++--- draginolht65n/sensor.go | 2 +- draginolht65n/sensor_test.go | 6 ++---- gateway/gateway.go | 2 +- gateway/gateway_test.go | 2 +- gateway/join.go | 5 ++++- gateway/join_test.go | 3 +-- gateway/uplink.go | 3 +-- gateway/uplink_test.go | 2 +- go.mod | 2 +- main.go | 7 +++---- 11 files changed, 18 insertions(+), 21 deletions(-) diff --git a/cmd/cli/cmd.go b/cmd/cli/cmd.go index 8a32a85..a1a70e7 100644 --- a/cmd/cli/cmd.go +++ b/cmd/cli/cmd.go @@ -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() { @@ -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 { diff --git a/draginolht65n/sensor.go b/draginolht65n/sensor.go index 38b12b2..7c04b44 100644 --- a/draginolht65n/sensor.go +++ b/draginolht65n/sensor.go @@ -4,8 +4,8 @@ package draginolht65n import ( "context" "embed" - "gateway/node" + "github.com/viam-modules/gateway/node" "go.viam.com/rdk/components/sensor" "go.viam.com/rdk/logging" "go.viam.com/rdk/resource" diff --git a/draginolht65n/sensor_test.go b/draginolht65n/sensor_test.go index 66aff2d..ed2ce8d 100644 --- a/draginolht65n/sensor_test.go +++ b/draginolht65n/sensor_test.go @@ -1,9 +1,9 @@ package draginolht65n import ( - "gateway/node" "testing" + "github.com/viam-modules/gateway/node" "go.viam.com/test" ) @@ -16,9 +16,7 @@ const ( testGatewayName = "gateway" ) -var ( - testInterval = 5.0 -) +var testInterval = 5.0 func TestConfigValidate(t *testing.T) { // valid config diff --git a/gateway/gateway.go b/gateway/gateway.go index 43ddee8..3e59aaf 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -18,7 +18,6 @@ import ( "encoding/hex" "errors" "fmt" - "gateway/node" "os" "path/filepath" "strconv" @@ -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" diff --git a/gateway/gateway_test.go b/gateway/gateway_test.go index ef1a504..e75951c 100644 --- a/gateway/gateway_test.go +++ b/gateway/gateway_test.go @@ -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" diff --git a/gateway/join.go b/gateway/join.go index 934d784..6ba881e 100644 --- a/gateway/join.go +++ b/gateway/join.go @@ -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 payload = append(payload, dAddrLE[:]...) // DLSettings byte: diff --git a/gateway/join_test.go b/gateway/join_test.go index ea76e07..bc8785c 100644 --- a/gateway/join_test.go +++ b/gateway/join_test.go @@ -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" diff --git a/gateway/uplink.go b/gateway/uplink.go index 2ed32aa..6b5fa9c 100644 --- a/gateway/uplink.go +++ b/gateway/uplink.go @@ -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" ) diff --git a/gateway/uplink_test.go b/gateway/uplink_test.go index 2687a60..8c3db0a 100644 --- a/gateway/uplink_test.go +++ b/gateway/uplink_test.go @@ -2,9 +2,9 @@ package gateway import ( "context" - "gateway/node" "testing" + "github.com/viam-modules/gateway/node" "go.viam.com/rdk/logging" "go.viam.com/test" ) diff --git a/go.mod b/go.mod index ba9422c..e28702b 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module gateway +module github.com/viam-modules/gateway go 1.23 diff --git a/main.go b/main.go index 98129a3..9866272 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,9 @@ package main import ( - "gateway/draginolht65n" - "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" From d3e09c521b0f46d3d53725e4e178cc00b68075e1 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 14 Feb 2025 15:03:56 -0500 Subject: [PATCH 07/11] use http and add tests for http --- draginolht65n/sensor.go | 18 ++++++--- node/node.go | 2 + node/node_test.go | 88 +++++++++++++++++++++++++++++++++++++++++ node/node_utils.go | 16 +++----- 4 files changed, 108 insertions(+), 16 deletions(-) diff --git a/draginolht65n/sensor.go b/draginolht65n/sensor.go index 7c04b44..2072a47 100644 --- a/draginolht65n/sensor.go +++ b/draginolht65n/sensor.go @@ -3,7 +3,8 @@ package draginolht65n import ( "context" - "embed" + "net/http" + "time" "github.com/viam-modules/gateway/node" "go.viam.com/rdk/components/sensor" @@ -11,10 +12,11 @@ import ( "go.viam.com/rdk/resource" ) -//go:embed LHT65NChirpstack4decoder.js -var decoderFile embed.FS - -const decoderFilename = "LHT65NChirpstack4decoder.js" +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 = resource.NewModel("viam", "lorawan", "dragino-LHT65N") @@ -79,10 +81,14 @@ func newLHT65N( conf resource.Config, logger logging.Logger, ) (sensor.Sensor, error) { - decoderFilePath, err := node.WriteDecoderFile(decoderFilename, decoderFile) + httpClient := &http.Client{ + Timeout: time.Second * 25, + } + decoderFilePath, err := node.WriteDecoderFileFromURL(ctx, decoderFilename, decoderURL, httpClient, logger) if err != nil { return nil, err } + n := &LHT65N{ Named: conf.ResourceName().AsNamed(), logger: logger, diff --git a/node/node.go b/node/node.go index 7999ede..b4f6625 100644 --- a/node/node.go +++ b/node/node.go @@ -31,6 +31,8 @@ var ( ErrNwkSKeyLength = errors.New("network session key must be 16 bytes") ErrDevAddrRequired = errors.New("device address is required for ABP join type") ErrDevAddrLength = errors.New("device address must be 4 bytes") + ErrBadDecoderURL = "Error Retreiving decoder url is invalid, please report to maintainer: " + + "Status Code %v" ) const ( diff --git a/node/node_test.go b/node/node_test.go index 91fda67..b7236f3 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -3,7 +3,12 @@ package node import ( "context" "encoding/hex" + "fmt" + "net/http" + "net/http/httptest" + "os" "testing" + "time" "go.viam.com/rdk/components/encoder" "go.viam.com/rdk/data" @@ -356,3 +361,86 @@ func TestReadings(t *testing.T) { test.That(t, err, test.ShouldBeNil) test.That(t, readings, test.ShouldResemble, noReadings) } + +type ctrl struct { + statusCode int + response string +} + +func (c *ctrl) mockHandler(w http.ResponseWriter, r *http.Request) { + resp := []byte(c.response) + + w.WriteHeader(c.statusCode) + w.Write(resp) +} + +// HTTPMock creates a mock HTTP server. +func HTTPMock(pattern string, statusCode int, response string) *httptest.Server { + c := &ctrl{statusCode, response} + + handler := http.NewServeMux() + handler.HandleFunc(pattern, c.mockHandler) + + return httptest.NewServer(handler) +} + +func TestWriteDecoder(t *testing.T) { + logger := logging.NewTestLogger(t) + // Prep first run directory + dataDirectory1 := t.TempDir() + + t.Setenv("VIAM_MODULE_DATA", dataDirectory1) + + t.Run("Test successful request of decoder", func(t *testing.T) { + resp := "good test" + srv := HTTPMock("/myurl", http.StatusOK, resp) + sClient := &http.Client{ + Timeout: time.Second * 180, + } + decoderName1 := "decoder1.js" + + fileName1, err := WriteDecoderFileFromURL(context.Background(), decoderName1, srv.URL+"/myurl", sClient, logger) + test.That(t, err, test.ShouldBeNil) + test.That(t, fileName1, test.ShouldContainSubstring, decoderName1) + file1, err := os.ReadFile(fileName1) + test.That(t, err, test.ShouldBeNil) + test.That(t, string(file1), test.ShouldEqual, resp) + // second decoder + + resp2 := "good test 2" + srv2 := HTTPMock("/myurl", http.StatusOK, resp2) + sClient2 := &http.Client{ + Timeout: time.Second * 180, + } + decoderName2 := "decoder2.js" + + fileName2, err := WriteDecoderFileFromURL(context.Background(), decoderName2, srv2.URL+"/myurl", sClient2, logger) + test.That(t, err, test.ShouldBeNil) + test.That(t, fileName2, test.ShouldContainSubstring, decoderName2) + file2, err := os.ReadFile(fileName2) + test.That(t, err, test.ShouldBeNil) + test.That(t, string(file2), test.ShouldEqual, resp2) + dirEntries, err := os.ReadDir(dataDirectory1) + test.That(t, err, test.ShouldBeNil) + test.That(t, len(dirEntries), test.ShouldEqual, 2) + + // repeat decoder + _, err = WriteDecoderFileFromURL(context.Background(), decoderName1, srv.URL+"/myurl", sClient, logger) + test.That(t, err, test.ShouldBeNil) + dirEntries, err = os.ReadDir(dataDirectory1) + test.That(t, err, test.ShouldBeNil) + test.That(t, len(dirEntries), test.ShouldEqual, 2) + }) + + t.Run("Test failed request of decoder", func(t *testing.T) { + resp := "bad test" + srv := HTTPMock("/myurl", http.StatusNotFound, resp) + sClient := &http.Client{ + Timeout: time.Second * 180, + } + decoderBad := "decoder3.js" + + _, err := WriteDecoderFileFromURL(context.Background(), decoderBad, srv.URL+"/myurl", sClient, logger) + test.That(t, err, test.ShouldBeError, fmt.Errorf(ErrBadDecoderURL, 404)) + }) +} diff --git a/node/node_utils.go b/node/node_utils.go index 1993541..a6a69df 100644 --- a/node/node_utils.go +++ b/node/node_utils.go @@ -6,6 +6,7 @@ import ( "embed" "encoding/hex" "errors" + "fmt" "io" "net/http" "os" @@ -157,10 +158,8 @@ func WriteDecoderFile(decoderFilename string, decoderFile embed.FS) (string, err return filePath, nil } -// GetFileFromURL writes a decoder file from a url into the data folder of the module. -func GetFileFromURL(ctx context.Context, decoderFilename, url string, logger logging.Logger) (string, error) { - url = "https://raw.githubusercontent.com/dragino/dragino-end-node-decoder/refs/heads/main/LHT65N/LHT65N Chirpstack 4.0 decoder.txt" - +// WriteDecoderFileFromURL writes a decoder file from a url into the data folder of the module. +func WriteDecoderFileFromURL(ctx context.Context, decoderFilename, url string, httpClient *http.Client, logger logging.Logger) (string, error) { moduleDataDir := os.Getenv("VIAM_MODULE_DATA") filePath := filepath.Join(moduleDataDir, decoderFilename) @@ -174,17 +173,13 @@ func GetFileFromURL(ctx context.Context, decoderFilename, url string, logger log return "", err } - logger.Info("Getting decoder") - httpClient := &http.Client{ - Timeout: time.Second * 25, - } res, err := httpClient.Do(req) if err != nil { return "", err } // check that the request was successful. if res.StatusCode != http.StatusOK { - return "", err + return "", fmt.Errorf(ErrBadDecoderURL, res.StatusCode) } // get the decoder data. decoderData, err := io.ReadAll(res.Body) @@ -193,7 +188,8 @@ func GetFileFromURL(ctx context.Context, decoderFilename, url string, logger log } //nolint:errcheck defer res.Body.Close() - logger.Infof("Writing decoder to file %s", filePath) + + logger.Debugf("Writing decoder to file %s", filePath) //nolint:all err = os.WriteFile(filePath, decoderData, 0755) if err != nil { From 1e54eca334703c991df397f23e9d8a6ba7edcfd1 Mon Sep 17 00:00:00 2001 From: John Date: Fri, 14 Feb 2025 15:05:14 -0500 Subject: [PATCH 08/11] lint --- node/node_utils.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/node/node_utils.go b/node/node_utils.go index a6a69df..5efc9e4 100644 --- a/node/node_utils.go +++ b/node/node_utils.go @@ -159,7 +159,9 @@ func WriteDecoderFile(decoderFilename string, decoderFile embed.FS) (string, err } // WriteDecoderFileFromURL writes a decoder file from a url into the data folder of the module. -func WriteDecoderFileFromURL(ctx context.Context, decoderFilename, url string, httpClient *http.Client, logger logging.Logger) (string, error) { +func WriteDecoderFileFromURL(ctx context.Context, decoderFilename, url string, + httpClient *http.Client, logger logging.Logger, +) (string, error) { moduleDataDir := os.Getenv("VIAM_MODULE_DATA") filePath := filepath.Join(moduleDataDir, decoderFilename) From 216808201beb259feaa040198094462a31a44c3d Mon Sep 17 00:00:00 2001 From: John Date: Fri, 14 Feb 2025 15:06:13 -0500 Subject: [PATCH 09/11] remove decoder to prefer url --- draginolht65n/LHT65NChirpstack4decoder.js | 332 ---------------------- 1 file changed, 332 deletions(-) delete mode 100644 draginolht65n/LHT65NChirpstack4decoder.js diff --git a/draginolht65n/LHT65NChirpstack4decoder.js b/draginolht65n/LHT65NChirpstack4decoder.js deleted file mode 100644 index 24932ca..0000000 --- a/draginolht65n/LHT65NChirpstack4decoder.js +++ /dev/null @@ -1,332 +0,0 @@ -function decodeUplink(input) { - return { - data: Decode(input.fPort, input.bytes, input.variables) - }; -} - -function Str1(str2){ - var str3 =""; - for (var i=0;i>16 | bytes[1+i])/100).toFixed(2)); -} - else if(Ext=='2') -{ - bb=parseFloat(((bytes[0+i]<<24>>16 | bytes[1+i])/100).toFixed(2)); -} - else if(Ext=='4') -{ - var Exti_pin_level=bytes[0+i] ? "High":"Low"; - var Exti_status=bytes[1+i] ? "True":"False"; - bb=Exti_pin_level+Exti_status; -} - else if(Ext=='5') -{ - bb=bytes[0+i]<<8 | bytes[1+i]; -} - else if(Ext=='6') -{ - bb=(bytes[0+i]<<8 | bytes[1+i])/1000; -} - else if(Ext=='7') -{ - bb=bytes[0+i]<<8 | bytes[1+i]; -} - else if((Ext=='8')||(Ext=='14')) -{ - bb=bytes[0+i]<<8 | bytes[1+i]; -} -else if(Ext=='11') -{ - bb=parseFloat(((bytes[0+i]<<24>>16 | bytes[1+i])/100).toFixed(2)); -} - var cc= parseFloat(((bytes[2+i]<<24>>16 | bytes[3+i])/100).toFixed(2)); - var dd= parseFloat((((bytes[4+i]<<8 | bytes[5+i])&0xFFF)/10).toFixed(1)); - var ee= getMyDate((bytes[7+i]<<24 | bytes[8+i]<<16 | bytes[9+i]<<8 | bytes[10+i]).toString(10)); - var string='['+bb+','+cc+','+dd+','+ee+']'+','; - - return string; -} - -function getzf(c_num){ - if(parseInt(c_num) < 10) - c_num = '0' + c_num; - - return c_num; -} - -function getMyDate(str){ - var c_Date; - if(str > 9999999999) - c_Date = new Date(parseInt(str)); - else - c_Date = new Date(parseInt(str) * 1000); - - var c_Year = c_Date.getFullYear(), - c_Month = c_Date.getMonth()+1, - c_Day = c_Date.getDate(), - c_Hour = c_Date.getHours(), - c_Min = c_Date.getMinutes(), - c_Sen = c_Date.getSeconds(); - var c_Time = c_Year +'-'+ getzf(c_Month) +'-'+ getzf(c_Day) +' '+ getzf(c_Hour) +':'+ getzf(c_Min) +':'+getzf(c_Sen); - - return c_Time; -} - -function Decode(fPort, bytes, variables) { -var Ext= bytes[6]&0x0F; -var poll_message_status=((bytes[6]>>6)&0x01); -var retransmission_Status=((bytes[6]>>7)&0x01); -var Connect=(bytes[6]&0x80)>>7; -var decode = {}; -var data = {}; -if((fPort==3)&&((bytes[2]==0x01)||(bytes[2]==0x02)||(bytes[2]==0x03)||(bytes[2]==0x04))){ - var array1=[] - var bytes1="0x" - var str1=Str1(bytes) - var str2=str1.substring(0,6) - var str3=str1.substring(6,) - var reg=/.{4}/g; - var rs=str3.match(reg); - rs.push(str3.substring(rs.join('').length)); - rs.pop() - var new_arr = [...rs] - var data1=new_arr - decode.bat=parseInt(bytes1+str2.substring(0,4)& 0x3FFF) - if (parseInt(bytes1+str2.substring(4,))==1){ - decode.sensor="ds18b20" - } - else if(parseInt(bytes1+str2.substring(4,))==2){ - decode.sensor="tmp117" - } - else if(parseInt(bytes1+str2.substring(4,))==3){ - decode.sensor="gxht30" - } - else if(parseInt(bytes1+str2.substring(4,))==4){ - decode.sensor="sht31" - } - for (var i=0;i>4&0x0f)+'.'+(bytes[2]&0x0f); - var bat= (bytes[5]<<8 | bytes[6])/1000; - - return { - SENSOR_MODEL:sensor, - FIRMWARE_VERSION:firm_ver, - FREQUENCY_BAND:freq_band, - SUB_BAND:sub_band, - BAT:bat, - }; -} -if (retransmission_Status==0) -{ -switch (poll_message_status) { -case 0: -{ -if(Ext==0x09) -{ - decode.TempC_DS=parseFloat(((bytes[0]<<24>>16 | bytes[1])/100).toFixed(2)); - decode.Bat_status=bytes[4]>>6; -} -else -{ - decode.BatV= ((bytes[0]<<8 | bytes[1]) & 0x3FFF)/1000; - decode.Bat_status=bytes[0]>>6; -} - -if(Ext!=0x0f) -{ - decode.TempC_SHT=parseFloat(((bytes[2]<<24>>16 | bytes[3])/100).toFixed(2)); - decode.Hum_SHT=parseFloat((((bytes[4]<<8 | bytes[5])&0xFFF)/10).toFixed(1)); -} -if(Connect=='1') -{ - decode.No_connect="Sensor no connection"; -} - -if(Ext=='0') -{ - decode.Ext_sensor ="No external sensor"; -} -else if(Ext=='1') -{ - decode.Ext_sensor ="Temperature Sensor"; - decode.TempC_DS=parseFloat(((bytes[7]<<24>>16 | bytes[8])/100).toFixed(2)); -} -else if(Ext=='2') -{ - decode.Ext_sensor ="Temperature Sensor"; - decode.TempC_TMP117=parseFloat(((bytes[7]<<24>>16 | bytes[8])/100).toFixed(2)); -} -else if(Ext=='4') -{ - decode.Work_mode="Interrupt Sensor send"; - decode.Exti_pin_level=bytes[7] ? "High":"Low"; - decode.Exti_status=bytes[8] ? "True":"False"; - decode.Exit_count= bytes[9]<<16 | bytes[10]<<8 | bytes[11]; - decode.Exit_duration= bytes[12]<<16 | bytes[13]<<8 | bytes[14]; -} -else if(Ext=='5') -{ - decode.Work_mode="Illumination Sensor"; - decode.ILL_lx=bytes[7]<<8 | bytes[8]; -} -else if(Ext=='6') -{ - decode.Work_mode="ADC Sensor"; - decode.ADC_V=(bytes[7]<<8 | bytes[8])/1000; -} -else if(Ext=='7') -{ - decode.Work_mode="Interrupt Sensor count"; - decode.Exit_count=bytes[7]<<8 | bytes[8]; -} -else if(Ext=='8') -{ - decode.Work_mode="Interrupt Sensor count"; - decode.Exit_count=bytes[7]<<24 | bytes[8]<<16 | bytes[9]<<8 | bytes[10]; -} -else if(Ext=='9') -{ - decode.Work_mode="DS18B20 & timestamp"; - decode.Systimestamp=(bytes[7]<<24 | bytes[8]<<16 | bytes[9]<<8 | bytes[10] ); -} -else if(Ext=='11') -{ - decode.Work_mode="SHT31 Sensor"; - decode.Ext_TempC_SHT=parseFloat(((bytes[7]<<24>>16 | bytes[8])/100).toFixed(2)); - decode.Ext_Hum_SHT=parseFloat((((bytes[9]<<8 | bytes[10])&0xFFF)/10).toFixed(1)); -} -else if(Ext=='14') -{ - decode.Work_mode="PIR Sensor"; - decode.Exti_pin_level=bytes[7] & 0x01 ? "Activity":"No activity"; - decode.Move_count=bytes[8]<<16 | bytes[9]<<8 | bytes[10]; -} -else if(Ext=='15') -{ - decode.Work_mode="DS18B20ID"; - decode.ID=str_pad(bytes[2])+str_pad(bytes[3])+str_pad(bytes[4])+str_pad(bytes[5])+str_pad(bytes[7])+str_pad(bytes[8])+str_pad(bytes[9])+str_pad(bytes[10]); -} -} - if((bytes.length==11)||(bytes.length==15)) - { - return decode; - } -break; - -case 1: - { - for(var i=0;i Date: Fri, 14 Feb 2025 15:13:50 -0500 Subject: [PATCH 10/11] use model family --- draginolht65n/sensor.go | 2 +- gateway/gateway.go | 2 +- node/node.go | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/draginolht65n/sensor.go b/draginolht65n/sensor.go index 2072a47..cbbef93 100644 --- a/draginolht65n/sensor.go +++ b/draginolht65n/sensor.go @@ -19,7 +19,7 @@ const ( ) // Model represents a dragino-LHT65N lorawan node model. -var Model = resource.NewModel("viam", "lorawan", "dragino-LHT65N") +var Model = node.LorawanFamily.WithModel("dragino-LHT65N") // Config defines the dragino-LHT65N's config. type Config struct { diff --git a/gateway/gateway.go b/gateway/gateway.go index 3e59aaf..e389f07 100644 --- a/gateway/gateway.go +++ b/gateway/gateway.go @@ -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 diff --git a/node/node.go b/node/node.go index b4f6625..a357fce 100644 --- a/node/node.go +++ b/node/node.go @@ -11,8 +11,11 @@ import ( "go.viam.com/rdk/resource" ) +// LorawanFamily is the model family for the Lorawan module. +var LorawanFamily = resource.NewModelFamily("viam", "lorawan") + // Model represents a lorawan node model. -var Model = resource.NewModel("viam", "lorawan", "node") +var Model = LorawanFamily.WithModel("node") // Error variables for validation. var ( From a06baea3a8e8c41fac2e72f097999362f8bdaa0a Mon Sep 17 00:00:00 2001 From: John Date: Fri, 14 Feb 2025 15:36:05 -0500 Subject: [PATCH 11/11] remove unused bool --- draginolht65n/sensor.go | 2 +- node/node.go | 2 +- node/node_utils.go | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/draginolht65n/sensor.go b/draginolht65n/sensor.go index cbbef93..7ccaa7f 100644 --- a/draginolht65n/sensor.go +++ b/draginolht65n/sensor.go @@ -118,7 +118,7 @@ func (n *LHT65N) Reconfigure(ctx context.Context, deps resource.Dependencies, co return err } - _, err = node.CheckCaptureFrequency(conf, *cfg.Interval, n.logger) + err = node.CheckCaptureFrequency(conf, *cfg.Interval, n.logger) if err != nil { return err } diff --git a/node/node.go b/node/node.go index a357fce..645171c 100644 --- a/node/node.go +++ b/node/node.go @@ -196,7 +196,7 @@ func (n *Node) Reconfigure(ctx context.Context, deps resource.Dependencies, conf return err } - _, err = CheckCaptureFrequency(conf, *cfg.Interval, n.logger) + err = CheckCaptureFrequency(conf, *cfg.Interval, n.logger) if err != nil { return err } diff --git a/node/node_utils.go b/node/node_utils.go index 5efc9e4..f7f4027 100644 --- a/node/node_utils.go +++ b/node/node_utils.go @@ -107,11 +107,11 @@ func getCaptureFrequencyHzFromConfig(c resource.Config) (float64, error) { // CheckCaptureFrequency check the resource's capture frequency // and reports to the user whether a safe value has been configured. -func CheckCaptureFrequency(c resource.Config, interval float64, logger logging.Logger) (bool, error) { +func CheckCaptureFrequency(c resource.Config, interval float64, logger logging.Logger) error { // Warn if user's configured capture frequency is more than the expected uplink interval. captureFreq, err := getCaptureFrequencyHzFromConfig(c) if err != nil { - return false, err + return err } intervalSeconds := (time.Duration(interval) * time.Minute).Seconds() @@ -124,9 +124,9 @@ func CheckCaptureFrequency(c resource.Config, interval float64, logger logging.L captureFreq, expectedFreq, c.ResourceName().AsNamed().Name().Name) - return false, nil + return nil } - return true, nil + return nil } // WriteDecoderFile writes an embedded decoderFile into the data folder of the module.