-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathTTN_Elsys-ERS_uplink-decoder.js
248 lines (237 loc) · 13.1 KB
/
TTN_Elsys-ERS_uplink-decoder.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
/*
______ _ _______ _______
| ____| | / ____\ \ / / ____|
| |__ | | | (___ \ \_/ / (___
| __| | | \___ \ \ / \___ \
| |____| |____ ____) | | | ____) |
|______|______|_____/ |_| |_____/
ELSYS simple payload decoder.
Use it as it is or remove the bugs :)
www.elsys.se
peter@elsys.se
Good-to-have links:
https://www.elsys.se/en/elsys-payload/
https://github.com/TheThingsNetwork/lorawan-devices/blob/master/vendor/elsys/elsys.js
https://elsys.se/public/documents/Sensor_payload.pdf
*/
// Constants for sensor data types
const SENSOR_DATA_TYPES = {
TEMP: 0x01, // Temperature Sensor: 2 bytes, range -3276.8°C to 3276.7°C.
RH: 0x02, // Humidity Sensor: 1 byte, percentage range 0-100%.
ACC: 0x03, // Accelerometer: 3 bytes for X, Y, Z axes, range -128 to 127, where +/-63 equals 1G.
LIGHT: 0x04, // Light Sensor: 2 bytes, luminosity range 0 to 65535 Lux.
MOTION: 0x05, // Motion Sensor: 1 byte, counts the number of motions detected, range 0-255.
CO2: 0x06, // CO2 Sensor: 2 bytes, CO2 concentration range 0-65535 ppm (parts per million).
VDD: 0x07, // Battery Voltage: 2 bytes, voltage level range 0-65535mV.
ANALOG1: 0x08, // Analog Input 1: 2 bytes, voltage measurement range 0-65535mV.
GPS: 0x09, // GPS Location: 6 bytes, 3 bytes for latitude and 3 for longitude, stored in binary format.
PULSE1: 0x0A, // Pulse Counter 1: 2 bytes, relative pulse count.
PULSE1_ABS: 0x0B, // Pulse Counter 1 (Absolute Value): 4 bytes, absolute number of pulses, range 0 to 0xFFFFFFFF.
EXT_TEMP1: 0x0C, // External Temperature Sensor 1: 2 bytes, range -3276.5°C to 3276.5°C.
EXT_DIGITAL: 0x0D, // External Digital Input: 1 byte, value either 1 (high) or 0 (low).
EXT_DISTANCE: 0x0E, // Distance Sensor: 2 bytes, measures distance in millimeters.
ACC_MOTION: 0x0F, // Acceleration-based Motion Detection: 1 byte, number of detected movements.
IR_TEMP: 0x10, // IR Temperature Sensor: 4 bytes, 2 for internal and 2 for external temperature, range -3276.5°C to 3276.5°C.
OCCUPANCY: 0x11, // Occupancy Sensor: 1 byte, data indicating presence (not detailed).
WATERLEAK: 0x12, // Water Leak Sensor: 1 byte, range 0-255 indicating leak strength or detection.
GRIDEYE: 0x13, // Grid-Eye Sensor: 65 bytes, 1 byte for reference temperature and 64 bytes for external temperatures.
PRESSURE: 0x14, // Pressure Sensor: 4 bytes, atmospheric pressure in hPa.
SOUND: 0x15, // Sound Sensor: 2 bytes, capturing peak and average sound levels.
PULSE2: 0x16, // Pulse Counter 2: 2 bytes, relative pulse count.
PULSE2_ABS: 0x17, // Pulse Counter 2 (Absolute Value): 4 bytes, absolute number of pulses, range 0 to 0xFFFFFFFF.
ANALOG2: 0x18, // Analog Input 2: 2 bytes, voltage measurement range 0-65535mV.
EXT_TEMP2: 0x19, // External Temperature Sensor 2: 2 bytes, range -3276.5°C to 3276.5°C.
EXT_DIGITAL2: 0x1A, // External Digital Input 2: 1 byte, value either 1 (high) or 0 (low).
EXT_ANALOG_UV: 0x1B, // External Analog UV Sensor: 4 bytes, UV light intensity in signed integer (microvolts).
TVOC: 0x1C, // Total Volatile Organic Compounds Sensor: 2 bytes, concentration in ppb (parts per billion).
DEBUG: 0x3D, // Debug Information: 4 bytes, intended for diagnostics or debugging purposes.
};
// Helper functions for decoding binary data
function bin16dec(bin) {
let num = bin & 0xffff;
if (0x8000 & num) num = -(0x010000 - num);
return num;
}
function bin8dec(bin) {
let num = bin & 0xff;
if (0x80 & num) num = -(0x0100 - num);
return num;
}
function hexToBytes(hex) {
let bytes = [];
for (let c = 0; c < hex.length; c += 2) {
bytes.push(parseInt(hex.substr(c, 2), 16));
}
return bytes;
}
// Main function to decode the ELSYS payload
function DecodeElsysPayload(data) {
let obj = {};
for (let i = 0; i < data.length; i++) {
switch (data[i]) {
// Case handlers for each sensor data type
case SENSOR_DATA_TYPES.TEMP: // Decode temperature from 2 bytes, convert to real value in °C.
var temp = (data[i + 1] << 8) | (data[i + 2]);
temp = bin16dec(temp);
obj.temperature = temp / 10; // Temperature is in tenths of a degree.
i += 2;
break;
case SENSOR_DATA_TYPES.RH: // Decode relative humidity from 1 byte, value in percentage.
var rh = data[i + 1];
obj.humidity = rh; // Humidity percentage, 0 to 100%.
i += 1;
break;
case SENSOR_DATA_TYPES.ACC: // Decode 3-axis acceleration, values in Gs, from 3 bytes.
obj.x = bin8dec(data[i + 1]); // X-axis acceleration.
obj.y = bin8dec(data[i + 2]); // Y-axis acceleration.
obj.z = bin8dec(data[i + 3]); // Z-axis acceleration.
i += 3;
break;
case SENSOR_DATA_TYPES.LIGHT: // Decode light intensity from 2 bytes, value in Lux.
obj.light = (data[i + 1] << 8) | (data[i + 2]);
i += 2;
break;
case SENSOR_DATA_TYPES.MOTION: // Decode motion count from 1 byte, number of detected movements.
obj.motion = data[i + 1];
i += 1;
break;
case SENSOR_DATA_TYPES.CO2: // Decode CO2 concentration from 2 bytes, value in ppm.
obj.co2 = (data[i + 1] << 8) | (data[i + 2]);
i += 2;
break;
case SENSOR_DATA_TYPES.VDD: // Decode battery voltage level from 2 bytes, value in mV.
obj.vdd = ((data[i + 1] << 8) | (data[i + 2]))/ 1000;
// added for battery presentage
obj.vddPct = Math.round(100 * (obj.vdd - 2.3) / (3.6 - 2.3));
if (obj.vddPct > 100) {
obj.vddPct = 100;
} else if (obj.vddPct < 1) {
obj.vddPct = 1;
}
i += 2;
break;
case SENSOR_DATA_TYPES.ANALOG1: // Decode analog input 1 from 2 bytes, value in mV.
obj.analog1 = (data[i + 1] << 8) | (data[i + 2]);
i += 2;
break;
case SENSOR_DATA_TYPES.GPS: // Decode GPS coordinates from 6 bytes, converted to decimal degrees.
i++;
obj.lat = (data[i + 0] | data[i + 1] << 8 | data[i + 2] << 16 | (data[i + 2] & 0x80 ? 0xFF << 24 : 0)) / 10000;
obj.long = (data[i + 3] | data[i + 4] << 8 | data[i + 5] << 16 | (data[i + 5] & 0x80 ? 0xFF << 24 : 0)) / 10000;
i += 5;
break;
case SENSOR_DATA_TYPES.PULSE1: // Decode pulse input 1 from 2 bytes, relative pulse count.
obj.pulse1 = (data[i + 1] << 8) | (data[i + 2]);
i += 2;
break;
case SENSOR_DATA_TYPES.PULSE1_ABS: // Decode absolute value of pulse input 1 from 4 bytes.
var pulseAbs = (data[i + 1] << 24) | (data[i + 2] << 16) | (data[i + 3] << 8) | (data[i + 4]);
obj.pulseAbs = pulseAbs;
i += 4;
break;
case SENSOR_DATA_TYPES.EXT_TEMP1: // Decode external temperature from 2 bytes, convert to real value in °C.
var temp = (data[i + 1] << 8) | (data[i + 2]);
temp = bin16dec(temp);
obj.externalTemperature = temp / 10; // External temperature in tenths of a degree.
i += 2;
break;
case SENSOR_DATA_TYPES.EXT_DIGITAL: // Decode external digital input from 1 byte, value 1 or 0.
obj.digital = data[i + 1];
i += 1;
break;
case SENSOR_DATA_TYPES.EXT_DISTANCE: // Decode distance sensor input from 2 bytes, value in mm.
obj.distance = (data[i + 1] << 8) | (data[i + 2]);
i += 2;
break;
case SENSOR_DATA_TYPES.ACC_MOTION: // Decode acceleration-based motion detection from 1 byte.
obj.accMotion = data[i + 1]; // Number of detected movements via accelerometer.
i += 1;
break;
case SENSOR_DATA_TYPES.IR_TEMP: // Decode IR temperatures: internal and external, from 4 bytes, convert to °C.
var iTemp = (data[i + 1] << 8) | (data[i + 2]);
iTemp = bin16dec(iTemp); // Internal temperature.
var eTemp = (data[i + 3] << 8) | (data[i + 4]);
eTemp = bin16dec(eTemp); // External temperature.
obj.irInternalTemperature = iTemp / 10; // Converted to real temperature value.
obj.irExternalTemperature = eTemp / 10; // Converted to real temperature value.
i += 4;
break;
case SENSOR_DATA_TYPES.OCCUPANCY: // Decode occupancy from 1 byte, presence detected or not.
obj.occupancy = data[i + 1]; // Occupancy data, binary presence indication.
i += 1;
break;
case SENSOR_DATA_TYPES.WATERLEAK: // Decode water leak detection from 1 byte.
obj.waterleak = data[i + 1]; // Water leak data, 0-255 indicating the detection level.
i += 1;
break;
case SENSOR_DATA_TYPES.GRIDEYE: // Decode Grid-Eye sensor data: 1 byte reference temperature + 64 bytes external temperatures.
var ref = data[i + 1];
i++;
obj.grideye = []; // Array to store temperature data.
for (var j = 0; j < 64; j++) {
obj.grideye[j] = ref + (data[1 + i + j] / 10.0); // Calculate each temperature point.
}
i += 64;
break;
case SENSOR_DATA_TYPES.PRESSURE: // Decode atmospheric pressure from 4 bytes, value in hPa.
var temp = (data[i + 1] << 24) | (data[i + 2] << 16) | (data[i + 3] << 8) | (data[i + 4]);
obj.pressure = temp / 1000; // Convert to hPa.
i += 4;
break;
case SENSOR_DATA_TYPES.SOUND: // Decode sound levels from 2 bytes: peak and average sound levels.
obj.soundPeak = data[i + 1]; // Peak sound level.
obj.soundAvg = data[i + 2]; // Average sound level.
i += 2;
break;
case SENSOR_DATA_TYPES.PULSE2: // Decode pulse input 2 from 2 bytes, relative pulse count.
obj.pulse2 = (data[i + 1] << 8) | (data[i + 2]);
i += 2;
break;
case SENSOR_DATA_TYPES.PULSE2_ABS: // Decode absolute value of pulse input 2 from 4 bytes.
obj.pulseAbs2 = (data[i + 1] << 24) | (data[i + 2] << 16) | (data[i + 3] << 8) | (data[i + 4]);
i += 4;
break;
case SENSOR_DATA_TYPES.ANALOG2: // Decode analog input 2 from 2 bytes, value in mV.
obj.analog2 = (data[i + 1] << 8) | (data[i + 2]);
i += 2;
break;
case SENSOR_DATA_TYPES.EXT_TEMP2: // Decode and manage external temperature 2 data from 2 bytes, convert to °C.
var temp = (data[i + 1] << 8) | (data[i + 2]);
temp = bin16dec(temp); // Convert binary to decimal.
// Ensure externalTemperature2 is properly handled as an array if multiple readings exist.
if (typeof obj.externalTemperature2 === "number") {
obj.externalTemperature2 = [obj.externalTemperature2];
}
if (Array.isArray(obj.externalTemperature2)) {
obj.externalTemperature2.push(temp / 10);
} else {
obj.externalTemperature2 = temp / 10;
}
i += 2;
break;
case SENSOR_DATA_TYPES.EXT_DIGITAL2: // Decode external digital input 2 from 1 byte, value 1 or 0.
obj.digital2 = data[i + 1];
i += 1;
break;
case SENSOR_DATA_TYPES.EXT_ANALOG_UV: // Decode load cell analog data in microvolts (uV) from 4 bytes.
obj.analogUv = (data[i + 1] << 24) | (data[i + 2] << 16) | (data[i + 3] << 8) | (data[i + 4]);
i += 4;
break;
case SENSOR_DATA_TYPES.TVOC: // Decode Total Volatile Organic Compounds (TVOC) from 2 bytes, value in parts per billion (ppb).
obj.tvoc = (data[i + 1] << 8) | (data[i + 2]);
i += 2; // Move past the bytes used for TVOC data.
break;
default: // Case to handle unknown or invalid sensor data types.
i = data.length; // If an unrecognized type is encountered, skip to the end of the data array to avoid processing invalid data.
break;
}
}
return obj;
}
function decodeUplink(input) {
return {
data: DecodeElsysPayload(input.bytes),
warnings: [],
errors: []
};
}