Skip to content

Commit 132ba6f

Browse files
authored
milesight: add EM400-TLD profile (#19)
1 parent a6cc0ea commit 132ba6f

5 files changed

Lines changed: 381 additions & 2 deletions

File tree

Lines changed: 356 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,356 @@
1+
/**
2+
* Payload Decoder
3+
*
4+
* Copyright 2024 Milesight IoT
5+
*
6+
* @product EM400-TLD
7+
*/
8+
var RAW_VALUE = 0x00;
9+
10+
/* eslint no-redeclare: "off" */
11+
/* eslint-disable */
12+
// Chirpstack v4
13+
function decodeUplink(input) {
14+
var decoded = milesightDeviceDecode(input.bytes);
15+
return { data: decoded };
16+
}
17+
18+
// Chirpstack v3
19+
function Decode(fPort, bytes) {
20+
return milesightDeviceDecode(bytes);
21+
}
22+
23+
// The Things Network
24+
function Decoder(bytes, port) {
25+
return milesightDeviceDecode(bytes);
26+
}
27+
/* eslint-enable */
28+
29+
function milesightDeviceDecode(bytes) {
30+
var decoded = {};
31+
32+
for (var i = 0; i < bytes.length; ) {
33+
var channel_id = bytes[i++];
34+
var channel_type = bytes[i++];
35+
36+
// IPSO VERSION
37+
if (channel_id === 0xff && channel_type === 0x01) {
38+
decoded.ipso_version = readProtocolVersion(bytes[i]);
39+
i += 1;
40+
}
41+
// HARDWARE VERSION
42+
else if (channel_id === 0xff && channel_type === 0x09) {
43+
decoded.hardware_version = readHardwareVersion(bytes.slice(i, i + 2));
44+
i += 2;
45+
}
46+
// FIRMWARE VERSION
47+
else if (channel_id === 0xff && channel_type === 0x0a) {
48+
decoded.firmware_version = readFirmwareVersion(bytes.slice(i, i + 2));
49+
i += 2;
50+
}
51+
// TSL VERSION
52+
else if (channel_id === 0xff && channel_type === 0xff) {
53+
decoded.tsl_version = readTslVersion(bytes.slice(i, i + 2));
54+
i += 2;
55+
}
56+
// SERIAL NUMBER
57+
else if (channel_id === 0xff && channel_type === 0x16) {
58+
decoded.sn = readSerialNumber(bytes.slice(i, i + 8));
59+
i += 8;
60+
}
61+
// LORAWAN CLASS TYPE
62+
else if (channel_id === 0xff && channel_type === 0x0f) {
63+
decoded.lorawan_class = readLoRaWANClass(bytes[i]);
64+
i += 1;
65+
}
66+
// RESET EVENT
67+
else if (channel_id === 0xff && channel_type === 0xfe) {
68+
decoded.reset_event = readResetEvent(1);
69+
i += 1;
70+
}
71+
// DEVICE STATUS
72+
else if (channel_id === 0xff && channel_type === 0x0b) {
73+
decoded.device_status = readDeviceStatus(1);
74+
i += 1;
75+
}
76+
77+
// BATTERY
78+
else if (channel_id === 0x01 && channel_type === 0x75) {
79+
decoded.battery = readUInt8(bytes[i]);
80+
i += 1;
81+
}
82+
// TEMPERATURE
83+
else if (channel_id === 0x03 && channel_type === 0x67) {
84+
decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10;
85+
i += 2;
86+
}
87+
// DISTANCE
88+
else if (channel_id === 0x04 && channel_type === 0x82) {
89+
decoded.distance = readUInt16LE(bytes.slice(i, i + 2));
90+
i += 2;
91+
}
92+
// POSITION
93+
else if (channel_id === 0x05 && channel_type === 0x00) {
94+
decoded.position = readPositionType(bytes[i]);
95+
i += 1;
96+
}
97+
// TEMPERATURE WITH ABNORMAL
98+
else if (channel_id === 0x83 && channel_type === 0x67) {
99+
decoded.temperature = readInt16LE(bytes.slice(i, i + 2)) / 10;
100+
decoded.temperature_alarm = readAlarmType(bytes[i + 2]);
101+
i += 3;
102+
}
103+
// DISTANCE WITH ALARMING
104+
else if (channel_id === 0x84 && channel_type === 0x82) {
105+
decoded.distance = readUInt16LE(bytes.slice(i, i + 2));
106+
decoded.distance_alarm = readAlarmType(bytes[i + 2]);
107+
i += 3;
108+
}
109+
// DOWNLINK RESPONSE
110+
else if (channel_id === 0xfe || channel_id === 0xff) {
111+
var result = handle_downlink_response(channel_type, bytes, i);
112+
decoded = Object.assign(decoded, result.data);
113+
i = result.offset;
114+
} else {
115+
break;
116+
}
117+
}
118+
119+
return decoded;
120+
}
121+
122+
function handle_downlink_response(channel_type, bytes, offset) {
123+
var decoded = {};
124+
125+
switch (channel_type) {
126+
case 0x02:
127+
decoded.collection_interval = readUInt16LE(bytes.slice(offset, offset + 2));
128+
offset += 2;
129+
break;
130+
case 0x03:
131+
decoded.report_interval = readUInt16LE(bytes.slice(offset, offset + 2));
132+
offset += 2;
133+
break;
134+
case 0x06:
135+
var value = readUInt8(bytes[offset]);
136+
var alarm_type = (value >>> 3) & 0x07;
137+
var condition_type = value & 0x07;
138+
var config = {};
139+
config.condition = readMathConditionType(condition_type);
140+
config.alarm_release_enable = readEnableStatus((value >>> 7) & 0x01);
141+
config.threshold_min = readUInt16LE(bytes.slice(offset + 1, offset + 3));
142+
config.threshold_max = readUInt16LE(bytes.slice(offset + 3, offset + 5));
143+
// skip 4 bytes
144+
offset += 9;
145+
if (alarm_type === 1) {
146+
decoded.standard_mode_alarm_config = config;
147+
} else if (alarm_type === 2) {
148+
decoded.bin_mode_alarm_config = config;
149+
}
150+
break;
151+
case 0x10:
152+
decoded.reboot = readYesNoStatus(1);
153+
offset += 1;
154+
break;
155+
case 0x13:
156+
decoded.install_height_enable = readEnableStatus(bytes[offset]);
157+
offset += 1;
158+
break;
159+
case 0x1c:
160+
decoded.recollection_config = {};
161+
decoded.recollection_config.counts = bytes[offset];
162+
decoded.recollection_config.interval = bytes[offset + 1];
163+
offset += 2;
164+
break;
165+
case 0x28:
166+
decoded.query_device_status = readYesNoStatus(1);
167+
offset += 1;
168+
break;
169+
case 0x3e:
170+
decoded.tilt_linkage_distance_enable = readEnableStatus(bytes[offset]);
171+
offset += 1;
172+
break;
173+
case 0x4a:
174+
decoded.sync_time = readYesNoStatus(1);
175+
offset += 1;
176+
break;
177+
case 0x56:
178+
decoded.tof_detection_enable = readEnableStatus(bytes[offset]);
179+
offset += 1;
180+
break;
181+
case 0x70:
182+
decoded.people_existing_height = readUInt16LE(bytes.slice(offset, offset + 2));
183+
offset += 2;
184+
break;
185+
case 0x71:
186+
decoded.working_mode = readWorkingMode(bytes[offset]);
187+
offset += 1;
188+
break;
189+
case 0x77:
190+
decoded.install_height = readUInt16LE(bytes.slice(offset, offset + 2));
191+
offset += 2;
192+
break;
193+
default:
194+
throw new Error("unknown downlink response");
195+
}
196+
197+
return { data: decoded, offset: offset };
198+
}
199+
200+
function readProtocolVersion(bytes) {
201+
var major = (bytes & 0xf0) >> 4;
202+
var minor = bytes & 0x0f;
203+
return "v" + major + "." + minor;
204+
}
205+
206+
function readHardwareVersion(bytes) {
207+
var major = (bytes[0] & 0xff).toString(16);
208+
var minor = (bytes[1] & 0xff) >> 4;
209+
return "v" + major + "." + minor;
210+
}
211+
212+
function readFirmwareVersion(bytes) {
213+
var major = (bytes[0] & 0xff).toString(16);
214+
var minor = (bytes[1] & 0xff).toString(16);
215+
return "v" + major + "." + minor;
216+
}
217+
218+
function readTslVersion(bytes) {
219+
var major = bytes[0] & 0xff;
220+
var minor = bytes[1] & 0xff;
221+
return "v" + major + "." + minor;
222+
}
223+
224+
function readSerialNumber(bytes) {
225+
var temp = [];
226+
for (var idx = 0; idx < bytes.length; idx++) {
227+
temp.push(("0" + (bytes[idx] & 0xff).toString(16)).slice(-2));
228+
}
229+
return temp.join("");
230+
}
231+
232+
function readLoRaWANClass(type) {
233+
var class_map = {
234+
0: "Class A",
235+
1: "Class B",
236+
2: "Class C",
237+
3: "Class CtoB",
238+
};
239+
return getValue(class_map, type);
240+
}
241+
242+
function readResetEvent(status) {
243+
var status_map = { 0: "normal", 1: "reset" };
244+
return getValue(status_map, status);
245+
}
246+
247+
function readDeviceStatus(status) {
248+
var status_map = { 0: "off", 1: "on" };
249+
return getValue(status_map, status);
250+
}
251+
252+
function readYesNoStatus(status) {
253+
var status_map = { 0: "no", 1: "yes" };
254+
return getValue(status_map, status);
255+
}
256+
257+
function readEnableStatus(status) {
258+
var status_map = { 0: "disable", 1: "enable" };
259+
return getValue(status_map, status);
260+
}
261+
262+
function readPositionType(type) {
263+
var type_map = { 0: "normal", 1: "tilt" };
264+
return getValue(type_map, type);
265+
}
266+
267+
function readAlarmType(type) {
268+
var type_map = { 0: "threshold_alarm_release", 1: "threshold_alarm" };
269+
return getValue(type_map, type);
270+
}
271+
272+
function readMathConditionType(type) {
273+
var type_map = { 0: "disable", 1: "below", 2: "above", 3: "between", 4: "outside" };
274+
return getValue(type_map, type);
275+
}
276+
277+
function readWorkingMode(type) {
278+
var type_map = { 0: "standard", 1: "bin" };
279+
return getValue(type_map, type);
280+
}
281+
282+
/* eslint-disable */
283+
function readUInt8(bytes) {
284+
return bytes & 0xff;
285+
}
286+
287+
function readInt8(bytes) {
288+
var ref = readUInt8(bytes);
289+
return ref > 0x7f ? ref - 0x100 : ref;
290+
}
291+
292+
function readUInt16LE(bytes) {
293+
var value = (bytes[1] << 8) + bytes[0];
294+
return value & 0xffff;
295+
}
296+
297+
function readInt16LE(bytes) {
298+
var ref = readUInt16LE(bytes);
299+
return ref > 0x7fff ? ref - 0x10000 : ref;
300+
}
301+
302+
function readUInt32LE(bytes) {
303+
var value = (bytes[3] << 24) + (bytes[2] << 16) + (bytes[1] << 8) + bytes[0];
304+
return (value & 0xffffffff) >>> 0;
305+
}
306+
307+
function readInt32LE(bytes) {
308+
var ref = readUInt32LE(bytes);
309+
return ref > 0x7fffffff ? ref - 0x100000000 : ref;
310+
}
311+
312+
function getValue(map, key) {
313+
if (RAW_VALUE) return key;
314+
315+
var value = map[key];
316+
if (!value) value = "unknown";
317+
return value;
318+
}
319+
320+
//if (!Object.assign) {
321+
Object.defineProperty(Object, "assign", {
322+
enumerable: false,
323+
configurable: true,
324+
writable: true,
325+
value: function (target) {
326+
"use strict";
327+
if (target == null) {
328+
throw new TypeError("Cannot convert first argument to object");
329+
}
330+
331+
var to = Object(target);
332+
for (var i = 1; i < arguments.length; i++) {
333+
var nextSource = arguments[i];
334+
if (nextSource == null) {
335+
continue;
336+
}
337+
nextSource = Object(nextSource);
338+
339+
var keysArray = Object.keys(Object(nextSource));
340+
for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
341+
var nextKey = keysArray[nextIndex];
342+
var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
343+
if (desc !== undefined && desc.enumerable) {
344+
// concat array
345+
if (Array.isArray(to[nextKey]) && Array.isArray(nextSource[nextKey])) {
346+
to[nextKey] = to[nextKey].concat(nextSource[nextKey]);
347+
} else {
348+
to[nextKey] = nextSource[nextKey];
349+
}
350+
}
351+
}
352+
}
353+
return to;
354+
},
355+
});
356+
//}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
[device]
2+
id = "92b180ce-3d9e-4f10-a617-d441a46a509e"
3+
name = "Milesight EM400-TLD"
4+
description = "ToF Laser Distance Sensor"
5+
6+
[[device.firmware]]
7+
version = "1.2"
8+
profiles = [
9+
"EU868-1_0_3.toml",
10+
"US915-1_0_3.toml",
11+
"AU915-1_0_3.toml",
12+
"AS923-1_0_3.toml",
13+
]
14+
codec = "em400-tld.js"
15+
16+
[device.metadata]
17+
product_url = "https://www.milesight.com/iot/product/lorawan-sensor/em400-tld"
18+
documentation_url = "https://www.milesight.com/iot/product/lorawan-sensor/em400-tld"

0 commit comments

Comments
 (0)