queclink-parser 1.9.19 → 1.9.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +53 -0
- package/example.js +5 -2
- package/package.json +3 -2
- package/src/gl300.js +56 -14
- package/src/gl50.js +32 -8
- package/src/gl533cg.js +523 -0
- package/src/gmt100.js +88 -22
- package/src/gv200.js +144 -36
- package/src/gv300.js +112 -28
- package/src/gv300w.js +152 -38
- package/src/gv310lau.js +291 -120
- package/src/gv500.js +104 -26
- package/src/gv50p.js +88 -22
- package/src/gv55.js +88 -22
- package/src/gv58lau.js +1223 -685
- package/src/gv600w.js +144 -36
- package/src/gv75w.js +152 -38
- package/src/gv800w.js +144 -36
- package/src/index.js +75 -9
- package/src/messages/es.json +15 -1
- package/src/utils.js +555 -267
package/src/gl533cg.js
ADDED
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
const utils = require('./utils.js')
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Parses messages data from GL533CG devices
|
|
6
|
+
*/
|
|
7
|
+
const parse = raw => {
|
|
8
|
+
let buf
|
|
9
|
+
if (typeof raw === 'string') {
|
|
10
|
+
buf = Buffer.from(raw, 'binary')
|
|
11
|
+
} else {
|
|
12
|
+
buf = raw
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Header 1 byte
|
|
16
|
+
// 00H 1 byte
|
|
17
|
+
// Frame Length 2 bytes
|
|
18
|
+
// Multi-packet Flag 1 byte
|
|
19
|
+
const multiPacketFlag = buf.readUInt8(4)
|
|
20
|
+
const isMultiPacket = multiPacketFlag >> 7 === 1
|
|
21
|
+
let offset = 5
|
|
22
|
+
|
|
23
|
+
if (isMultiPacket) {
|
|
24
|
+
offset += 2 // Frame Count & Frame Number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// IMEI 8 bytes (16 hex digits)
|
|
28
|
+
const imeiBuffer = buf.slice(offset, offset + 8)
|
|
29
|
+
const imei = imeiBuffer.toString('hex').replace(/^0+/, '') // e.g. 0123456789012345
|
|
30
|
+
offset += 8
|
|
31
|
+
|
|
32
|
+
// Device Type 2 bytes
|
|
33
|
+
const deviceTypeInt = buf.readUInt16BE(offset)
|
|
34
|
+
const deviceTypeStr = deviceTypeInt
|
|
35
|
+
.toString(16)
|
|
36
|
+
.padStart(4, '0')
|
|
37
|
+
.toUpperCase() // C301
|
|
38
|
+
offset += 2
|
|
39
|
+
|
|
40
|
+
// Protocol Version 2 bytes
|
|
41
|
+
const protocolVersionInt = buf.readUInt16BE(offset)
|
|
42
|
+
const protocolVersionStr = protocolVersionInt
|
|
43
|
+
.toString(16)
|
|
44
|
+
.padStart(4, '0')
|
|
45
|
+
.toUpperCase() // 0005
|
|
46
|
+
offset += 2
|
|
47
|
+
|
|
48
|
+
// Custom Version 1 byte
|
|
49
|
+
buf.readUInt8(offset)
|
|
50
|
+
offset += 1
|
|
51
|
+
|
|
52
|
+
// Reserved Field Length 1 byte
|
|
53
|
+
const reservedLen = buf.readUInt8(offset)
|
|
54
|
+
offset += 1 + reservedLen
|
|
55
|
+
|
|
56
|
+
let data = {
|
|
57
|
+
raw: typeof raw === 'string' ? raw : raw.toString('hex').toUpperCase(),
|
|
58
|
+
manufacturer: 'queclink',
|
|
59
|
+
device: 'Queclink-GL533CG',
|
|
60
|
+
type: 'data',
|
|
61
|
+
imei: imei,
|
|
62
|
+
protocolVersion: utils.getProtocolVersion(
|
|
63
|
+
`${deviceTypeStr}${protocolVersionStr}`
|
|
64
|
+
),
|
|
65
|
+
temperature: null,
|
|
66
|
+
history: buf[0] === 0x2d, // 2DH means Buffer Report
|
|
67
|
+
sentTime: null,
|
|
68
|
+
serialId: null
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Records Part
|
|
72
|
+
// Read records
|
|
73
|
+
while (offset < buf.length - 4) {
|
|
74
|
+
// keep 4 bytes for Count, CRC, Tail
|
|
75
|
+
const recordLengthByte = buf.readUInt8(offset)
|
|
76
|
+
let recordLength
|
|
77
|
+
let recordLenSize
|
|
78
|
+
if (recordLengthByte >> 7 === 0) {
|
|
79
|
+
recordLength = recordLengthByte
|
|
80
|
+
recordLenSize = 1
|
|
81
|
+
} else {
|
|
82
|
+
recordLength = buf.readUInt16BE(offset) & 0x7fff
|
|
83
|
+
recordLenSize = 2
|
|
84
|
+
}
|
|
85
|
+
const recordEnd = offset + recordLenSize + recordLength
|
|
86
|
+
offset += recordLenSize
|
|
87
|
+
|
|
88
|
+
// Generated Time 4 bytes
|
|
89
|
+
const genTimeTimestamp = buf.readUInt32BE(offset)
|
|
90
|
+
data.sentTime = new Date(genTimeTimestamp * 1000)
|
|
91
|
+
offset += 4
|
|
92
|
+
|
|
93
|
+
// Record Count Number (Serial ID) 2 bytes
|
|
94
|
+
const serialId = buf.readUInt16BE(offset)
|
|
95
|
+
if (data.serialId === null) data.serialId = serialId
|
|
96
|
+
offset += 2
|
|
97
|
+
|
|
98
|
+
// Record ID 1 or 2 bytes
|
|
99
|
+
const recordIdByte = buf.readUInt8(offset)
|
|
100
|
+
let recordId
|
|
101
|
+
if (recordIdByte >> 3 === 0x1f) {
|
|
102
|
+
recordId = buf.readUInt16BE(offset) & 0x7fff
|
|
103
|
+
offset += 2
|
|
104
|
+
} else {
|
|
105
|
+
recordId = recordIdByte
|
|
106
|
+
offset += 1
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Event Code (1 byte)
|
|
110
|
+
const eventCode = buf.readUInt8(offset)
|
|
111
|
+
offset += 1
|
|
112
|
+
|
|
113
|
+
if (recordId === 0x50 || recordId === 0x04) {
|
|
114
|
+
// 50H / 04H Fixed Report Information
|
|
115
|
+
data.alarm = utils.getAlarm('GTFRI', null)
|
|
116
|
+
} else if (recordId === 0x01) {
|
|
117
|
+
// 01H Device Startup
|
|
118
|
+
data.alarm = utils.getAlarm('GTPNA', null)
|
|
119
|
+
} else if (recordId === 0x05) {
|
|
120
|
+
// 05H Device Shutdown
|
|
121
|
+
data.alarm = utils.getAlarm('GTPFA', null)
|
|
122
|
+
} else if (recordId === 0x10) {
|
|
123
|
+
// 10H SOS Notification
|
|
124
|
+
data.alarm = utils.getAlarm('GTSOS', null)
|
|
125
|
+
} else if (recordId === 0x11) {
|
|
126
|
+
// 11H Device Basic Information
|
|
127
|
+
data.alarm = utils.getAlarm('GTATI', null)
|
|
128
|
+
} else if (recordId === 0x12) {
|
|
129
|
+
// 12H Real Time Location
|
|
130
|
+
data.alarm = utils.getAlarm('GTRTL', null)
|
|
131
|
+
// } else if (recordId === 0x17) { // 17H Geo-fence events
|
|
132
|
+
// data.alarm = utils.getAlarm('GTGEO', null)
|
|
133
|
+
} else if (recordId === 0x20) {
|
|
134
|
+
// 20H Internal Battery Information
|
|
135
|
+
data.alarm = utils.getAlarm('GTBPL', null)
|
|
136
|
+
// } else if (recordId === 0x21) { // 21H Motion Information
|
|
137
|
+
// data.alarm = utils.getAlarm('GSENSOR', null)
|
|
138
|
+
} else if (recordId === 0x87) {
|
|
139
|
+
// 87H Light Intensity Alarm
|
|
140
|
+
data.alarm = utils.getAlarm('LIGHT', null)
|
|
141
|
+
} else if (recordId === 0x94) {
|
|
142
|
+
// 94H SVR Connection Notification
|
|
143
|
+
data.alarm = utils.getAlarm('GTSVR', eventCode)
|
|
144
|
+
} else if (recordId === 0xf3) {
|
|
145
|
+
// F3H GTC Status
|
|
146
|
+
data.alarm = utils.getAlarm('GTALM', null)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Inside record, process Data IDs until recordEnd
|
|
150
|
+
while (offset < recordEnd && offset < buf.length - 4) {
|
|
151
|
+
const dataIdByte = buf.readUInt8(offset)
|
|
152
|
+
let dataId
|
|
153
|
+
if (dataIdByte >> 7 === 0) {
|
|
154
|
+
dataId = dataIdByte
|
|
155
|
+
offset += 1
|
|
156
|
+
} else {
|
|
157
|
+
dataId = buf.readUInt16BE(offset) & 0x7fff
|
|
158
|
+
offset += 2
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const dataLenByte = buf.readUInt8(offset)
|
|
162
|
+
let dataLen
|
|
163
|
+
if (dataLenByte >> 7 === 0) {
|
|
164
|
+
dataLen = dataLenByte
|
|
165
|
+
offset += 1
|
|
166
|
+
} else {
|
|
167
|
+
dataLen = buf.readUInt16BE(offset) & 0x7fff
|
|
168
|
+
offset += 2
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const dataContent = buf.slice(offset, offset + dataLen)
|
|
172
|
+
offset += dataLen
|
|
173
|
+
|
|
174
|
+
if (dataId === 0x01) {
|
|
175
|
+
// Profile ID
|
|
176
|
+
data.profileId = dataContent.readUInt8(0)
|
|
177
|
+
} else if (dataId === 0x02) {
|
|
178
|
+
// Device Name
|
|
179
|
+
// data.deviceName = dataContent.toString()
|
|
180
|
+
continue
|
|
181
|
+
} else if (dataId === 0x04) {
|
|
182
|
+
// Device Serial Number
|
|
183
|
+
data.serialNumber = dataContent.toString()
|
|
184
|
+
} else if (dataId === 0x07) {
|
|
185
|
+
// Current Working Mode
|
|
186
|
+
data.status = data.status || {}
|
|
187
|
+
const mode = dataContent.readUInt8(0)
|
|
188
|
+
data.connectionMode =
|
|
189
|
+
mode === 0x00
|
|
190
|
+
? 'Power saving'
|
|
191
|
+
: mode === 0x01 ? 'Continuous' : 'Unknown'
|
|
192
|
+
} else if (dataId === 0x0a) {
|
|
193
|
+
// Internal Battery Percentage
|
|
194
|
+
data.voltage = data.voltage || {}
|
|
195
|
+
data.voltage.battery = dataContent.readUInt8(0)
|
|
196
|
+
data.voltage.inputCharge = false
|
|
197
|
+
} else if (dataId === 0x11) {
|
|
198
|
+
// RF433 Working Status
|
|
199
|
+
const rfState = dataContent.readUInt8(0)
|
|
200
|
+
data.rf433 =
|
|
201
|
+
rfState === 0
|
|
202
|
+
? 'Normal'
|
|
203
|
+
: rfState === 1
|
|
204
|
+
? 'Module abnormal'
|
|
205
|
+
: rfState === 2 ? 'Antenna abnormal' : 'Unknown'
|
|
206
|
+
} else if (dataId === 0x13) {
|
|
207
|
+
// Triggered Time
|
|
208
|
+
const triggeredTime = dataContent.readUInt32BE(0)
|
|
209
|
+
data.datetime = new Date(triggeredTime * 1000)
|
|
210
|
+
} else if (dataId === 0x16) {
|
|
211
|
+
// Record Count (History count)
|
|
212
|
+
data.recordCount = dataContent.readUInt16BE(0)
|
|
213
|
+
} else if (dataId === 0x17) {
|
|
214
|
+
// Motion Status
|
|
215
|
+
const motion = dataContent.readUInt8(0)
|
|
216
|
+
data.status = data.status || {}
|
|
217
|
+
data.status.motion =
|
|
218
|
+
motion === 0x00
|
|
219
|
+
? 'Motionless'
|
|
220
|
+
: motion === 0x01 ? 'Moving' : 'Unknown'
|
|
221
|
+
data.alarm = utils.getAlarm('GSENSOR', parseInt(motion).toString(2))
|
|
222
|
+
// } else if (dataId === 0x22) { // Real-time Customization
|
|
223
|
+
// data.alarm = utils.getAlarm('GTINFO', null)
|
|
224
|
+
} else if (dataId === 0x2b) {
|
|
225
|
+
// Light Sensor
|
|
226
|
+
data.lightSensor =
|
|
227
|
+
dataContent.readUInt8(0) === 0x01 ? 'Triggered' : 'Normal'
|
|
228
|
+
} else if (dataId === 0x50) {
|
|
229
|
+
// Fixed Report Information
|
|
230
|
+
data.alarm = utils.getAlarm('GTFRI', null)
|
|
231
|
+
} else if (dataId === 0x51) {
|
|
232
|
+
// 81 Mini Location
|
|
233
|
+
let locOffset = 0
|
|
234
|
+
const fixStateMode = dataContent.readUInt8(locOffset)
|
|
235
|
+
const signalStrength = (fixStateMode >> 4) & 0x0f
|
|
236
|
+
const fixState = (fixStateMode >> 2) & 0x03
|
|
237
|
+
const fixMode = fixStateMode & 0x03
|
|
238
|
+
locOffset += 1
|
|
239
|
+
|
|
240
|
+
const lon = dataContent.readInt32BE(locOffset) / 1000000
|
|
241
|
+
locOffset += 4
|
|
242
|
+
const lat = dataContent.readInt32BE(locOffset) / 1000000
|
|
243
|
+
locOffset += 4
|
|
244
|
+
|
|
245
|
+
const utcTime = dataContent.readUInt32BE(locOffset)
|
|
246
|
+
data.datetime = new Date(utcTime * 1000)
|
|
247
|
+
locOffset += 4
|
|
248
|
+
|
|
249
|
+
const speed = dataContent.readUInt16BE(locOffset) * 0.1
|
|
250
|
+
|
|
251
|
+
data.loc = { type: 'Point', coordinates: [lon, lat] }
|
|
252
|
+
data.speed = parseFloat(speed.toFixed(1))
|
|
253
|
+
data.gpsStatus = utils.checkGps(lon, lat)
|
|
254
|
+
data.gpsSignalStrength = utils.getSignalStrength(
|
|
255
|
+
'GPS',
|
|
256
|
+
signalStrength,
|
|
257
|
+
true
|
|
258
|
+
)
|
|
259
|
+
data.fixState =
|
|
260
|
+
fixState === 0
|
|
261
|
+
? 'Off'
|
|
262
|
+
: fixState === 1 ? 'No fix' : fixState === 2 ? 'Fix' : 'Unknown'
|
|
263
|
+
data.fixMode = fixMode === 0 ? '2D' : fixMode === 1 ? '3D' : 'Unknown'
|
|
264
|
+
} else if (dataId === 0x52) {
|
|
265
|
+
// 82 Full Location
|
|
266
|
+
let locOffset = 0
|
|
267
|
+
const fixStateMode = dataContent.readUInt8(locOffset)
|
|
268
|
+
const signalStrength = (fixStateMode >> 4) & 0x0f
|
|
269
|
+
const fixState = (fixStateMode >> 2) & 0x03
|
|
270
|
+
const fixMode = fixStateMode & 0x03
|
|
271
|
+
locOffset += 1
|
|
272
|
+
|
|
273
|
+
const lon = dataContent.readInt32BE(locOffset) / 1000000
|
|
274
|
+
locOffset += 4
|
|
275
|
+
const lat = dataContent.readInt32BE(locOffset) / 1000000
|
|
276
|
+
locOffset += 4
|
|
277
|
+
|
|
278
|
+
const utcTime = dataContent.readUInt32BE(locOffset)
|
|
279
|
+
data.datetime = new Date(utcTime * 1000)
|
|
280
|
+
locOffset += 4
|
|
281
|
+
|
|
282
|
+
const speed = dataContent.readUInt16BE(locOffset) * 0.1
|
|
283
|
+
locOffset += 2
|
|
284
|
+
|
|
285
|
+
const hdopByte = dataContent.readUInt8(locOffset)
|
|
286
|
+
const hdop = hdopByte === 0xff ? 25.5 : hdopByte * 0.1
|
|
287
|
+
locOffset += 1
|
|
288
|
+
|
|
289
|
+
const azimuth = dataContent.readUInt16BE(locOffset)
|
|
290
|
+
locOffset += 2
|
|
291
|
+
|
|
292
|
+
const altHigh = dataContent.readInt8(locOffset)
|
|
293
|
+
const altMid = dataContent.readUInt8(locOffset + 1)
|
|
294
|
+
const altLow = dataContent.readUInt8(locOffset + 2)
|
|
295
|
+
const altitude = ((altHigh << 16) | (altMid << 8) | altLow) * 0.1
|
|
296
|
+
locOffset += 3
|
|
297
|
+
|
|
298
|
+
const satellites = dataContent.readUInt8(locOffset)
|
|
299
|
+
|
|
300
|
+
data.loc = { type: 'Point', coordinates: [lon, lat] }
|
|
301
|
+
data.speed = parseFloat(speed.toFixed(1))
|
|
302
|
+
data.hdop = parseFloat(hdop.toFixed(1))
|
|
303
|
+
data.azimuth = azimuth
|
|
304
|
+
data.altitude = parseFloat(altitude.toFixed(1))
|
|
305
|
+
data.gpsStatus = utils.checkGps(lon, lat)
|
|
306
|
+
data.gpsSignalStrength = utils.getSignalStrength(
|
|
307
|
+
'GPS',
|
|
308
|
+
signalStrength,
|
|
309
|
+
true
|
|
310
|
+
)
|
|
311
|
+
data.fixState =
|
|
312
|
+
fixState === 0
|
|
313
|
+
? 'Off'
|
|
314
|
+
: fixState === 1 ? 'No fix' : fixState === 2 ? 'Fix' : 'Unknown'
|
|
315
|
+
data.fixMode = fixMode === 0 ? '2D' : fixMode === 1 ? '3D' : 'Unknown'
|
|
316
|
+
data.satellites = satellites
|
|
317
|
+
} else if (dataId === 0x55) {
|
|
318
|
+
// 85 Registered Cell
|
|
319
|
+
const plmnLen = dataContent.readUInt8(0)
|
|
320
|
+
const plmnVal =
|
|
321
|
+
(dataContent.readUInt8(1) << 16) |
|
|
322
|
+
(dataContent.readUInt8(2) << 8) |
|
|
323
|
+
dataContent.readUInt8(3)
|
|
324
|
+
const plmnStr = plmnVal.toString().padStart(plmnLen, '0')
|
|
325
|
+
const mccStr = plmnStr.substring(0, 3)
|
|
326
|
+
const mncStr = plmnStr.substring(3)
|
|
327
|
+
data.mcc = utils.latamMcc[parseInt(mccStr, 10)] || null
|
|
328
|
+
data.mnc = utils.getMNC(mccStr, mncStr)
|
|
329
|
+
data.lac = dataContent.readUInt16BE(4)
|
|
330
|
+
data.cid = dataContent.readUInt32BE(6)
|
|
331
|
+
// Access Tech: Bit 7 Roaming & Bit 6 - 0 Technology
|
|
332
|
+
const techRoaming = dataContent.readUInt8(10)
|
|
333
|
+
const tech = techRoaming & 0x7f
|
|
334
|
+
data.accessTechnology =
|
|
335
|
+
tech === 0 ? 'GSM' : tech === 5 ? 'LTE Cat-1' : 'Unknown'
|
|
336
|
+
data.roaming = techRoaming >> 7 === 1
|
|
337
|
+
|
|
338
|
+
const rssi = dataContent.readUInt8(12)
|
|
339
|
+
if (rssi !== 0xff && rssi !== 99) {
|
|
340
|
+
data.rssi = rssi <= 31 ? rssi * 2 - 113 : null
|
|
341
|
+
}
|
|
342
|
+
} else if (dataId === 0x58) {
|
|
343
|
+
// 88 SIM Card
|
|
344
|
+
data.imsi = dataContent
|
|
345
|
+
.slice(0, 8)
|
|
346
|
+
.toString('hex')
|
|
347
|
+
.replace(/^0/, '')
|
|
348
|
+
.replace(/f$/i, '')
|
|
349
|
+
if (dataContent.length > 8) {
|
|
350
|
+
const iccidContent = dataContent.slice(8)
|
|
351
|
+
if (iccidContent[0] === 0xff) {
|
|
352
|
+
data.iccid = iccidContent.slice(2, 2 + iccidContent[1]).toString()
|
|
353
|
+
} else {
|
|
354
|
+
data.iccid = iccidContent.toString('hex').replace(/f$/i, '')
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
} else if (dataId === 0x59) {
|
|
358
|
+
// 89 GSV
|
|
359
|
+
const svCount = dataContent.readUInt8(0)
|
|
360
|
+
data.satellites_details = []
|
|
361
|
+
for (let i = 0; i < svCount; i++) {
|
|
362
|
+
const svOffset = 1 + i * 6
|
|
363
|
+
if (svOffset + 6 <= dataContent.length) {
|
|
364
|
+
data.satellites_details.push({
|
|
365
|
+
gnssId: dataContent.readUInt8(svOffset),
|
|
366
|
+
svId: dataContent.readUInt8(svOffset + 1),
|
|
367
|
+
snr: dataContent.readUInt8(svOffset + 2),
|
|
368
|
+
elevation: dataContent.readUInt8(svOffset + 3),
|
|
369
|
+
azimuth: dataContent.readUInt16BE(svOffset + 4)
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
} else if (dataId === 0x5a) {
|
|
374
|
+
// 90 Versions
|
|
375
|
+
const mask = dataContent.readUInt8(0)
|
|
376
|
+
if (mask === 0x80) {
|
|
377
|
+
let vOffset = 1
|
|
378
|
+
while (vOffset < dataContent.length) {
|
|
379
|
+
const type = dataContent.readUInt8(vOffset++)
|
|
380
|
+
const len = dataContent.readUInt8(vOffset++)
|
|
381
|
+
const val = dataContent.slice(vOffset, vOffset + len).toString()
|
|
382
|
+
vOffset += len
|
|
383
|
+
if (type === 0x01) data.hardwareVersion = val
|
|
384
|
+
else if (type === 0x02) data.appVersion = val
|
|
385
|
+
else if (type === 0x05) data.bootloaderVersion = val
|
|
386
|
+
else if (type === 0x06) data.firmwareVersion = val
|
|
387
|
+
else if (type === 0x09) data.bleBootloaderVersion = val
|
|
388
|
+
else if (type === 0x0a) data.bleFirmwareVersion = val
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
} else if (dataId === 0x5c) {
|
|
392
|
+
// 92 GEO Status
|
|
393
|
+
data.geofence = data.geofence || {}
|
|
394
|
+
data.geofence.id = dataContent.readUInt8(0)
|
|
395
|
+
const gStat = dataContent.readUInt8(1)
|
|
396
|
+
data.geofence.status =
|
|
397
|
+
gStat === 0x00 ? 'Inside' : gStat === 0x01 ? 'Outside' : 'Unknown'
|
|
398
|
+
} else if (dataId === 0x5d) {
|
|
399
|
+
// 93 All GEO Status
|
|
400
|
+
const geofenceCount = dataLen / 2
|
|
401
|
+
data.geofences = []
|
|
402
|
+
for (let i = 0; i < geofenceCount; i++) {
|
|
403
|
+
data.geofences.push({
|
|
404
|
+
id: dataContent.readUInt8(i * 2),
|
|
405
|
+
status:
|
|
406
|
+
dataContent.readUInt8(i * 2 + 1) === 0x00 ? 'Inside' : 'Outside'
|
|
407
|
+
})
|
|
408
|
+
}
|
|
409
|
+
} else if (dataId === 0x65 || dataId === 0x66 || dataId === 0x67) {
|
|
410
|
+
// 101/102/103 Upgrade/Update/Get Config
|
|
411
|
+
const getUpgradeStatus = (c, sc) => {
|
|
412
|
+
const codes = {
|
|
413
|
+
1: 'Start download',
|
|
414
|
+
2: 'Start update',
|
|
415
|
+
3: 'Download successful',
|
|
416
|
+
4: 'Download failed',
|
|
417
|
+
5: 'Update successful',
|
|
418
|
+
6: 'Update failed'
|
|
419
|
+
}
|
|
420
|
+
const subcodes = {
|
|
421
|
+
0: 'Normal',
|
|
422
|
+
16: 'Low battery',
|
|
423
|
+
32: 'No network',
|
|
424
|
+
33: 'Connection failed',
|
|
425
|
+
48: 'File not found',
|
|
426
|
+
49: 'CRC error',
|
|
427
|
+
50: 'Type error',
|
|
428
|
+
51: 'Size error',
|
|
429
|
+
52: 'Incompatible version',
|
|
430
|
+
240: 'In progress',
|
|
431
|
+
255: 'Unknown'
|
|
432
|
+
}
|
|
433
|
+
return {
|
|
434
|
+
code: codes[c] || `Unknown (${c})`,
|
|
435
|
+
subcode: subcodes[sc] || `Unknown (${sc})`
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
const status = getUpgradeStatus(
|
|
439
|
+
dataContent.readUInt8(0),
|
|
440
|
+
dataContent.readUInt8(1)
|
|
441
|
+
)
|
|
442
|
+
const fieldName =
|
|
443
|
+
dataId === 0x65
|
|
444
|
+
? 'upgradeInfo'
|
|
445
|
+
: dataId === 0x66 ? 'updateConfig' : 'getConfig'
|
|
446
|
+
data[fieldName] = {
|
|
447
|
+
status: status.code,
|
|
448
|
+
reason: status.subcode,
|
|
449
|
+
sequence: dataContent.readUInt16BE(5)
|
|
450
|
+
}
|
|
451
|
+
} else if (dataId === 0x61) {
|
|
452
|
+
// 97 Internal Battery Status
|
|
453
|
+
data.voltage = data.voltage || {}
|
|
454
|
+
data.voltage.connected = (dataContent.readUInt8(0) & 0x01) === 1
|
|
455
|
+
data.voltage.value = dataContent.readUInt16BE(1)
|
|
456
|
+
data.voltage.battery = dataContent.readUInt8(3)
|
|
457
|
+
const charging = dataContent.readUInt8(4)
|
|
458
|
+
data.voltage.inputCharge = charging !== 0x02 && charging !== 0x007
|
|
459
|
+
} else if (dataId === 0x78) {
|
|
460
|
+
// 120 Self Test
|
|
461
|
+
data.selfTestTime = new Date(dataContent.readUInt32BE(0) * 1000)
|
|
462
|
+
const conn = dataContent.readUInt32BE(4)
|
|
463
|
+
data.selfTest = {
|
|
464
|
+
imeiAbnormal: (conn & 0x04) !== 0,
|
|
465
|
+
simAbnormal: (conn & 0x08) !== 0,
|
|
466
|
+
gnssAbnormal: (conn & 0x10) !== 0,
|
|
467
|
+
gSensorAbnormal: (conn & 0x20) !== 0,
|
|
468
|
+
bluetoothAbnormal: (conn & 0x80) !== 0,
|
|
469
|
+
bluetoothMacAbnormal: (conn & 0x100) !== 0
|
|
470
|
+
}
|
|
471
|
+
} else if (dataId === 173) {
|
|
472
|
+
// 80ADH SVR Status (ID 173 dec)
|
|
473
|
+
const appendInfo = dataContent.slice(8).toString('hex') !== '00'
|
|
474
|
+
data.bluetooth = data.bluetooth || {}
|
|
475
|
+
data.bluetooth.connection =
|
|
476
|
+
dataContent.readUInt8(0) === 0x01 ? 'Normal' : 'Abnormal'
|
|
477
|
+
data.bluetooth.role =
|
|
478
|
+
dataContent.readUInt8(1) === 0x00 ? 'Master' : 'Slave'
|
|
479
|
+
data.bluetooth.targetMac = dataContent
|
|
480
|
+
.slice(2, 8)
|
|
481
|
+
.toString('hex')
|
|
482
|
+
.toUpperCase()
|
|
483
|
+
if (appendInfo) {
|
|
484
|
+
const lostReasonHex = dataContent
|
|
485
|
+
.slice(8, dataContent.length - 1)
|
|
486
|
+
.toString('hex')
|
|
487
|
+
data.bluetooth.lostReason =
|
|
488
|
+
lostReasonHex === '03'
|
|
489
|
+
? 'Bluetooth connection failed'
|
|
490
|
+
: lostReasonHex === '07'
|
|
491
|
+
? 'Bluetooth connection successfull'
|
|
492
|
+
: lostReasonHex === '08'
|
|
493
|
+
? 'Failed to decrypt interactive data'
|
|
494
|
+
: lostReasonHex === '09'
|
|
495
|
+
? 'Data check is incorrect (after decryption)'
|
|
496
|
+
: lostReasonHex === '0B'
|
|
497
|
+
? 'No data interaction (after connection)'
|
|
498
|
+
: 'Unknown'
|
|
499
|
+
}
|
|
500
|
+
} else if (dataId === 159) {
|
|
501
|
+
// 809FH Bluetooth Info (ID 159 dec)
|
|
502
|
+
data.bluetooth = data.bluetooth || {}
|
|
503
|
+
const nameLength = dataContent.readUInt8(0)
|
|
504
|
+
data.bluetooth.name = dataContent.slice(1, nameLength + 1).toString()
|
|
505
|
+
data.bluetooth.mac = dataContent
|
|
506
|
+
.slice(nameLength + 1, nameLength + 7)
|
|
507
|
+
.toString('hex')
|
|
508
|
+
.toUpperCase()
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
data.status = data.status || null
|
|
514
|
+
data.odometer = data.odometer || null
|
|
515
|
+
data.hourmeter = data.hourmeter || null
|
|
516
|
+
data.gpsStatus = data.gpsStatus || false
|
|
517
|
+
|
|
518
|
+
return data
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
module.exports = {
|
|
522
|
+
parse: parse
|
|
523
|
+
}
|