Skip to content

Commit

Permalink
added ict vendor and sfm1x device
Browse files Browse the repository at this point in the history
  • Loading branch information
FranciscoLozCoding committed Sep 4, 2024
1 parent 4de6b53 commit 9e89f4f
Show file tree
Hide file tree
Showing 6 changed files with 363 additions and 0 deletions.
3 changes: 3 additions & 0 deletions vendor/ict-international/index.yaml
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
16 changes: 16 additions & 0 deletions vendor/ict-international/sfm1x-profile-us915.yaml
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
Binary file added vendor/ict-international/sfm1x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
49 changes: 49 additions & 0 deletions vendor/ict-international/sfm1x.yaml
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
148 changes: 148 additions & 0 deletions vendor/ict-international/sfm1x_r1-1_codec.js
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;
}
147 changes: 147 additions & 0 deletions vendor/ict-international/sfm1x_r3-1_codec.js
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;
}

0 comments on commit 9e89f4f

Please sign in to comment.