forked from TheThingsNetwork/lorawan-devices
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
4de6b53
commit 9e89f4f
Showing
6 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
endDevices: | ||
# Unique identifier of the end device (lowercase, alphanumeric with dashes, max 36 characters) | ||
- sfm1x # look in sfm1x.yaml for the end device definition |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# Whether the end device supports class B | ||
supportsClassB: false | ||
# Whether the end device supports class C | ||
supportsClassC: false | ||
# LoRaWAN MAC version: 1.0, 1.0.1, 1.0.2, 1.0.3, 1.0.4 or 1.1 | ||
macVersion: 1.0.2 | ||
# LoRaWAN Regional Parameters version. Values depend on the LoRaWAN version: | ||
# 1.0: TS001-1.0 | ||
# 1.0.1: TS001-1.0.1 | ||
# 1.0.2: RP001-1.0.2 or RP001-1.0.2-RevB | ||
# 1.0.3: RP001-1.0.3-RevA | ||
# 1.0.4: RP002-1.0.0 or RP002-1.0.1 | ||
# 1.1: RP001-1.1-RevA or RP001-1.1-RevB | ||
regionalParametersVersion: RP001-1.0.2 | ||
# Whether the end device supports join (OTAA) or not (ABP) | ||
supportsJoin: true |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
name: SFM1x | ||
description: The SFM1x Sap Flow Meter enables individual tree water use and health to be monitored in real time. This is because the SFM has integrated data transmission direct to cloud using IoT/LTE-M Cat-M1. The SFM1x Sap Flow Meter is a discrete standalone instrument based upon the Heat Ratio Method. This measurement principle has proven to be a robust and flexible technique to measure plant water use; being able to measure high, low, zero and reverse flows in a large range of plant anatomies & species from herbaceous to woody, and stem sizes > 10 mm in diameter. The theoretical basis and ratio metric design of the Heat Ratio Method makes possible the measurement of high, low, zero and reverse flows. The SFM1x Sap Flow Meter consists of two temperature sensing needles arranged equidistance above and below a central heater. These needles are inserted into the water conducting tissue of the plant by drilling 3 small parallel holes. Heat is then pulsed every 10 minutes into the water conducting tissue of the plant. The heat is used as a tracer to directly measure the velocity of water movement in the plant stem. | ||
|
||
hardwareVersions: | ||
- version: '1.0' | ||
numeric: 1 | ||
|
||
firmwareVersions: | ||
- version: '1.1' | ||
numeric: 1 | ||
hardwareVersions: | ||
- '1.0' | ||
profiles: | ||
US902-928: | ||
id: sfm1x-profile-us915 | ||
lorawanCertified: false | ||
codec: sfm1x_r1-1_codec | ||
- version: '3.1' | ||
numeric: 2 | ||
hardwareVersions: | ||
- '1.0' | ||
profiles: | ||
US902-928: | ||
id: sfm1x-profile-us915 | ||
lorawanCertified: false | ||
codec: sfm1x_r3-1_codec | ||
|
||
additionalRadios: | ||
- ble #bluetooth low engergy | ||
- cellular | ||
|
||
keyProgramming: | ||
- serial #the user has a serial interface to set the keys | ||
|
||
firmwareProgramming: | ||
- serial #the user has a serial interface to update the firmware | ||
|
||
productURL: https://ictinternational.com/product/sfm1x-sap-flow-meter/ | ||
dataSheetURL: https://example.org/wind-sensor/datasheet.pdfhttps://ictinternational.com/manuals-and-brochures/sfm1x-sap-flow-meter/ | ||
|
||
# Commercial information | ||
resellerURLs: | ||
- name: 'ICT International' | ||
region: | ||
- United States | ||
url: https://ictinternational.com/ | ||
|
||
photos: | ||
main: sfm1x.png |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
/* SFM1x Reduced Packet Decoder Base to be used with lorawan listener plugin | ||
Includes Base Diagnostic and Switchable Result Packet | ||
*/ | ||
|
||
// Structure Type Define, "nested" or "flat" | ||
var TYPE = "nested"; | ||
|
||
/*Class Buffer | ||
Purpose: A psuedo buffer class for accessing packet data, | ||
allows for uniformity between decoder types | ||
*/ | ||
function Buf(buf){this.pl=buf; this.length=this.pl.length;} | ||
Buf.prototype.readUInt8=function(ofs){return ((this.pl[ofs]<<24)>>>24);}; | ||
Buf.prototype.readUInt16BE=function(ofs){return ((this.pl[ofs++]<<24|this.pl[ofs++]<<16)>>>16);}; | ||
Buf.prototype.readUInt32BE=function(ofs){return ((this.pl[ofs++]<<24|this.pl[ofs++]<<16|this.pl[ofs++]<<8|this.pl[ofs++])>>>0);}; | ||
Buf.prototype.readUInt16LE=function(ofs){return ((this.pl[ofs+1]<<24|this.pl[ofs--]<<16)>>>16);}; | ||
Buf.prototype.readUInt32LE=function(ofs){return ((this.pl[ofs+3]<<24|this.pl[ofs+2]<<16|this.pl[ofs+1]<<8|this.pl[ofs])>>>0);}; | ||
Buf.prototype.readInt8=function(ofs){return ((this.pl[ofs]<<24)>>24);}; | ||
Buf.prototype.readInt16BE=function(ofs){return ((this.pl[ofs++]<<24|this.pl[ofs++]<<16)>>16);}; | ||
Buf.prototype.readInt32BE=function(ofs){return ((this.pl[ofs++]<<24|this.pl[ofs++]<<16|this.pl[ofs++]<<8|this.pl[ofs++])>>0);}; | ||
Buf.prototype.readInt16LE=function(ofs){return ((this.pl[ofs+1]<<24|this.pl[ofs--]<<16)>>16);}; | ||
Buf.prototype.readInt32LE=function(ofs){return ((this.pl[ofs+3]<<24|this.pl[ofs+2]<<16|this.pl[ofs+1]<<8|this.pl[ofs])>>0);}; | ||
Buf.prototype.readFloatBE=function(ofs){return B2Fl(this.readUInt32BE(ofs));}; | ||
Buf.prototype.readFloatLE=function(ofs){return B2Fl(this.readUInt32LE(ofs));}; | ||
Buf.prototype.slice=function(s,e){return this.pl.slice(s,e);}; | ||
Buf.prototype.length=function(){return this.pl.length;}; | ||
|
||
/*Function Bytes2Float32(bytes) | ||
Purpose: Decodes an array of bytes(len 4(32 bit) into a float. | ||
Args: bytes - an array of bytes, 4 bytes long | ||
Returns: 32bit Float representation | ||
*/ | ||
function B2Fl(b){ | ||
var sign =(b>>31)?-1:1; | ||
var exp=((b>>23)&0xFF)-127; | ||
var sig=(b&~(-1<<23)); | ||
if(exp==128) return sign*((sig)?Number.NaN:Number.POSITIVE_INFINITY); | ||
if(exp==-127){ | ||
if(sig===0) return sign*0.0; | ||
exp=-126; | ||
sig/=(1<<22); | ||
} else sig=(sig|(1<<23))/(1<<23); | ||
return sign*sig*Math.pow(2,exp); | ||
} | ||
|
||
/*Function buildNested(a) - SAGE version | ||
Purpose: Takes an array and parses them into a clean and succinct object of nested parameter sets | ||
Args: a - An array of arrays containing Parameter Sets | ||
Returns: An Object containing nested Parameter Sets | ||
*/ | ||
function buildNested(a){ | ||
var exc=["main","diagnostic","downlink","device_info","unknown"]; | ||
var ret=[]; | ||
for(var el in a){ | ||
var e=a[el]; | ||
var par={}; | ||
par['name']=e[0]; | ||
par['channelId']=e[1]; | ||
par['value']=e[2]; | ||
var e_length = e.length; | ||
if(e_length>3&&exc.indexOf(e[3])<0) par['source']=e[3]; | ||
if(e_length>4) par['unit']=e[4]; | ||
if(e_length>5) par['address']=e[5]; | ||
ret.push(par); | ||
} return ret;} | ||
|
||
/*Function buildFlat(a) | ||
Purpose: Takes an array and parses them into a clean and succinct object of flat parameters | ||
Args: a - An array of arrays containing Parameter Sets | ||
Returns: An Object containing nested Parameter Sets | ||
*/ | ||
function buildFlat(a){ | ||
var exc=["main","diagnostic","downlink","device_info","unknown"]; | ||
var ret={}; | ||
for(var el in a){ | ||
var e=a[el]; | ||
var label = ''; | ||
if(e.length==6){ | ||
e[0] = e[0]+e[5]+'_'; | ||
} | ||
if(e.length<=4){ | ||
label=(exc.indexOf(e[3])<0) ? (e[0]+e[1]) : (e[0]); | ||
} else{ | ||
label=(exc.indexOf(e[3])<0) ? (e[0]+e[1]+'_'+e[4]) : (e[0]+'_'+e[4]); | ||
|
||
} ret[label]=e[2]; | ||
} return ret;} | ||
|
||
//Function - Decode, Wraps the primary decoder function for Chirpstack - SAGE version | ||
function decodeUplink(input){ | ||
var buf = new Buf(input.bytes); | ||
var decoded = {}; | ||
var readingsArr = primaryDecoder(buf, input.fPort); | ||
if(TYPE == "flat"){ decoded['measurements'] = buildFlat(readingsArr); } | ||
else decoded['measurements'] = buildNested(readingsArr); | ||
|
||
return {data:decoded}; | ||
} | ||
|
||
/*Function primaryDecoder | ||
Purpose: Main Entry point of TTN Console Decoder | ||
Args: bytes - An array of bytes from LoRaWan raw payload(Hex Represented) | ||
port - LoRaWan Port that the message came through(set by Definium firmware) | ||
Returns: decoded - An object with data fields as decoded parameter values | ||
*/ | ||
function primaryDecoder(buf,p){ | ||
var arr = []; | ||
var byte = 0; | ||
|
||
//Data Packet Recieved | ||
if (p == 1){ | ||
var src = "main"; | ||
arr.push(["packet-type", 0, "DATA_PACKET", src]); | ||
arr.push(["uncorrected-outer", 0, +(buf.readFloatLE(byte).toFixed(3)), src, "cm/hr"]); | ||
arr.push(["uncorrected-inner", 0, +(buf.readFloatLE(byte=byte+4).toFixed(3)), src, "cm/hr"]); | ||
arr.push(["corrected-outer", 0, +(buf.readFloatLE(byte=byte+4).toFixed(3)), src, "cm/hr"]); | ||
arr.push(["corrected-inner", 0, +(buf.readFloatLE(byte=byte+4).toFixed(3)), src, "cm/hr"]); | ||
arr.push(["battery-voltage", 0, +((buf.readUInt16LE(byte=byte+4)/100).toFixed(2)), src, "V"]); | ||
arr.push(["external-power-supply-voltage", 0, +((buf.readUInt16LE(byte=byte+2)/100).toFixed(2)), src, "V"]); | ||
} else if(p == 10){ | ||
var src = "device_info"; | ||
arr.push(["packet-type", 0,"DEVICE_INFO","main"]); | ||
|
||
var fw_main = buf.readInt16LE(byte).toString(); | ||
fw_main = ('R'+fw_main[0]+'-'+fw_main.slice(1,3).replace(/^(0)?/,'')+'-'+fw_main.slice(3,fw_main.length).replace(/^(0)?/,'')); | ||
arr.push(["firmware-mainboard",0,fw_main,src]); | ||
|
||
var fw_ucmi = buf.readInt16LE(byte=byte+2).toString(); | ||
var region = ['a','e','c','s','u']; | ||
var ucmi_reg = region[+(fw_ucmi[fw_ucmi.length-1])]; | ||
fw_ucmi = ('R'+fw_ucmi[0]+'-'+fw_ucmi[1]+'-'+fw_ucmi.slice(2,4).replace(/^(0)?/, '')+ucmi_reg); | ||
arr.push(["firmware-ucmi",0, fw_ucmi,src]); | ||
|
||
arr.push(["vs-factor",0,+(((buf.readInt16LE(byte=byte+2))/1000).toFixed(4)),src]); | ||
arr.push(["outer-area",0, +(((buf.readInt16LE(byte=byte+2))/1000).toFixed(3)),src, "cm^2"]); | ||
arr.push(["inner-area",0,+(((buf.readInt16LE(byte=byte+2))/1000).toFixed(3)),src, "cm^2"]); | ||
} else if(p === 100){ | ||
//Downlink Response Packet Recieved | ||
arr.push(["packet-type", 0, "DOWNLINK_RESPONSE", "main"]); | ||
arr.push(["downlink-response", 0, String.fromCharCode.apply(String, buf.slice(0, buf.length)), "downlink"]); | ||
} else{ | ||
// Unknown Response Recieved | ||
arr.push(["packet-type", 0, "UNKNOWN_RESPONSE", "main"]); | ||
arr.push(["raw-payload", 0, buf.slice(0, buf.length), "unknown"]); | ||
} | ||
|
||
return arr; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,147 @@ | ||
/* SFM1x R3-1 Reduced Packet Decoder Base to be used with lorawan listener plugin | ||
Includes Base Diagnostic and Switchable Result Packet. | ||
Suits SFM1x LoRa UCMI Firmware Revision r1-0-16 and newer all regions. | ||
Port1 Data containing Sap Flow uncorrected and Battery Voltage is maximum of 10-byte payload. | ||
Port10 Diagnostic Payload directly following OTAA Join is a maximum of 4-bytes. | ||
*/ | ||
|
||
// Structure Type Define, "nested" or "flat" | ||
var TYPE = "nested"; | ||
|
||
/*Class Buffer | ||
Purpose: A psuedo buffer class for accessing packet data, | ||
allows for uniformity between decoder types | ||
*/ | ||
function Buf(buf){this.pl=buf; this.length=this.pl.length;} | ||
Buf.prototype.readUInt8=function(ofs){return ((this.pl[ofs]<<24)>>>24);}; | ||
Buf.prototype.readUInt16BE=function(ofs){return ((this.pl[ofs++]<<24|this.pl[ofs++]<<16)>>>16);}; | ||
Buf.prototype.readUInt32BE=function(ofs){return ((this.pl[ofs++]<<24|this.pl[ofs++]<<16|this.pl[ofs++]<<8|this.pl[ofs++])>>>0);}; | ||
Buf.prototype.readUInt16LE=function(ofs){return ((this.pl[ofs+1]<<24|this.pl[ofs--]<<16)>>>16);}; | ||
Buf.prototype.readUInt32LE=function(ofs){return ((this.pl[ofs+3]<<24|this.pl[ofs+2]<<16|this.pl[ofs+1]<<8|this.pl[ofs])>>>0);}; | ||
Buf.prototype.readInt8=function(ofs){return ((this.pl[ofs]<<24)>>24);}; | ||
Buf.prototype.readInt16BE=function(ofs){return ((this.pl[ofs++]<<24|this.pl[ofs++]<<16)>>16);}; | ||
Buf.prototype.readInt32BE=function(ofs){return ((this.pl[ofs++]<<24|this.pl[ofs++]<<16|this.pl[ofs++]<<8|this.pl[ofs++])>>0);}; | ||
Buf.prototype.readInt16LE=function(ofs){return ((this.pl[ofs+1]<<24|this.pl[ofs--]<<16)>>16);}; | ||
Buf.prototype.readInt32LE=function(ofs){return ((this.pl[ofs+3]<<24|this.pl[ofs+2]<<16|this.pl[ofs+1]<<8|this.pl[ofs])>>0);}; | ||
Buf.prototype.readFloatBE=function(ofs){return B2Fl(this.readUInt32BE(ofs));}; | ||
Buf.prototype.readFloatLE=function(ofs){return B2Fl(this.readUInt32LE(ofs));}; | ||
Buf.prototype.slice=function(s,e){return this.pl.slice(s,e);}; | ||
Buf.prototype.length=function(){return this.pl.length;}; | ||
|
||
/*Function Bytes2Float32(bytes) | ||
Purpose: Decodes an array of bytes(len 4(32 bit) into a float. | ||
Args: bytes - an array of bytes, 4 bytes long | ||
Returns: 32bit Float representation | ||
*/ | ||
function B2Fl(b){ | ||
var sign =(b>>31)?-1:1; | ||
var exp=((b>>23)&0xFF)-127; | ||
var sig=(b&~(-1<<23)); | ||
if(exp==128) return sign*((sig)?Number.NaN:Number.POSITIVE_INFINITY); | ||
if(exp==-127){ | ||
if(sig===0) return sign*0.0; | ||
exp=-126; | ||
sig/=(1<<22); | ||
} else sig=(sig|(1<<23))/(1<<23); | ||
return sign*sig*Math.pow(2,exp); | ||
} | ||
|
||
/*Function buildNested(a) - SAGE version | ||
Purpose: Takes an array and parses them into a clean and succinct object of nested parameter sets | ||
Args: a - An array of arrays containing Parameter Sets | ||
Returns: An Object containing nested Parameter Sets | ||
*/ | ||
function buildNested(a){ | ||
var exc=["main","diagnostic","downlink","device_info","unknown"]; | ||
var ret=[]; | ||
for(var el in a){ | ||
var e=a[el]; | ||
var par={}; | ||
par['name']=e[0]; | ||
par['channelId']=e[1]; | ||
par['value']=e[2]; | ||
var e_length = e.length; | ||
if(e_length>3&&exc.indexOf(e[3])<0) par['source']=e[3]; | ||
if(e_length>4) par['unit']=e[4]; | ||
if(e_length>5) par['address']=e[5]; | ||
ret.push(par); | ||
} return ret;} | ||
|
||
/*Function buildFlat(a) | ||
Purpose: Takes an array and parses them into a clean and succinct object of flat parameters | ||
Args: a - An array of arrays containing Parameter Sets | ||
Returns: An Object containing nested Parameter Sets | ||
*/ | ||
function buildFlat(a){ | ||
var exc=["main","diagnostic","downlink","device_info","unknown"]; | ||
var ret={}; | ||
for(var el in a){ | ||
var e=a[el]; | ||
var label = ''; | ||
if(e.length==6){ | ||
e[0] = e[0]+e[5]+'_'; | ||
} | ||
if(e.length<=4){ | ||
label=(exc.indexOf(e[3])<0) ? (e[0]+e[1]) : (e[0]); | ||
} else{ | ||
label=(exc.indexOf(e[3])<0) ? (e[0]+e[1]+'_'+e[4]) : (e[0]+'_'+e[4]); | ||
|
||
} ret[label]=e[2]; | ||
} return ret;} | ||
|
||
//Function - Decode, Wraps the primary decoder function for Chirpstack - SAGE version | ||
function decodeUplink(input){ | ||
var buf = new Buf(input.bytes); | ||
var decoded = {}; | ||
var readingsArr = primaryDecoder(buf, input.fPort); | ||
if(TYPE == "flat"){ decoded['measurements'] = buildFlat(readingsArr); } | ||
else decoded['measurements'] = buildNested(readingsArr); | ||
|
||
return {data:decoded}; | ||
} | ||
|
||
/*Function primaryDecoder | ||
Purpose: Main Entry point of TTN Console Decoder | ||
Args: bytes - An array of bytes from LoRaWan raw payload(Hex Represented) | ||
port - LoRaWan Port that the message came through(set by Definium firmware) | ||
Returns: decoded - An object with data fields as decoded parameter values | ||
*/ | ||
function primaryDecoder(buf,p){ | ||
var arr = []; | ||
var byte = 0; | ||
|
||
//Data Packet Recieved | ||
if (p == 1){ | ||
var src = "main"; | ||
arr.push(["packet-type", 0, "DATA_PACKET", src]); | ||
arr.push(["uncorrected-outer", 0, +(buf.readFloatLE(byte).toFixed(3)), src, "cm/hr"]); | ||
arr.push(["uncorrected-inner", 0, +(buf.readFloatLE(byte=byte+4).toFixed(3)), src, "cm/hr"]); | ||
arr.push(["battery-voltage", 0, +((buf.readUInt16LE(byte=byte+4)/100).toFixed(2)), src, "V"]); | ||
} else if(p == 10){ | ||
var src = "device_info"; | ||
arr.push(["packet-type", 0,"DEVICE_INFO","main"]); | ||
|
||
var fw_main = buf.readInt16LE(byte).toString(); | ||
fw_main = ('R'+fw_main[0]+'-'+fw_main.slice(1,3).replace(/^(0)?/,'')+'-'+fw_main.slice(3,fw_main.length).replace(/^(0)?/,'')); | ||
arr.push(["firmware-mainboard",0,fw_main,src]); | ||
|
||
var fw_ucmi = buf.readInt16LE(byte=byte+2).toString(); | ||
//var region = ['a','e','c','s','u']; | ||
var region = ['a','c','e','i','s','u']; | ||
var ucmi_reg = region[+(fw_ucmi[fw_ucmi.length-1])]; | ||
//fw_ucmi = ('R'+fw_ucmi[0]+'-'+fw_ucmi[1]+'-'+fw_ucmi.slice(2,4).replace(/^(0)?/, '')+ucmi_reg); | ||
fw_ucmi = ('R'+fw_ucmi[0]+'-'+fw_ucmi[1]+'-'+fw_ucmi.slice(2,4) +ucmi_reg); | ||
arr.push(["firmware-ucmi",0, fw_ucmi,src]); | ||
|
||
} else if(p === 100){ | ||
//Downlink Response Packet Recieved | ||
arr.push(["packet-type", 0, "DOWNLINK_RESPONSE", "main"]); | ||
arr.push(["downlink-response", 0, String.fromCharCode.apply(String, buf.slice(0, buf.length)), "downlink"]); | ||
} else{ | ||
// Unknown Response Recieved | ||
arr.push(["packet-type", 0, "UNKNOWN_RESPONSE", "main"]); | ||
arr.push(["raw-payload", 0, buf.slice(0, buf.length), "unknown"]); | ||
} | ||
|
||
return arr; | ||
} |