11/**
22 * Payload Decoder
33 *
4- * Copyright 2024 Milesight IoT
4+ * Copyright 2025 Milesight IoT
55 *
66 * @product VS330
77 */
8+ var RAW_VALUE = 0x00 ;
9+
810// Chirpstack v4
911function decodeUplink ( input ) {
1012 var decoded = milesightDeviceDecode ( input . bytes ) ;
@@ -24,12 +26,53 @@ function Decoder(bytes, port) {
2426function milesightDeviceDecode ( bytes ) {
2527 var decoded = { } ;
2628
27- for ( var i = 0 ; i < bytes . length ; ) {
29+ for ( var i = 0 ; i < bytes . length ; ) {
2830 var channel_id = bytes [ i ++ ] ;
2931 var channel_type = bytes [ i ++ ] ;
3032
33+
34+ // IPSO VERSION
35+ if ( channel_id === 0xff && channel_type === 0x01 ) {
36+ decoded . ipso_version = readProtocolVersion ( bytes [ i ] ) ;
37+ i += 1 ;
38+ }
39+ // HARDWARE VERSION
40+ else if ( channel_id === 0xff && channel_type === 0x09 ) {
41+ decoded . hardware_version = readHardwareVersion ( bytes . slice ( i , i + 2 ) ) ;
42+ i += 2 ;
43+ }
44+ // FIRMWARE VERSION
45+ else if ( channel_id === 0xff && channel_type === 0x0a ) {
46+ decoded . firmware_version = readFirmwareVersion ( bytes . slice ( i , i + 2 ) ) ;
47+ i += 2 ;
48+ }
49+ // TSL VERSION
50+ else if ( channel_id === 0xff && channel_type === 0xff ) {
51+ decoded . tsl_version = readTslVersion ( bytes . slice ( i , i + 2 ) ) ;
52+ i += 2 ;
53+ }
54+ // SERIAL NUMBER
55+ else if ( channel_id === 0xff && channel_type === 0x16 ) {
56+ decoded . sn = readSerialNumber ( bytes . slice ( i , i + 8 ) ) ;
57+ i += 8 ;
58+ }
59+ // LORAWAN CLASS TYPE
60+ else if ( channel_id === 0xff && channel_type === 0x0f ) {
61+ decoded . lorawan_class = readLoRaWANClass ( bytes [ i ] ) ;
62+ i += 1 ;
63+ }
64+ // RESET EVENT
65+ else if ( channel_id === 0xff && channel_type === 0xfe ) {
66+ decoded . reset_event = readResetEvent ( 1 ) ;
67+ i += 1 ;
68+ }
69+ // DEVICE STATUS
70+ else if ( channel_id === 0xff && channel_type === 0x0b ) {
71+ decoded . device_status = readDeviceStatus ( 1 ) ;
72+ i += 1 ;
73+ }
3174 // BATTERY
32- if ( channel_id === 0x01 && channel_type === 0x75 ) {
75+ else if ( channel_id === 0x01 && channel_type === 0x75 ) {
3376 decoded . battery = bytes [ i ] ;
3477 i += 1 ;
3578 }
@@ -40,13 +83,18 @@ function milesightDeviceDecode(bytes) {
4083 }
4184 // OCCUPANCY
4285 else if ( channel_id === 0x03 && channel_type === 0x8e ) {
43- decoded . occupancy = bytes [ i ] === 0 ? "vacant" : "occupied" ;
86+ decoded . occupancy = readOccupancyStatus ( bytes [ i ] ) ;
4487 i += 1 ;
4588 }
4689 // CALIBRATION
4790 else if ( channel_id === 0x04 && channel_type === 0x8e ) {
48- decoded . calibration = bytes [ i ] === 0 ? "failed" : "success" ;
91+ decoded . calibration_status = readCalibrationStatus ( bytes [ i ] ) ;
4992 i += 1 ;
93+ } // DOWNLINK RESPONSE
94+ else if ( channel_id === 0xfe || channel_id === 0xff ) {
95+ result = handle_downlink_response ( channel_type , bytes , i ) ;
96+ decoded = Object . assign ( decoded , result . data ) ;
97+ i = result . offset ;
5098 } else {
5199 break ;
52100 }
@@ -55,8 +103,180 @@ function milesightDeviceDecode(bytes) {
55103 return decoded ;
56104}
57105
58- // bytes to number
106+ function handle_downlink_response ( channel_type , bytes , offset ) {
107+ var decoded = { } ;
108+
109+ switch ( channel_type ) {
110+ case 0x02 :
111+ decoded . collection_interval = readUInt16LE ( bytes . slice ( offset , offset + 2 ) ) ;
112+ offset += 2 ;
113+ break ;
114+ case 0x03 :
115+ decoded . report_interval = readUInt16LE ( bytes . slice ( offset , offset + 2 ) ) ;
116+ offset += 2 ;
117+ break ;
118+ case 0x10 :
119+ decoded . reboot = readYesNoStatus ( 1 ) ;
120+ offset += 1 ;
121+ break ;
122+ case 0x70 :
123+ decoded . human_exist_height = readUInt16LE ( bytes . slice ( offset , offset + 2 ) ) ;
124+ offset += 2 ;
125+ break ;
126+ case 0x71 :
127+ decoded . test_enable = readEnableStatus ( bytes [ offset ] ) ;
128+ offset += 1 ;
129+ break ;
130+ case 0x72 :
131+ decoded . test_duration = readUInt16LE ( bytes . slice ( offset , offset + 2 ) ) ;
132+ offset += 2 ;
133+ break ;
134+ case 0x7a :
135+ decoded . back_test_config = { } ;
136+ decoded . back_test_config . enable = readEnableStatus ( bytes [ offset ] ) ;
137+ decoded . back_test_config . distance = readUInt16LE ( bytes . slice ( offset + 1 , offset + 3 ) ) ;
138+ offset += 3 ;
139+ break ;
140+ default :
141+ throw new Error ( "unknown downlink response" ) ;
142+ }
143+
144+ return { data : decoded , offset : offset } ;
145+ }
146+
147+ function readProtocolVersion ( bytes ) {
148+ var major = ( bytes & 0xf0 ) >> 4 ;
149+ var minor = bytes & 0x0f ;
150+ return "v" + major + "." + minor ;
151+ }
152+
153+ function readHardwareVersion ( bytes ) {
154+ var major = bytes [ 0 ] & 0xff ;
155+ var minor = ( bytes [ 1 ] & 0xff ) >> 4 ;
156+ return "v" + major + "." + minor ;
157+ }
158+
159+ function readFirmwareVersion ( bytes ) {
160+ var major = bytes [ 0 ] & 0xff ;
161+ var minor = bytes [ 1 ] & 0xff ;
162+ return "v" + major + "." + minor ;
163+ }
164+
165+ function readTslVersion ( bytes ) {
166+ var major = bytes [ 0 ] & 0xff ;
167+ var minor = bytes [ 1 ] & 0xff ;
168+ return "v" + major + "." + minor ;
169+ }
170+
171+ function readSerialNumber ( bytes ) {
172+ var temp = [ ] ;
173+ for ( var idx = 0 ; idx < bytes . length ; idx ++ ) {
174+ temp . push ( ( "0" + ( bytes [ idx ] & 0xff ) . toString ( 16 ) ) . slice ( - 2 ) ) ;
175+ }
176+ return temp . join ( "" ) ;
177+ }
178+
179+ function readLoRaWANClass ( type ) {
180+ var class_map = {
181+ 0 : "Class A" ,
182+ 1 : "Class B" ,
183+ 2 : "Class C" ,
184+ 3 : "Class CtoB" ,
185+ } ;
186+ return getValue ( class_map , type ) ;
187+ }
188+
189+ function readResetEvent ( status ) {
190+ var status_map = { 0 : "normal" , 1 : "reset" } ;
191+ return getValue ( status_map , status ) ;
192+ }
193+
194+ function readDeviceStatus ( status ) {
195+ var status_map = { 0 : "off" , 1 : "on" } ;
196+ return getValue ( status_map , status ) ;
197+ }
198+
199+ function readYesNoStatus ( status ) {
200+ var status_map = { 0 : "no" , 1 : "yes" } ;
201+ return getValue ( status_map , status ) ;
202+ }
203+
204+ function readEnableStatus ( status ) {
205+ var status_map = { 0 : "disable" , 1 : "enable" } ;
206+ return getValue ( status_map , status ) ;
207+ }
208+
209+ function readOccupancyStatus ( status ) {
210+ var status_map = { 0 : "vacant" , 1 : "occupied" } ;
211+ return getValue ( status_map , status ) ;
212+ }
213+
214+ function readCalibrationStatus ( status ) {
215+ var status_map = { 0 : "failed" , 1 : "success" } ;
216+ return getValue ( status_map , status ) ;
217+ }
218+
219+ function readUInt8 ( bytes ) {
220+ return bytes & 0xff ;
221+ }
222+
223+ function readInt8 ( bytes ) {
224+ var ref = readUInt8 ( bytes ) ;
225+ return ref > 0x7f ? ref - 0x100 : ref ;
226+ }
227+
59228function readUInt16LE ( bytes ) {
60229 var value = ( bytes [ 1 ] << 8 ) + bytes [ 0 ] ;
61230 return value & 0xffff ;
62231}
232+
233+ function readInt16LE ( bytes ) {
234+ var ref = readUInt16LE ( bytes ) ;
235+ return ref > 0x7fff ? ref - 0x10000 : ref ;
236+ }
237+
238+ function getValue ( map , key ) {
239+ if ( RAW_VALUE ) return key ;
240+
241+ var value = map [ key ] ;
242+ if ( ! value ) value = "unknown" ;
243+ return value ;
244+ }
245+
246+ if ( ! Object . assign ) {
247+ Object . defineProperty ( Object , "assign" , {
248+ enumerable : false ,
249+ configurable : true ,
250+ writable : true ,
251+ value : function ( target ) {
252+ "use strict" ;
253+ if ( target == null ) {
254+ throw new TypeError ( "Cannot convert first argument to object" ) ;
255+ }
256+
257+ var to = Object ( target ) ;
258+ for ( var i = 1 ; i < arguments . length ; i ++ ) {
259+ var nextSource = arguments [ i ] ;
260+ if ( nextSource == null ) {
261+ continue ;
262+ }
263+ nextSource = Object ( nextSource ) ;
264+
265+ var keysArray = Object . keys ( Object ( nextSource ) ) ;
266+ for ( var nextIndex = 0 , len = keysArray . length ; nextIndex < len ; nextIndex ++ ) {
267+ var nextKey = keysArray [ nextIndex ] ;
268+ var desc = Object . getOwnPropertyDescriptor ( nextSource , nextKey ) ;
269+ if ( desc !== undefined && desc . enumerable ) {
270+ // concat array
271+ if ( Array . isArray ( to [ nextKey ] ) && Array . isArray ( nextSource [ nextKey ] ) ) {
272+ to [ nextKey ] = to [ nextKey ] . concat ( nextSource [ nextKey ] ) ;
273+ } else {
274+ to [ nextKey ] = nextSource [ nextKey ] ;
275+ }
276+ }
277+ }
278+ }
279+ return to ;
280+ } ,
281+ } ) ;
282+ }
0 commit comments