Skip to content

Commit 3402905

Browse files
elsys: add ELT-2 HP device profile with payload codec (#16)
1 parent eeffa23 commit 3402905

6 files changed

Lines changed: 377 additions & 0 deletions

File tree

vendors/elsys/codecs/elsys.js

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
/*
2+
ELSYS payload decoder for ChirpStack v4.
3+
Based on the official Elsys payload decoder.
4+
www.elsys.se
5+
peter@elsys.se
6+
7+
Adapted for chirpstack-device-profiles decodeUplink/encodeDownlink format.
8+
*/
9+
10+
var TYPE_TEMP = 0x01; // temp 2 bytes -3276.8°C to 3276.7°C
11+
var TYPE_RH = 0x02; // Humidity 1 byte 0-100%
12+
var TYPE_ACC = 0x03; // Acceleration 3 bytes X,Y,Z -128 to 127 +/-63=1G
13+
var TYPE_LIGHT = 0x04; // Light 2 bytes 0-65535 Lux
14+
var TYPE_MOTION = 0x05; // No of motion 1 byte 0-255
15+
var TYPE_CO2 = 0x06; // CO2 2 bytes 0-65535 ppm
16+
var TYPE_VDD = 0x07; // Battery mV 2 bytes 0-65535
17+
var TYPE_ANALOG1 = 0x08; // Analog input 1 2 bytes 0-65535 mV
18+
var TYPE_GPS = 0x09; // GPS 6 bytes lat(3),long(3)
19+
var TYPE_PULSE1 = 0x0A; // Pulse input 1 relative 2 bytes
20+
var TYPE_PULSE1_ABS = 0x0B; // Pulse input 1 absolute 4 bytes
21+
var TYPE_EXT_TEMP1 = 0x0C; // External temp 1 2 bytes -3276.8°C to 3276.7°C
22+
var TYPE_EXT_DIGITAL = 0x0D; // Digital input 1 byte
23+
var TYPE_EXT_DISTANCE = 0x0E; // Distance sensor 2 bytes 0-65535 mm
24+
var TYPE_ACC_MOTION = 0x0F; // Acc motion 1 byte 0-255
25+
var TYPE_IR_TEMP = 0x10; // IR temperature 4 bytes internal(2),external(2)
26+
var TYPE_OCCUPANCY = 0x11; // Occupancy 1 byte
27+
var TYPE_WATERLEAK = 0x12; // Water leak 1 byte 0-255
28+
var TYPE_GRIDEYE = 0x13; // Grideye data 65 bytes
29+
var TYPE_PRESSURE = 0x14; // Pressure 4 bytes (hPa)
30+
var TYPE_SOUND = 0x15; // Sound 2 bytes (peak,avg)
31+
var TYPE_PULSE2 = 0x16; // Pulse input 2 relative 2 bytes
32+
var TYPE_PULSE2_ABS = 0x17; // Pulse input 2 absolute 4 bytes
33+
var TYPE_ANALOG2 = 0x18; // Analog input 2 2 bytes 0-65535 mV
34+
var TYPE_EXT_TEMP2 = 0x19; // External temp 2 2 bytes -3276.8°C to 3276.7°C
35+
var TYPE_EXT_DIGITAL2 = 0x1A; // Digital input 2 1 byte
36+
var TYPE_EXT_ANALOG_UV= 0x1B; // Analog UV 4 bytes
37+
38+
function bin16dec(bin) {
39+
var num = bin & 0xFFFF;
40+
if (0x8000 & num)
41+
num = -(0x010000 - num);
42+
return num;
43+
}
44+
45+
function bin8dec(bin) {
46+
var num = bin & 0xFF;
47+
if (0x80 & num)
48+
num = -(0x0100 - num);
49+
return num;
50+
}
51+
52+
function DecodeElsysPayload(data) {
53+
var obj = {};
54+
for (var i = 0; i < data.length; i++) {
55+
switch (data[i]) {
56+
case TYPE_TEMP:
57+
var temp = (data[i + 1] << 8) | (data[i + 2]);
58+
temp = bin16dec(temp);
59+
obj.temperature = temp / 10;
60+
i += 2;
61+
break;
62+
case TYPE_RH:
63+
obj.humidity = data[i + 1];
64+
i += 1;
65+
break;
66+
case TYPE_ACC:
67+
obj.x = bin8dec(data[i + 1]);
68+
obj.y = bin8dec(data[i + 2]);
69+
obj.z = bin8dec(data[i + 3]);
70+
i += 3;
71+
break;
72+
case TYPE_LIGHT:
73+
obj.light = (data[i + 1] << 8) | (data[i + 2]);
74+
i += 2;
75+
break;
76+
case TYPE_MOTION:
77+
obj.motion = data[i + 1];
78+
i += 1;
79+
break;
80+
case TYPE_CO2:
81+
obj.co2 = (data[i + 1] << 8) | (data[i + 2]);
82+
i += 2;
83+
break;
84+
case TYPE_VDD:
85+
obj.vdd = (data[i + 1] << 8) | (data[i + 2]);
86+
i += 2;
87+
break;
88+
case TYPE_ANALOG1:
89+
obj.analog1 = (data[i + 1] << 8) | (data[i + 2]);
90+
i += 2;
91+
break;
92+
case TYPE_GPS:
93+
obj.lat = (data[i + 1] << 16) | (data[i + 2] << 8) | (data[i + 3]);
94+
obj.long = (data[i + 4] << 16) | (data[i + 5] << 8) | (data[i + 6]);
95+
i += 6;
96+
break;
97+
case TYPE_PULSE1:
98+
obj.pulse1 = (data[i + 1] << 8) | (data[i + 2]);
99+
i += 2;
100+
break;
101+
case TYPE_PULSE1_ABS:
102+
obj.pulseAbs = (data[i + 1] << 24) | (data[i + 2] << 16) | (data[i + 3] << 8) | (data[i + 4]);
103+
i += 4;
104+
break;
105+
case TYPE_EXT_TEMP1:
106+
var temp = (data[i + 1] << 8) | (data[i + 2]);
107+
temp = bin16dec(temp);
108+
obj.externalTemperature = temp / 10;
109+
i += 2;
110+
break;
111+
case TYPE_EXT_DIGITAL:
112+
obj.digital = data[i + 1];
113+
i += 1;
114+
break;
115+
case TYPE_EXT_DISTANCE:
116+
obj.distance = (data[i + 1] << 8) | (data[i + 2]);
117+
i += 2;
118+
break;
119+
case TYPE_ACC_MOTION:
120+
obj.accMotion = data[i + 1];
121+
i += 1;
122+
break;
123+
case TYPE_IR_TEMP:
124+
var iTemp = (data[i + 1] << 8) | (data[i + 2]);
125+
iTemp = bin16dec(iTemp);
126+
var eTemp = (data[i + 3] << 8) | (data[i + 4]);
127+
eTemp = bin16dec(eTemp);
128+
obj.irInternalTemperature = iTemp / 10;
129+
obj.irExternalTemperature = eTemp / 10;
130+
i += 4;
131+
break;
132+
case TYPE_OCCUPANCY:
133+
obj.occupancy = data[i + 1];
134+
i += 1;
135+
break;
136+
case TYPE_WATERLEAK:
137+
obj.waterleak = data[i + 1];
138+
i += 1;
139+
break;
140+
case TYPE_GRIDEYE:
141+
var ref = data[i + 1];
142+
i++;
143+
obj.grideye = [];
144+
for (var j = 0; j < 64; j++) {
145+
obj.grideye[j] = ref + (data[1 + i + j] / 10.0);
146+
}
147+
i += 64;
148+
break;
149+
case TYPE_PRESSURE:
150+
var press = (data[i + 1] << 24) | (data[i + 2] << 16) | (data[i + 3] << 8) | (data[i + 4]);
151+
obj.pressure = press / 1000;
152+
i += 4;
153+
break;
154+
case TYPE_SOUND:
155+
obj.soundPeak = data[i + 1];
156+
obj.soundAvg = data[i + 2];
157+
i += 2;
158+
break;
159+
case TYPE_PULSE2:
160+
obj.pulse2 = (data[i + 1] << 8) | (data[i + 2]);
161+
i += 2;
162+
break;
163+
case TYPE_PULSE2_ABS:
164+
obj.pulseAbs2 = (data[i + 1] << 24) | (data[i + 2] << 16) | (data[i + 3] << 8) | (data[i + 4]);
165+
i += 4;
166+
break;
167+
case TYPE_ANALOG2:
168+
obj.analog2 = (data[i + 1] << 8) | (data[i + 2]);
169+
i += 2;
170+
break;
171+
case TYPE_EXT_TEMP2:
172+
var temp = (data[i + 1] << 8) | (data[i + 2]);
173+
temp = bin16dec(temp);
174+
if (typeof obj.externalTemperature2 === "number") {
175+
obj.externalTemperature2 = [obj.externalTemperature2];
176+
}
177+
if (typeof obj.externalTemperature2 === "object") {
178+
obj.externalTemperature2.push(temp / 10);
179+
} else {
180+
obj.externalTemperature2 = temp / 10;
181+
}
182+
i += 2;
183+
break;
184+
case TYPE_EXT_DIGITAL2:
185+
obj.digital2 = data[i + 1];
186+
i += 1;
187+
break;
188+
case TYPE_EXT_ANALOG_UV:
189+
obj.analogUv = (data[i + 1] << 24) | (data[i + 2] << 16) | (data[i + 3] << 8) | (data[i + 4]);
190+
i += 4;
191+
break;
192+
default:
193+
i = data.length;
194+
break;
195+
}
196+
}
197+
return obj;
198+
}
199+
200+
// ChirpStack v4 / LoRa Alliance TS013 codec API
201+
function decodeUplink(input) {
202+
return {
203+
data: DecodeElsysPayload(input.bytes),
204+
};
205+
}
206+
207+
function encodeDownlink(input) {
208+
return {
209+
bytes: [],
210+
};
211+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
[
2+
{
3+
"name": "Test decode temperature and humidity",
4+
"input": {
5+
"fPort": 5,
6+
"bytes": [1, 0, 234, 2, 55]
7+
},
8+
"expected": {
9+
"data": {
10+
"temperature": 23.4,
11+
"humidity": 55
12+
}
13+
}
14+
},
15+
{
16+
"name": "Test decode temperature, humidity and VDD",
17+
"input": {
18+
"fPort": 5,
19+
"bytes": [1, 0, 198, 2, 42, 7, 14, 10]
20+
},
21+
"expected": {
22+
"data": {
23+
"temperature": 19.8,
24+
"humidity": 42,
25+
"vdd": 3594
26+
}
27+
}
28+
},
29+
{
30+
"name": "Test decode negative temperature",
31+
"input": {
32+
"fPort": 5,
33+
"bytes": [1, 255, 206, 2, 80]
34+
},
35+
"expected": {
36+
"data": {
37+
"temperature": -5.0,
38+
"humidity": 80
39+
}
40+
}
41+
},
42+
{
43+
"name": "Test decode external temperature (DS18B20)",
44+
"input": {
45+
"fPort": 5,
46+
"bytes": [1, 0, 200, 12, 0, 150]
47+
},
48+
"expected": {
49+
"data": {
50+
"temperature": 20.0,
51+
"externalTemperature": 15.0
52+
}
53+
}
54+
},
55+
{
56+
"name": "Test decode analog input",
57+
"input": {
58+
"fPort": 5,
59+
"bytes": [1, 0, 180, 8, 19, 136]
60+
},
61+
"expected": {
62+
"data": {
63+
"temperature": 18.0,
64+
"analog1": 5000
65+
}
66+
}
67+
},
68+
{
69+
"name": "Test decode pressure",
70+
"input": {
71+
"fPort": 5,
72+
"bytes": [20, 0, 15, 92, 168]
73+
},
74+
"expected": {
75+
"data": {
76+
"pressure": 1006.76
77+
}
78+
}
79+
},
80+
{
81+
"name": "Test decode digital input",
82+
"input": {
83+
"fPort": 5,
84+
"bytes": [1, 0, 210, 13, 1]
85+
},
86+
"expected": {
87+
"data": {
88+
"temperature": 21.0,
89+
"digital": 1
90+
}
91+
}
92+
},
93+
{
94+
"name": "Test decode pulse counter",
95+
"input": {
96+
"fPort": 5,
97+
"bytes": [10, 0, 42]
98+
},
99+
"expected": {
100+
"data": {
101+
"pulse1": 42
102+
}
103+
}
104+
}
105+
]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[
2+
{
3+
"name": "Test encode empty (no downlink encoding supported)",
4+
"input": {
5+
"data": {}
6+
},
7+
"expected": {
8+
"bytes": []
9+
}
10+
}
11+
]
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
[device]
2+
id = "b2c3d4e5-6f7a-8b9c-0d1e-f2a3b4c5d6e7"
3+
name = "ELT-2 HP"
4+
description = "Multi-sensor with 2 external sensor inputs. Internal temperature, humidity, accelerometer and barometric pressure. External inputs support analog 0-10V, digital, pulse counter, DS18B20, ultrasonic distance, and water leak sensors. IP67 rated with external antenna."
5+
6+
[[device.firmware]]
7+
version = "1.0"
8+
profiles = [
9+
"EU868-1_0_3.toml",
10+
]
11+
codec = "elsys.js"
12+
13+
[device.metadata]
14+
product_url = "https://www.elsys.se/shop/product/elt2hp/"
15+
documentation_url = "https://elsys.se/public/documents/Sensor_payload.pdf"
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
[profile]
2+
id = "c3d4e5f6-7a8b-9c0d-1e2f-a3b4c5d6e7f8"
3+
vendor_profile_id = 0
4+
region = "EU868"
5+
mac_version = "1.0.3"
6+
reg_params_revision = "A"
7+
supports_otaa = true
8+
supports_class_b = false
9+
supports_class_c = false
10+
max_eirp = 14
11+
12+
[profile.abp]
13+
rx1_delay = 0
14+
rx1_dr_offset = 0
15+
rx2_dr = 0
16+
rx2_freq = 0
17+
18+
[profile.class_b]
19+
timeout_secs = 0
20+
ping_slot_nb_k = 0
21+
ping_slot_dr = 0
22+
ping_slot_freq = 0
23+
24+
[profile.class_c]
25+
timeout_secs = 0

vendors/elsys/vendor.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[vendor]
2+
id = "a1b2c3d4-5e6f-7a8b-9c0d-e1f2a3b4c5d6"
3+
name = "Elsys"
4+
ouis = ["fc0fee"]
5+
devices = [
6+
"elt-2-hp.toml",
7+
]
8+
9+
[vendor.metadata]
10+
homepage = "https://www.elsys.se/"

0 commit comments

Comments
 (0)