Skip to content

Commit 60bbb2f

Browse files
committed
Adapts the latest version of TTN V3
1 parent af9acdb commit 60bbb2f

File tree

4 files changed

+375
-0
lines changed

4 files changed

+375
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ We assume that you already setup your gateway and SenseCAP nodes correctly, and
1616
- Please navigate to the `Payload Formats` tab of your application to which your SenseCAP nodes send data.
1717
- Select `Custom` for `Payload Format`
1818
- Copy and paste the whole contents of `decoder.js` to the `decoder` textarea.
19+
> If you are using the latest version of TTN v3, you may encounter an error that exceeds the character limit. In this case, you will need to use `decoder_new-v3-uglifyjs.js` instead.
1920
- Click `save payload functions`
2021

2122
![image](https://user-images.githubusercontent.com/5130185/76605545-954c9c80-654b-11ea-984e-272c58492e85.png)

decoder_new-v3-uglifyjs.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

decoder_new-v3.js

Lines changed: 349 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,349 @@
1+
/**
2+
* SenseCAP & TTN (new v3) Converter
3+
*
4+
* @since 3.0
5+
* @return Object
6+
* @param Boolean valid Indicates whether the payload is a valid payload.
7+
* @param String err The reason for the payload to be invalid. 0 means valid, minus means invalid.
8+
* @param String payload Hexadecimal string, to show the payload.
9+
* @param Array messages One or more messages are parsed according to payload.
10+
* type // Enum:
11+
* // - "report_telemetry"
12+
* // - "upload_battery"
13+
* // - "upload_interval"
14+
* // - "upload_version"
15+
* // - "upload_sensor_id"
16+
* // - "report_remove_sensor"
17+
* // - "unknown_message"
18+
*
19+
*
20+
*
21+
*
22+
* @sample-1
23+
* var sample = Decoder(["00", "00", "00", "01", "01", "00", "01", "00", "07", "00", "64", "00", "3C", "00", "01", "20", "01", "00", "00", "00", "00", "28", "90"], null);
24+
* {
25+
* valid: true,
26+
* err: 0,
27+
* payload: '0000000101000100070064003C00012001000000002890',
28+
* messages: [
29+
* { type: 'upload_version',
30+
* hardwareVersion: '1.0',
31+
* softwareVersion: '1.1' },
32+
* { type: 'upload_battery', battery: 100 },
33+
* { type: 'upload_interval', interval: 3600 },
34+
* { type: 'report_remove_sensor', channel: 1 }
35+
* ]
36+
* }
37+
* @sample-2
38+
* var sample = Decoder(["01", "01", "10", "98", "53", "00", "00", "01", "02", "10", "A8", "7A", "00", "00", "AF", "51"], null);
39+
* {
40+
* valid: true,
41+
* err: 0,
42+
* payload: '01011098530000010210A87A0000AF51',
43+
* messages: [
44+
* { type: 'report_telemetry',
45+
* measurementId: 4097,
46+
* measurementValue: 21.4 },
47+
* { type: 'report_telemetry',
48+
* measurementId: 4098,
49+
* measurementValue: 31.4 }
50+
* ]
51+
* }
52+
* @sample-3
53+
* var sample = Decoder(["01", "01", "00", "01", "01", "00", "01", "01", "02", "00", "6A", "01", "00", "15", "01", "03", "00", "30", "F1", "F7", "2C", "01", "04", "00", "09", "0C", "13", "14", "01", "05", "00", "7F", "4D", "00", "00", "01", "06", "00", "00", "00", "00", "00", "4C", "BE"], null);
54+
* {
55+
* valid: true,
56+
* err: 0,
57+
* payload: '010100010100010102006A01001501030030F1F72C010400090C13140105007F4D0000010600000000004CBE',
58+
* messages: [
59+
* { type: 'upload_sensor_id', sensorId: '30F1F72C6A010015', channel: 1 }
60+
* ]
61+
* }
62+
*/
63+
64+
65+
/**
66+
* Entry, decoder.js
67+
*/
68+
function decodeUplink (input) {
69+
var bytes = input['bytes'];
70+
// init
71+
var bytesString = bytes2HexString(bytes)
72+
.toLocaleUpperCase();
73+
var decoded = {
74+
// valid
75+
valid: true,
76+
err: 0,
77+
// bytes
78+
payload: bytesString,
79+
// messages array
80+
messages: []
81+
};
82+
83+
// CRC check
84+
if (!crc16Check(bytesString)) {
85+
decoded["valid"] = false;
86+
decoded["err"] = -1; // "crc check fail."
87+
return {data: decoded};
88+
}
89+
90+
// Length Check
91+
if ((((bytesString.length / 2) - 2) % 7) !== 0) {
92+
decoded["valid"] = false;
93+
decoded["err"] = -2; // "length check fail."
94+
return {data: decoded};
95+
}
96+
97+
// Cache sensor id
98+
var sensorEuiLowBytes;
99+
var sensorEuiHighBytes;
100+
101+
// Handle each frame
102+
var frameArray = divideBy7Bytes(bytesString);
103+
for (var forFrame = 0; forFrame < frameArray.length; forFrame++) {
104+
var frame = frameArray[forFrame];
105+
// Extract key parameters
106+
var channel = strTo10SysNub(frame.substring(0, 2));
107+
var dataID = strTo10SysNub(frame.substring(2, 6));
108+
var dataValue = frame.substring(6, 14);
109+
var realDataValue = isSpecialDataId(dataID) ? ttnDataSpecialFormat(dataID, dataValue) : ttnDataFormat(dataValue);
110+
111+
if (checkDataIdIsMeasureUpload(dataID)) {
112+
// if telemetry.
113+
decoded.messages.push({
114+
type: "report_telemetry",
115+
measurementId: dataID,
116+
measurementValue: realDataValue
117+
});
118+
} else if (isSpecialDataId(dataID) || (dataID === 5) || (dataID === 6)) {
119+
// if special order, except "report_sensor_id".
120+
switch (dataID) {
121+
case 0x00:
122+
// node version
123+
var versionData = sensorAttrForVersion(realDataValue);
124+
decoded.messages.push({
125+
type: "upload_version",
126+
hardwareVersion: versionData.ver_hardware,
127+
softwareVersion: versionData.ver_software
128+
});
129+
break;
130+
case 1:
131+
// sensor version
132+
break;
133+
case 2:
134+
// sensor eui, low bytes
135+
sensorEuiLowBytes = realDataValue;
136+
break;
137+
case 3:
138+
// sensor eui, high bytes
139+
sensorEuiHighBytes = realDataValue;
140+
break;
141+
case 7:
142+
// battery power && interval
143+
decoded.messages.push({
144+
type: "upload_battery",
145+
battery: realDataValue.power
146+
}, {
147+
type: "upload_interval",
148+
interval: parseInt(realDataValue.interval) * 60
149+
});
150+
break;
151+
case 0x120:
152+
// remove sensor
153+
decoded.messages.push({
154+
type: "report_remove_sensor",
155+
channel: 1
156+
});
157+
break;
158+
default:
159+
break;
160+
}
161+
} else {
162+
decoded.messages.push({
163+
type: "unknown_message",
164+
dataID: dataID,
165+
dataValue: dataValue
166+
});
167+
}
168+
169+
}
170+
171+
// if the complete id received, as "upload_sensor_id"
172+
if (sensorEuiHighBytes && sensorEuiLowBytes) {
173+
decoded.messages.unshift({
174+
type: "upload_sensor_id",
175+
channel: 1,
176+
sensorId: (sensorEuiHighBytes + sensorEuiLowBytes).toUpperCase()
177+
});
178+
}
179+
180+
// return
181+
return {data: decoded};
182+
}
183+
184+
function crc16Check (data) {
185+
return true;
186+
}
187+
188+
// util
189+
function bytes2HexString (arrBytes) {
190+
var str = "";
191+
for (var i = 0; i < arrBytes.length; i++) {
192+
var tmp;
193+
var num = arrBytes[i];
194+
if (num < 0) {
195+
tmp = (255 + num + 1).toString(16);
196+
} else {
197+
tmp = num.toString(16);
198+
}
199+
if (tmp.length === 1) {
200+
tmp = "0" + tmp;
201+
}
202+
str += tmp;
203+
}
204+
return str;
205+
}
206+
207+
// util
208+
function divideBy7Bytes (str) {
209+
var frameArray = [];
210+
for (var i = 0; i < str.length - 4; i += 14) {
211+
var data = str.substring(i, i + 14);
212+
frameArray.push(data);
213+
}
214+
return frameArray;
215+
}
216+
217+
// util
218+
function littleEndianTransform (data) {
219+
var dataArray = [];
220+
for (var i = 0; i < data.length; i += 2) {
221+
dataArray.push(data.substring(i, i + 2));
222+
}
223+
dataArray.reverse();
224+
return dataArray;
225+
}
226+
227+
// util
228+
function strTo10SysNub (str) {
229+
var arr = littleEndianTransform(str);
230+
return parseInt(arr.toString()
231+
.replace(/,/g, ""), 16);
232+
}
233+
234+
// util
235+
function checkDataIdIsMeasureUpload (dataId) {
236+
return parseInt(dataId) > 4096;
237+
}
238+
239+
// configurable.
240+
function isSpecialDataId (dataID) {
241+
switch (dataID) {
242+
case 0:
243+
case 1:
244+
case 2:
245+
case 3:
246+
case 4:
247+
case 7:
248+
case 0x120:
249+
return true;
250+
default:
251+
return false;
252+
}
253+
}
254+
255+
// configurable
256+
function ttnDataSpecialFormat (dataId, str) {
257+
var strReverse = littleEndianTransform(str);
258+
if (dataId === 2 || dataId === 3) {
259+
return strReverse.join("");
260+
}
261+
262+
// handle unsigned number
263+
var str2 = toBinary(strReverse);
264+
265+
var dataArray = [];
266+
switch (dataId) {
267+
case 0: // DATA_BOARD_VERSION
268+
case 1: // DATA_SENSOR_VERSION
269+
// Using point segmentation
270+
for (var k = 0; k < str2.length; k += 16) {
271+
var tmp146 = str2.substring(k, k + 16);
272+
tmp146 = (parseInt(tmp146.substring(0, 8), 2) || 0) + "." + (parseInt(tmp146.substring(8, 16), 2) || 0);
273+
dataArray.push(tmp146);
274+
}
275+
return dataArray.join(",");
276+
case 4:
277+
for (var i = 0; i < str2.length; i += 8) {
278+
var item = parseInt(str2.substring(i, i + 8), 2);
279+
if (item < 10) {
280+
item = "0" + item.toString();
281+
} else {
282+
item = item.toString();
283+
}
284+
dataArray.push(item);
285+
}
286+
return dataArray.join("");
287+
case 7:
288+
// battery && interval
289+
return {
290+
interval: parseInt(str2.substr(0, 16), 2),
291+
power: parseInt(str2.substr(-16, 16), 2)
292+
};
293+
}
294+
}
295+
296+
// util
297+
function ttnDataFormat (str) {
298+
var strReverse = littleEndianTransform(str);
299+
var str2 = toBinary(strReverse);
300+
if (str2.substring(0, 1) === "1") {
301+
var arr = str2.split("");
302+
var reverseArr = [];
303+
for (var forArr = 0; forArr < arr.length; forArr++) {
304+
var item = arr[forArr];
305+
if (parseInt(item) === 1) {
306+
reverseArr.push(0);
307+
} else {
308+
reverseArr.push(1);
309+
}
310+
}
311+
str2 = parseInt(reverseArr.join(""), 2) + 1;
312+
return parseFloat("-" + str2 / 1000);
313+
}
314+
return parseInt(str2, 2) / 1000;
315+
}
316+
317+
// util
318+
function sensorAttrForVersion (dataValue) {
319+
var dataValueSplitArray = dataValue.split(",");
320+
return {
321+
ver_hardware: dataValueSplitArray[0],
322+
ver_software: dataValueSplitArray[1]
323+
};
324+
}
325+
326+
// util
327+
function toBinary (arr) {
328+
var binaryData = [];
329+
for (var forArr = 0; forArr < arr.length; forArr++) {
330+
var item = arr[forArr];
331+
var data = parseInt(item, 16)
332+
.toString(2);
333+
var dataLength = data.length;
334+
if (data.length !== 8) {
335+
for (var i = 0; i < 8 - dataLength; i++) {
336+
data = "0" + data;
337+
}
338+
}
339+
binaryData.push(data);
340+
}
341+
return binaryData.toString()
342+
.replace(/,/g, "");
343+
}
344+
345+
// Samples
346+
// var sample = Decoder(["00", "00", "00", "01", "01", "00", "01", "00", "07", "00", "64", "00", "3C", "00", "01", "20", "01", "00", "00", "00", "00", "28", "90"], null);
347+
// var sample = Decoder(["01", "01", "10", "98", "53", "00", "00", "01", "02", "10", "A8", "7A", "00", "00", "AF", "51"], null);
348+
// var sample = Decoder(["01", "01", "00", "01", "01", "00", "01", "01", "02", "00", "6A", "01", "00", "15", "01", "03", "00", "30", "F1", "F7", "2C", "01", "04", "00", "09", "0C", "13", "14", "01", "05", "00", "7F", "4D", "00", "00", "01", "06", "00", "00", "00", "00", "00", "4C", "BE"], null);
349+
// console.log(sample);

uglifyjs_decoder_new-v3.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
let fs = require('fs');
2+
let UglifyJS = require('uglify-js')
3+
4+
var code = fs.readFileSync("decoder_new-v3.js", "utf8");
5+
6+
7+
let options = {
8+
compress: {
9+
properties: {}
10+
},
11+
mangle: {
12+
toplevel: true,
13+
reserved: ['firstLongName', 'decodeUplink'],
14+
// properties: {
15+
// debug: "",
16+
// reserved: ['bytes', 'hardwareVersion']
17+
// }
18+
}
19+
}
20+
21+
code = UglifyJS.minify(code, options).code;
22+
23+
console.log('==== Length: ' + code.length)
24+
console.log(code);

0 commit comments

Comments
 (0)