@canboat/canboatjs 3.3.3 → 3.3.5

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.
Files changed (73) hide show
  1. package/dist/bin/actisense-file.js +2 -0
  2. package/dist/bin/actisense-file.js.map +1 -1
  3. package/dist/bin/actisense-n2k-tcp.js +2 -0
  4. package/dist/bin/actisense-n2k-tcp.js.map +1 -1
  5. package/dist/bin/actisense-serialjs.js +2 -0
  6. package/dist/bin/actisense-serialjs.js.map +1 -1
  7. package/dist/bin/analyzerjs.js +2 -0
  8. package/dist/bin/analyzerjs.js.map +1 -1
  9. package/dist/bin/candumpjs.js +7 -4
  10. package/dist/bin/candumpjs.js.map +1 -1
  11. package/dist/bin/cansend.js +2 -0
  12. package/dist/bin/cansend.js.map +1 -1
  13. package/dist/bin/to-pgn.js +2 -0
  14. package/dist/bin/to-pgn.js.map +1 -1
  15. package/dist/bin/utils.d.ts +4 -0
  16. package/dist/bin/utils.d.ts.map +1 -0
  17. package/dist/bin/utils.js +15 -0
  18. package/dist/bin/utils.js.map +1 -0
  19. package/dist/bin/ydvr-file.d.ts +3 -0
  20. package/dist/bin/ydvr-file.d.ts.map +1 -0
  21. package/dist/bin/ydvr-file.js +31 -0
  22. package/dist/bin/ydvr-file.js.map +1 -0
  23. package/dist/fromPgn.d.ts.map +1 -1
  24. package/dist/fromPgn.js +1 -0
  25. package/dist/fromPgn.js.map +1 -1
  26. package/package.json +4 -3
  27. package/tsconfig.tsbuildinfo +1 -1
  28. package/.github/workflows/publish.yml +0 -32
  29. package/.github/workflows/release_on_tag.yml +0 -27
  30. package/.github/workflows/require_pr_label.yml +0 -13
  31. package/.github/workflows/test.yml +0 -28
  32. package/.github/workflows/test_canboat_changes.yml +0 -92
  33. package/examples/signalk-device-emulator/index.js +0 -1
  34. package/examples/signalk-device-emulator/package.json +0 -24
  35. package/examples/simpleCan.js +0 -42
  36. package/ios.js +0 -67
  37. package/lib/actisense-serial.ts +0 -644
  38. package/lib/bin/actisense-file.ts +0 -53
  39. package/lib/bin/actisense-n2k-tcp.ts +0 -50
  40. package/lib/bin/actisense-serialjs.ts +0 -55
  41. package/lib/bin/analyzerjs.ts +0 -91
  42. package/lib/bin/candumpjs.ts +0 -100
  43. package/lib/bin/cansend.ts +0 -131
  44. package/lib/bin/ikonvert-serial.ts +0 -44
  45. package/lib/bin/to-pgn.ts +0 -65
  46. package/lib/bin/ydvr-file +0 -33
  47. package/lib/canId.test.js +0 -61
  48. package/lib/canId.ts +0 -84
  49. package/lib/canbus.ts +0 -293
  50. package/lib/candevice.ts +0 -41
  51. package/lib/codes.ts +0 -21
  52. package/lib/discovery.ts +0 -118
  53. package/lib/fromPgn.ts +0 -1217
  54. package/lib/fromPgnStream.ts +0 -54
  55. package/lib/ikonvert.ts +0 -250
  56. package/lib/index.ts +0 -48
  57. package/lib/n2k-actisense.test.js +0 -58
  58. package/lib/n2k-actisense.ts +0 -152
  59. package/lib/n2kDevice.ts +0 -509
  60. package/lib/pgns.test.ts +0 -12
  61. package/lib/pgns.ts +0 -191
  62. package/lib/simpleCan.ts +0 -140
  63. package/lib/stringMsg.test.js +0 -288
  64. package/lib/stringMsg.ts +0 -478
  65. package/lib/toPgn.ts +0 -601
  66. package/lib/utilities.test.js +0 -8
  67. package/lib/utilities.ts +0 -169
  68. package/lib/venus-mqtt.js +0 -118
  69. package/lib/venus.js +0 -88
  70. package/lib/w2k01.ts +0 -142
  71. package/lib/yddevice.ts +0 -48
  72. package/lib/ydgw02.ts +0 -197
  73. package/lib/ydvr.js +0 -138
package/lib/fromPgn.ts DELETED
@@ -1,1217 +0,0 @@
1
- /**
2
- * Copyright 2018 Scott Bender (scott@scottbender.net)
3
- *
4
- * Licensed under the Apache License, Version 2.0 (the "License");
5
- * you may not use this file except in compliance with the License.
6
- * You may obtain a copy of the License at
7
- *
8
- * http://www.apache.org/licenses/LICENSE-2.0
9
- *
10
- * Unless required by applicable law or agreed to in writing, software
11
- * distributed under the License is distributed on an "AS IS" BASIS,
12
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- * See the License for the specific language governing permissions and
14
- * limitations under the License.
15
- */
16
-
17
- // FIXME: MMSI sould be a string
18
-
19
- import { Definition, Field, PGN, FieldType, createPGN } from '@canboat/ts-pgns'
20
- import { createDebug } from './utilities'
21
- import { EventEmitter } from 'node:events'
22
- import pkg from '../package.json'
23
- import _ from 'lodash'
24
- import {
25
- getPgn,
26
- getCustomPgn,
27
- addCustomPgns,
28
- lookupEnumerationName,
29
- lookupFieldTypeEnumerationName,
30
- lookupBitEnumerationName,
31
- lookupFieldTypeEnumerationBits,
32
- lookupFieldTypeEnumerationValue
33
- } from './pgns'
34
- import { BitStream, BitView } from 'bit-buffer'
35
- import { Int64LE, Uint64LE } from 'int64-buffer'
36
-
37
- import {
38
- parseN2kString,
39
- parseYDRAW,
40
- isN2KOver0183,
41
- parsePDGY,
42
- parseActisenseN2KASCII
43
- } from './stringMsg'
44
-
45
- const debug = createDebug('canboatjs:fromPgn')
46
- const trace = createDebug('canboatjs:fromPgn:trace')
47
-
48
- export type FromPgnCallback = (msg: any, pgn: any | undefined) => void
49
- export type PostProcessor = (field: Field, value: any) => any
50
- type FieldTypeReader = (pgn: PGN, field: Field, bs: BitStream) => any
51
-
52
- const fieldTypeReaders: {
53
- [key: string]: FieldTypeReader
54
- } = {}
55
-
56
- const fieldTypePostProcessors: {
57
- [key: string]: PostProcessor
58
- } = {}
59
-
60
- const FORMAT_PLAIN = 0
61
- const FORMAT_COALESCED = 1
62
- const RES_BINARY = 'Binary data'
63
-
64
- const FASTPACKET_INDEX = 0
65
- const FASTPACKET_SIZE = 1
66
- const FASTPACKET_BUCKET_0_SIZE = 6
67
- const FASTPACKET_BUCKET_N_SIZE = 7
68
- const FASTPACKET_BUCKET_0_OFFSET = 2
69
- const FASTPACKET_BUCKET_N_OFFSET = 1
70
- const FASTPACKET_MAX_INDEX = 0x1f
71
-
72
- export class Parser extends EventEmitter {
73
- options: any
74
- name: string
75
- version: string
76
- author: string
77
- license: string
78
- format: number
79
- devices: { [key: number]: { [key: number]: any } }
80
- mixedFormat: boolean
81
-
82
- constructor(opts: any = {}) {
83
- super()
84
- this.options = opts === undefined ? {} : opts
85
-
86
- if (this.options.returnNulls === undefined) {
87
- this.options.returnNulls = false
88
- }
89
-
90
- if (this.options.useCamel === undefined) {
91
- this.options.useCamel = true
92
- }
93
-
94
- if (this.options.useCamelCompat === undefined) {
95
- this.options.useCamelCompat = false
96
- }
97
-
98
- if (this.options.returnNonMatches === undefined) {
99
- this.options.returnNonMatches = false
100
- }
101
-
102
- this.name = pkg.name
103
- this.version = pkg.version
104
- this.author = pkg.author
105
- this.license = pkg.license
106
- this.format = this.options.format === undefined ? -1 : this.options.format
107
- this.devices = {}
108
- this.mixedFormat = this.options.mixedFormat || false
109
-
110
- if (this.options.onPropertyValues) {
111
- this.options.onPropertyValues('canboat-custom-pgns', (values: any[]) => {
112
- values
113
- .filter((v) => v != null)
114
- .forEach((pv) => {
115
- addCustomPgns(pv.value, pv.setter)
116
- })
117
- })
118
- }
119
- }
120
-
121
- _parse(
122
- pgn: PGN,
123
- bs: BitStream,
124
- len: number,
125
- coalesced: boolean,
126
- cb: FromPgnCallback | undefined,
127
- sourceString: string | undefined = undefined
128
- ) {
129
- if (pgn.src === undefined) {
130
- throw new Error('invalid pgn, missing src')
131
- }
132
-
133
- const customPgns = getCustomPgn(pgn.pgn)
134
- let pgnList = getPgn(pgn.pgn)
135
-
136
- if (!pgnList && !customPgns) {
137
- this.emit(
138
- 'warning',
139
- pgn,
140
- `no conversion found for pgn ${JSON.stringify(pgn)}`
141
- )
142
- return undefined
143
- }
144
-
145
- if (customPgns) {
146
- pgnList = [...customPgns.definitions, ...(pgnList || [])]
147
- }
148
-
149
- if (!pgnList || pgnList.length === 0) {
150
- this.emit(
151
- 'warning',
152
- pgn,
153
- `no conversion found for pgn ${JSON.stringify(pgn)}`
154
- )
155
- return undefined
156
- }
157
-
158
- if (pgnList === undefined) {
159
- return
160
- }
161
-
162
- let pgnData: Definition | undefined
163
-
164
- if (pgnList.length > 1) {
165
- pgnData = this.findMatchPgn(pgnList)
166
-
167
- if (pgnData === null) {
168
- pgnData = pgnList[0]
169
- }
170
- } else {
171
- pgnData = pgnList[0]
172
- }
173
-
174
- if (pgnData === undefined) {
175
- return
176
- }
177
-
178
- let couldBeMulti = false
179
-
180
- if (pgnList.length > 0 && len == 8) {
181
- pgnList.forEach((pgnD) => {
182
- if (pgnD.Length && pgnD.Length > 8) {
183
- couldBeMulti = true
184
- }
185
- })
186
- }
187
-
188
- trace(`${pgn.pgn} ${len} ${pgnData.Length} ${couldBeMulti}`)
189
- if (
190
- coalesced ||
191
- len > 0x8 ||
192
- (this.format == FORMAT_COALESCED && !this.mixedFormat)
193
- ) {
194
- this.format = FORMAT_COALESCED
195
- if (sourceString) {
196
- pgn.input = [sourceString]
197
- }
198
- //} else if ( pgnData.Length > 0x8 || (len == 0x8 && (pgnData.RepeatingFields || couldBeMulti))) {
199
- } else if (pgnData.Type === 'Fast') {
200
- //partial packet
201
- this.format = FORMAT_PLAIN
202
-
203
- if (this.devices[pgn.src] === undefined) {
204
- this.devices[pgn.src] = {}
205
- }
206
- let packet = this.devices[pgn.src][pgn.pgn]
207
-
208
- if (!packet) {
209
- packet = { bufferSize: 0, lastPacket: 0, src: [] }
210
- this.devices[pgn.src][pgn.pgn] = packet
211
- }
212
- if (sourceString) {
213
- packet.src.push(sourceString)
214
- }
215
-
216
- const start = bs.byteIndex
217
- const packetIndex = bs.view.buffer.readUInt8(FASTPACKET_INDEX)
218
- const bucket = packetIndex & FASTPACKET_MAX_INDEX
219
-
220
- trace(`${pgn.pgn} partial ${packetIndex} ${bucket} ${packet.size}`)
221
-
222
- if (bucket == 0) {
223
- packet.size = bs.view.buffer.readUInt8(FASTPACKET_SIZE)
224
- const newSize = packet.size + FASTPACKET_BUCKET_N_SIZE
225
- if (newSize > packet.bufferSize) {
226
- const newBuf = Buffer.alloc(newSize)
227
- packet.bufferSize = newSize
228
- if (packet.buffer) {
229
- packet.buffer.copy(newBuf)
230
- }
231
- packet.buffer = newBuf
232
- }
233
- bs.view.buffer.copy(packet.buffer, 0, FASTPACKET_BUCKET_0_OFFSET, 8)
234
- trace(
235
- `${pgn.pgn} targetStart: 0 sourceStart: ${FASTPACKET_BUCKET_0_OFFSET}`
236
- )
237
- } else if (!packet.buffer) {
238
- //we got a non-zero bucket, but we never got the zero bucket
239
- debug(
240
- `PGN ${pgn.pgn} malformed packet for ${pgn.src} received; got a non-zero bucket first`
241
- )
242
- cb && cb(`Could not parse ${JSON.stringify(pgn)}`, undefined)
243
- bs.byteIndex = start
244
- delete this.devices[pgn.src][pgn.pgn]
245
- return
246
- } else {
247
- if (packet.lastPacket + 1 != packetIndex) {
248
- debug(
249
- `PGN ${pgn.pgn} malformed packet for ${pgn.src} received; expected ${packet.lastPacket + 1} but got ${packetIndex}`
250
- )
251
- cb && cb(`Could not parse ${JSON.stringify(pgn)}`, undefined)
252
- bs.byteIndex = start
253
- delete this.devices[pgn.src][pgn.pgn]
254
- return
255
- } else {
256
- trace(
257
- `${pgn.pgn} targetStart: ${FASTPACKET_BUCKET_0_SIZE + FASTPACKET_BUCKET_N_SIZE * (bucket - 1)} sourceStart: ${FASTPACKET_BUCKET_N_OFFSET} sourceEned: ${FASTPACKET_BUCKET_N_SIZE}`
258
- )
259
- bs.view.buffer.copy(
260
- packet.buffer,
261
- FASTPACKET_BUCKET_0_SIZE + FASTPACKET_BUCKET_N_SIZE * (bucket - 1),
262
- FASTPACKET_BUCKET_N_OFFSET,
263
- 8
264
- )
265
- }
266
- }
267
- packet.lastPacket = packetIndex
268
- if (
269
- FASTPACKET_BUCKET_0_SIZE + FASTPACKET_BUCKET_N_SIZE * bucket <
270
- packet.size
271
- ) {
272
- // Packet is not complete yet
273
- trace(`${pgn.pgn} not complete`)
274
- return
275
- }
276
- const view = new BitView(packet.buffer)
277
- bs = new BitStream(view)
278
- trace(`${pgn.pgn} done`)
279
- pgn.input = packet.src
280
- delete this.devices[pgn.src][pgn.pgn]
281
- } else if (sourceString) {
282
- pgn.input = [sourceString]
283
- }
284
-
285
- let RepeatingFields = pgnData.RepeatingFieldSet1Size
286
- ? pgnData.RepeatingFieldSet1Size
287
- : 0
288
-
289
- pgn.fields = {}
290
-
291
- try {
292
- let fields = pgnData.Fields
293
-
294
- let continueReading = true
295
- for (let i = 0; i < fields.length - RepeatingFields; i++) {
296
- const field = fields[i]
297
- const hasMatch = field.Match !== undefined
298
-
299
- let value = readField(this.options, !hasMatch, pgn, field, bs, fields)
300
-
301
- if (hasMatch) {
302
- //console.log(`looking for ${field.Name} == ${value}`)
303
- //console.log(JSON.stringify(pgnList, null, 2))
304
- pgnList = pgnList.filter((f) => f.Fields[i].Match == value)
305
- if (pgnList.length == 0) {
306
- if (this.options.returnNonMatches) {
307
- //this.emit('warning', pgn, `no conversion found for pgn`)
308
- trace('warning no conversion found for pgn %j', pgn)
309
- continueReading = false
310
- break
311
- } else {
312
- return undefined
313
- }
314
- } else {
315
- pgnData = pgnList[0]
316
- fields = pgnData.Fields
317
- //console.log(`using ${JSON.stringify(pgnData, null, 2)}`)
318
- value = pgnData.Fields[i].Description
319
- if (value == null) {
320
- value = pgnData.Fields[i].Match
321
- }
322
- RepeatingFields = pgnData.RepeatingFieldSet1Size
323
- ? pgnData.RepeatingFieldSet1Size
324
- : 0
325
- }
326
- }
327
-
328
- if (
329
- value !== undefined &&
330
- (value != null || this.options.returnNulls)
331
- ) {
332
- this.setField(pgn.fields, field, value)
333
- }
334
- }
335
- if (RepeatingFields > 0 && continueReading) {
336
- const repeating: Field[] = (fields as any).slice(
337
- fields.length - RepeatingFields
338
- )
339
-
340
- const fany = pgn.fields as any
341
- fany.list = []
342
-
343
- let count
344
-
345
- if (pgnData.RepeatingFieldSet1CountField !== undefined) {
346
- const rfield =
347
- pgnData.Fields[pgnData.RepeatingFieldSet1CountField - 1]
348
- const dataKey = this.options.useCamel ? rfield.Id : rfield.Name
349
- count = (pgn.fields as any)[dataKey]
350
- } else {
351
- count = 2048
352
- }
353
-
354
- while (bs.bitsLeft > 0 && --count >= 0) {
355
- const group = {}
356
- repeating.forEach((field) => {
357
- if (bs.bitsLeft > 0) {
358
- const value = readField(
359
- this.options,
360
- true,
361
- pgn,
362
- field,
363
- bs,
364
- fields
365
- )
366
- if (value !== undefined && value != null) {
367
- this.setField(group, field, value)
368
- }
369
- }
370
- })
371
- if (_.keys(group).length > 0) {
372
- ;(pgn.fields as any).list.push(group)
373
- }
374
- }
375
- }
376
-
377
- /*
378
- if ( pgnData.callback ) {
379
- pgnData.callback(pgn)
380
- }
381
- */
382
-
383
- const res = createPGN(pgnData.Id, pgn.fields)
384
-
385
- if (res === undefined) {
386
- this.emit('error', pgn, 'no class')
387
- cb && cb('no class', undefined)
388
- return
389
- }
390
-
391
- res.description = pgnData.Description
392
- res.src = pgn.src
393
- res.dst = pgn.dst
394
- res.prio = pgn.prio
395
- ;(res as any).canId = (pgn as any).canId
396
- ;(res as any).time = (pgn as any).time
397
- ;(res as any).timer = (pgn as any).timer
398
- ;(res as any).direction = (pgn as any).direction
399
-
400
- // Stringify timestamp because SK Server needs it that way.
401
- const ts = _.get(pgn, 'timestamp', new Date())
402
- res.timestamp = _.isDate(ts) ? ts.toISOString() : ts
403
- this.emit('pgn', res)
404
- cb && cb(undefined, res)
405
-
406
- return res
407
- } catch (error) {
408
- this.emit('error', pgn, error)
409
- cb && cb(error, undefined)
410
- return
411
- }
412
- }
413
-
414
- setField(res: any, field: Field, value: any) {
415
- if (this.options.useCamelCompat) {
416
- res[field.Id] = value
417
- res[field.Name] = value
418
- } else if (this.options.useCamel) {
419
- res[field.Id] = value
420
- } else {
421
- res[field.Name] = value
422
- }
423
- }
424
-
425
- findNonMatchPgn(pgnList: Definition[]): Definition | undefined {
426
- return pgnList.find((f) => {
427
- return !f.Fields.find((f) => f.Match !== undefined)
428
- })
429
- }
430
-
431
- findMatchPgn(pgnList: Definition[]): Definition | undefined {
432
- return pgnList.find((f) => {
433
- return f.Fields.find((f) => f.Match !== undefined)
434
- })
435
- }
436
-
437
- parse(data: any, cb: FromPgnCallback | undefined = undefined) {
438
- if (_.isString(data)) {
439
- return this.parseString(data, cb)
440
- } else if (_.isBuffer(data)) {
441
- return this.parseBuffer(data, cb)
442
- } else {
443
- return this.parsePgnData(
444
- data.pgn,
445
- data.length,
446
- data.data,
447
- data.coalesced === true,
448
- cb,
449
- data.sourceString
450
- )
451
- }
452
- }
453
-
454
- parsePgnData(
455
- pgn: PGN,
456
- length: number,
457
- data: string[] | Buffer,
458
- coalesced: boolean,
459
- cb: FromPgnCallback | undefined,
460
- sourceString: string
461
- ) {
462
- try {
463
- let buffer = data
464
- if (!_.isBuffer(data)) {
465
- const array = new Int16Array(length)
466
- const strings = data as string[]
467
- strings.forEach((num, index) => {
468
- array[index] = parseInt(num, 16)
469
- })
470
- buffer = Buffer.from(array)
471
- }
472
-
473
- const bv = new BitView(buffer as Buffer)
474
- const bs = new BitStream(bv)
475
- const res = this._parse(pgn, bs, length, coalesced, cb, sourceString)
476
- if (res) {
477
- debug('parsed pgn %j', pgn)
478
- }
479
- return res
480
- } catch (error) {
481
- cb && cb(error, undefined)
482
- this.emit('error', pgn, error)
483
- }
484
- }
485
-
486
- isN2KOver0183(sentence: string) {
487
- return isN2KOver0183(sentence)
488
- }
489
-
490
- parseN2KOver0183(sentence: string, cb: FromPgnCallback) {
491
- return this.parseString(sentence, cb)
492
- }
493
-
494
- /*
495
- // Venus MQTT-N2K
496
- parseVenusMQTT(pgn_data: any, cb: FromPgnCallback) {
497
- try {
498
- const pgn = {
499
- pgn: pgn_data.pgn,
500
- timestamp: new Date().toISOString(),
501
- src: pgn_data.src,
502
- dst: pgn_data.dst,
503
- prio: pgn_data.prio,
504
- fields: {}
505
- }
506
- const bs = new BitStream(Buffer.from(pgn_data.data, 'base64'))
507
- delete pgn_data.data
508
- const res = this._parse(pgn, bs, 8, false, cb)
509
- if (res) {
510
- debug('parsed pgn %j', pgn)
511
- }
512
- return res
513
- } catch (error) {
514
- cb && cb(error, undefined)
515
- this.emit('error', pgn_data, error)
516
- }
517
- }
518
- */
519
-
520
- //Yacht Devices NMEA2000 Wifi gateway
521
- parseYDGW02(pgn_data: any, cb: FromPgnCallback) {
522
- try {
523
- const { data, direction, error, ...pgn } = parseYDRAW(pgn_data)
524
- if (!error && direction === 'R') {
525
- const bs = new BitStream(data)
526
- delete pgn.format
527
- const res = this._parse(pgn, bs, data.length, false, cb, pgn_data)
528
- if (res) {
529
- debug('parsed ydgw02 pgn %j', pgn_data)
530
- return res
531
- }
532
- } else if (error) {
533
- cb && cb(error, undefined)
534
- this.emit('error', pgn_data, error)
535
- }
536
- } catch (error) {
537
- cb && cb(error, undefined)
538
- this.emit('error', pgn_data, error)
539
- }
540
- return undefined
541
- }
542
-
543
- //Actisense W2k-1
544
- parseActisenceN2KAscii(pgn_data: any, cb: FromPgnCallback) {
545
- try {
546
- const { data, error, ...pgn } = parseActisenseN2KASCII(pgn_data)
547
- if (!error) {
548
- const bs = new BitStream(data)
549
- delete pgn.format
550
- const res = this._parse(pgn, bs, data.length, false, cb, pgn_data)
551
- if (res) {
552
- debug('parsed n2k ascii pgn %j', pgn_data)
553
- return res
554
- }
555
- } else if (error) {
556
- cb && cb(error, undefined)
557
- this.emit('error', pgn_data, error)
558
- }
559
- } catch (error) {
560
- cb && cb(error, undefined)
561
- this.emit('error', pgn_data, error)
562
- }
563
- return undefined
564
- }
565
-
566
- parsePDGY(pgn_data: any, cb: FromPgnCallback) {
567
- if (pgn_data[0] != '!') {
568
- return
569
- }
570
- try {
571
- const { coalesced, data, error, len, ...pgn } = parsePDGY(pgn_data)
572
- if (error) {
573
- cb && cb(error, undefined)
574
- this.emit('error', pgn, error)
575
- return
576
- }
577
-
578
- const bs = new BitStream(data)
579
- delete pgn.format
580
- delete pgn.type
581
- delete pgn.prefix
582
- const res = this._parse(
583
- pgn,
584
- bs,
585
- len || data.length,
586
- coalesced,
587
- cb,
588
- pgn_data
589
- )
590
- if (res) {
591
- debug('parsed pgn %j', pgn)
592
- }
593
- return res
594
- } catch (error) {
595
- cb && cb(error, undefined)
596
- this.emit('error', pgn_data, error)
597
- }
598
- }
599
-
600
- parseString(pgn_data: string, cb: FromPgnCallback | undefined = undefined) {
601
- try {
602
- const { coalesced, data, error, len, ...pgn } = parseN2kString(
603
- pgn_data,
604
- this.options
605
- )
606
- if (error) {
607
- cb && cb(error, undefined)
608
- this.emit('error', pgn, error)
609
- return
610
- }
611
-
612
- const bs = new BitStream(data)
613
- delete pgn.format
614
- delete pgn.type
615
- delete pgn.prefix
616
- const res = this._parse(
617
- pgn,
618
- bs,
619
- len || data.length,
620
- coalesced,
621
- cb,
622
- pgn_data
623
- )
624
- if (res) {
625
- debug('parsed pgn %j', pgn)
626
- }
627
- return res
628
- } catch (error) {
629
- cb && cb(error, undefined)
630
- this.emit('error', pgn_data, error)
631
- }
632
- }
633
-
634
- parseBuffer(pgn_data: any, cb: FromPgnCallback | undefined) {
635
- try {
636
- const bv = new BitView(pgn_data)
637
- const bs = new BitStream(bv)
638
-
639
- const pgn: any = {}
640
-
641
- // This might be good to move to canId.js ?
642
- pgn.prio = bs.readUint8()
643
- pgn.pgn = bs.readUint8() + 256 * (bs.readUint8() + 256 * bs.readUint8())
644
- pgn.dst = bs.readUint8()
645
- pgn.src = bs.readUint8()
646
- pgn.timestamp = new Date().toISOString()
647
-
648
- //const timestamp = FIXME?? use timestamp?
649
- bs.readUint32()
650
- const len = bs.readUint8()
651
- const res = this._parse(pgn, bs, len, true, cb)
652
- if (res) {
653
- debug('parsed pgn %j', pgn)
654
- }
655
- return res
656
- } catch (error) {
657
- const err = new Error(
658
- `error reading pgn ${JSON.stringify(pgn_data)} ${error}`
659
- )
660
- cb && cb(err, undefined)
661
- this.emit('error', pgn_data, error)
662
- console.error(err)
663
- return
664
- }
665
- }
666
- }
667
-
668
- export function getField(pgn_number: number, index: number, data: any) {
669
- let pgnList = getPgn(pgn_number)
670
- if (pgnList) {
671
- let pgn = pgnList[0]
672
- const dataList = data.list ? data.list : data.fields.list
673
-
674
- if (pgnList.length > 1) {
675
- let idx = 0
676
- while (idx < pgn.Fields.length) {
677
- const field = pgn.Fields[idx]
678
- const hasMatch = !_.isUndefined(field.Match)
679
- if (hasMatch && dataList.length > 0) {
680
- const param = dataList.find((f: any) => {
681
- const param = f.parameter !== undefined ? f.parameter : f.Parameter
682
- return param === idx + 1
683
- })
684
-
685
- if (param) {
686
- pgnList = pgnList.filter((f) => {
687
- const value =
688
- param.value !== undefined ? param.value : param.Value
689
- return f.Fields[idx].Match == value
690
- })
691
- if (pgnList.length == 0) {
692
- throw new Error('unable to read: ' + JSON.stringify(data))
693
- return
694
- } else {
695
- pgn = pgnList[0]
696
- }
697
- }
698
- }
699
- idx++
700
- }
701
- }
702
-
703
- if (index >= 0 && index < pgn.Fields.length) {
704
- return pgn.Fields[index]
705
- }
706
-
707
- const RepeatingFields = pgn.RepeatingFieldSet1Size
708
- ? pgn.RepeatingFieldSet1Size
709
- : 0
710
- if (RepeatingFields) {
711
- const startOfRepeatingFields = pgn.Fields.length - RepeatingFields
712
- index =
713
- startOfRepeatingFields +
714
- ((index - startOfRepeatingFields) % RepeatingFields)
715
- return pgn.Fields[index]
716
- }
717
- }
718
- return null
719
- }
720
-
721
- function pad2(x: number) {
722
- const s = x.toString()
723
- return s.length === 1 ? '0' + x : x
724
- }
725
-
726
- function lookup(field: Field, value: number) {
727
- let name
728
- if (field.LookupEnumeration) {
729
- name = lookupEnumerationName(field.LookupEnumeration, value)
730
- } else {
731
- name = lookupFieldTypeEnumerationName(
732
- field.LookupFieldTypeEnumeration,
733
- value
734
- )
735
- }
736
-
737
- return name ? name : value
738
- }
739
-
740
- function readField(
741
- options: any,
742
- runPostProcessor: boolean,
743
- pgn: PGN,
744
- field: Field,
745
- bs: BitStream,
746
- fields: Field[] | undefined = undefined
747
- ): any {
748
- let value
749
-
750
- const reader = fieldTypeReaders[field.FieldType]
751
- if (reader) {
752
- value = reader(pgn, field, bs)
753
- } else {
754
- if (
755
- field.FieldType !== FieldType.Binary &&
756
- field.BitLength !== undefined &&
757
- bs.bitsLeft < field.BitLength
758
- ) {
759
- //no more data
760
- bs.readBits(bs.bitsLeft, false)
761
- return
762
- }
763
- value = readValue(options, pgn, field, bs, fields)
764
- }
765
-
766
- //console.log(`${field.Name} ${value} ${field.Resolution}`)
767
-
768
- if (value != null && !_.isUndefined(value)) {
769
- const type = field.FieldType //hack, missing type
770
- const postProcessor = fieldTypePostProcessors[type]
771
- if (postProcessor) {
772
- if (runPostProcessor) {
773
- value = postProcessor(field, value)
774
- }
775
- } else {
776
- if (field.Offset) {
777
- value += field.Offset
778
- }
779
- let max
780
- if (typeof field.RangeMax !== 'undefined' && field.Resolution) {
781
- max = field.RangeMax / field.Resolution
782
- }
783
- if (
784
- options.checkForInvalidFields !== false &&
785
- max !== undefined &&
786
- field.FieldType !== 'LOOKUP' &&
787
- field.FieldType !== 'DYNAMIC_FIELD_KEY' &&
788
- field.FieldType !== 'PGN' &&
789
- field.BitLength !== undefined &&
790
- field.BitLength > 1 &&
791
- max - value < 0
792
- ) {
793
- //console.log(`Bad field ${field.Name} ${max - value}`)
794
- value = null
795
- }
796
- if (field.Resolution && typeof value === 'number') {
797
- let resolution = field.Resolution
798
-
799
- if (_.isString(resolution)) {
800
- resolution = Number.parseFloat(resolution)
801
- }
802
-
803
- value = value * resolution
804
-
805
- let precision = 0
806
- for (let r = resolution; r > 0.0 && r < 1.0; r = r * 10.0) {
807
- precision++
808
- }
809
-
810
- value = Number.parseFloat(value.toFixed(precision))
811
- }
812
-
813
- if (
814
- (field.FieldType === 'LOOKUP' ||
815
- field.FieldType === 'DYNAMIC_FIELD_KEY') &&
816
- runPostProcessor &&
817
- (_.isUndefined(options.resolveEnums) || options.resolveEnums)
818
- ) {
819
- if (field.Id === 'timeStamp' && value < 60) {
820
- value = value.toString()
821
- } else {
822
- value = lookup(field, value)
823
- }
824
- }
825
-
826
- /*
827
- if ( field.Name === 'Industry Code' && _.isNumber(value) && runPostProcessor ) {
828
- const name = getIndustryName(value)
829
- if ( name ) {
830
- value = name
831
- }
832
- }
833
- */
834
-
835
- if (field.Unit === 'kWh') {
836
- value *= 3.6e6 // 1 kWh = 3.6 MJ.
837
- } else if (field.Unit === 'Ah') {
838
- value *= 3600.0 // 1 Ah = 3600 C.
839
- }
840
- }
841
- }
842
- return value
843
- }
844
-
845
- function readValue(
846
- options: any,
847
- pgn: PGN,
848
- field: Field,
849
- bs: BitStream,
850
- fields: Field[] | undefined,
851
- bitLength: number | undefined = undefined
852
- ) {
853
- if (field.FieldType == 'VARIABLE') {
854
- return readVariableLengthField(options, pgn, field, bs)
855
- } else {
856
- let value
857
- if (bitLength === undefined) {
858
- if (
859
- field.BitLengthVariable &&
860
- field.FieldType === 'DYNAMIC_FIELD_VALUE'
861
- ) {
862
- bitLength = lookupKeyBitLength(pgn.fields, fields as Field[])
863
- } else {
864
- bitLength = field.BitLength
865
- }
866
-
867
- if (bitLength === undefined) {
868
- //FIXME?? error? mesg? should never happen
869
- return
870
- }
871
- }
872
-
873
- if (bitLength === 8) {
874
- if (field.Signed) {
875
- value = bs.readInt8()
876
- value = value === 0x7f ? null : value
877
- } else {
878
- value = bs.readUint8()
879
- value = value === 0xff ? null : value
880
- }
881
- } else if (bitLength == 16) {
882
- if (field.Signed) {
883
- value = bs.readInt16()
884
- value = value === 0x7fff ? null : value
885
- } else {
886
- value = bs.readUint16()
887
- value = value === 0xffff ? null : value
888
- }
889
- } else if (bitLength == 24) {
890
- const b1 = bs.readUint8()
891
- const b2 = bs.readUint8()
892
- const b3 = bs.readUint8()
893
-
894
- //debug(`24 bit ${b1.toString(16)} ${b2.toString(16)} ${b3.toString(16)}`)
895
- value = (b3 << 16) + (b2 << 8) + b1
896
- //debug(`value ${value.toString(16)}`)
897
- } else if (bitLength == 32) {
898
- if (field.Signed) {
899
- value = bs.readInt32()
900
- value = value === 0x7fffffff ? null : value
901
- } else {
902
- value = bs.readUint32()
903
- value = value === 0xffffffff ? null : value
904
- }
905
- } else if (bitLength == 48) {
906
- const a = bs.readUint32()
907
- const b = bs.readUint16()
908
-
909
- if (field.Signed) {
910
- value = a == 0xffffffff && b == 0x7fff ? null : new Int64LE(b, a)
911
- } else {
912
- value = a == 0xffffffff && b == 0xffff ? null : new Int64LE(b, a)
913
- }
914
- } else if (bitLength == 64) {
915
- const x = bs.readUint32()
916
- const y = bs.readUint32()
917
-
918
- if (field.Signed) {
919
- value =
920
- (x === 0xffffffff || x === 0xfffffffe) && y == 0x7fffffff
921
- ? null
922
- : new Int64LE(y, x)
923
- } else {
924
- value =
925
- (x === 0xffffffff || x === 0xfffffffe) && y == 0xffffffff
926
- ? null
927
- : new Uint64LE(y, x)
928
- }
929
- } else if (bitLength <= 64) {
930
- value = bs.readBits(bitLength, field.Signed)
931
- if (bitLength > 1 && isMax(bitLength, value, field.Signed as boolean)) {
932
- value = null
933
- }
934
- } else {
935
- if (bs.bitsLeft < bitLength) {
936
- bitLength = bs.bitsLeft
937
- if (bitLength === undefined) {
938
- return null
939
- }
940
- }
941
-
942
- value = bs.readArrayBuffer(bitLength / 8) //, field.Signed)
943
- const arr: string[] = []
944
- value = new Uint32Array(value)
945
- .reduce(function (acc, i) {
946
- acc.push(i.toString(16))
947
- return acc
948
- }, arr)
949
- .map((x) => (x.length === 1 ? '0' + x : x))
950
- .join(' ')
951
-
952
- return value
953
- }
954
-
955
- if (
956
- value != null &&
957
- typeof value !== 'undefined' &&
958
- typeof value !== 'number'
959
- ) {
960
- value = Number(value)
961
- }
962
-
963
- return value
964
- }
965
- }
966
-
967
- function isMax(numBits: number, value: number, signed: boolean) {
968
- if (signed) {
969
- numBits--
970
- }
971
-
972
- while (numBits--) {
973
- if ((value & 1) == 0) {
974
- return false
975
- }
976
- value = value >> 1
977
- }
978
- return signed ? (value & 1) == 0 : true
979
- }
980
-
981
- function readVariableLengthField(
982
- options: any,
983
- pgn: PGN,
984
- field: Field,
985
- bs: BitStream
986
- ) {
987
- /* PGN 126208 contains variable field length.
988
- * The field length can be derived from the PGN mentioned earlier in the message,
989
- * plus the field number.
990
- */
991
-
992
- /*
993
- * This is rather hacky. We know that the 'data' pointer points to the n-th variable field
994
- * length and thus that the field number is exactly one byte earlier.
995
- */
996
-
997
- try {
998
- const refField = getField(
999
- (pgn.fields as any).pgn || (pgn.fields as any).PGN,
1000
- bs.view.buffer[bs.byteIndex - 1] - 1,
1001
- pgn
1002
- )
1003
-
1004
- if (refField) {
1005
- const res = readField(options, false, pgn, refField, bs)
1006
-
1007
- if (refField.BitLength !== undefined) {
1008
- const bits = (refField.BitLength + 7) & ~7 // Round # of bits in field refField up to complete bytes: 1->8, 7->8, 8->8 etc.
1009
- if (bits > refField.BitLength) {
1010
- bs.readBits(bits - refField.BitLength, false)
1011
- }
1012
- }
1013
-
1014
- return res
1015
- }
1016
- } catch (error) {
1017
- debug(error)
1018
- }
1019
- }
1020
-
1021
- fieldTypeReaders[
1022
- 'STRING_LAU'
1023
- //'ASCII or UNICODE string starting with length and control byte'
1024
- ] = (pgn, field, bs) => {
1025
- if (bs.bitsLeft >= 16) {
1026
- const len = bs.readUint8() - 2
1027
- const control = bs.readUint8()
1028
- let nameLen = len
1029
-
1030
- if (field.Name === 'AtoN Name' && len > 20) {
1031
- nameLen = 20
1032
- } else if (len <= 0) {
1033
- return null
1034
- }
1035
-
1036
- const buf = Buffer.alloc(len)
1037
- let idx = 0
1038
- for (; idx < len && bs.bitsLeft >= 8; idx++) {
1039
- const c = bs.readUint8()
1040
- buf.writeUInt8(c, idx)
1041
- }
1042
-
1043
- if (buf[buf.length - 1] === 0) {
1044
- nameLen = nameLen - 1
1045
- }
1046
-
1047
- return buf
1048
- .toString(
1049
- control == 0 ? 'utf8' : 'ascii',
1050
- 0,
1051
- idx < nameLen ? idx : nameLen
1052
- )
1053
- .trim()
1054
- } else {
1055
- return null
1056
- }
1057
- }
1058
-
1059
- fieldTypeReaders[
1060
- 'STRING_LZ'
1061
- //'ASCII string starting with length byte'
1062
- ] = (pgn, field, bs) => {
1063
- const len = bs.readUint8()
1064
-
1065
- const buf = Buffer.alloc(len)
1066
- let idx = 0
1067
- for (; idx < len && bs.bitsLeft >= 8; idx++) {
1068
- const c = bs.readUint8()
1069
- buf.writeUInt8(c, idx)
1070
- }
1071
-
1072
- return buf.toString('utf-8', 0, idx)
1073
- }
1074
-
1075
- fieldTypeReaders['String with start/stop byte'] = (pgn, field, bs) => {
1076
- const first = bs.readUint8()
1077
- if (first == 0xff) {
1078
- // no name, stop reading
1079
- return ''
1080
- } else if (first == 0x02) {
1081
- const buf = Buffer.alloc(255)
1082
- let c
1083
- let idx = 0
1084
- while ((c = bs.readUint8()) != 0x01) {
1085
- buf.writeUInt8(c, idx++)
1086
- }
1087
- return buf.toString('ascii', 0, idx)
1088
- } else if (first > 0x02) {
1089
- let len = first
1090
- const second = bs.readUint8()
1091
- const buf = Buffer.alloc(len)
1092
- let idx = 0
1093
- if (second == 0x01) {
1094
- len -= 2
1095
- } else {
1096
- buf.writeUInt8(second)
1097
- idx = 1
1098
- }
1099
- for (; idx < len; idx++) {
1100
- const c = bs.readUint8()
1101
- buf.writeUInt8(c, idx)
1102
- }
1103
- return buf.toString('ascii', 0, idx)
1104
- }
1105
- }
1106
-
1107
- fieldTypeReaders['STRING_FIX'] = (pgn, field, bs) => {
1108
- let len = (field.BitLength as number) / 8
1109
- const buf = Buffer.alloc(len)
1110
-
1111
- for (let i = 0; i < len && bs.bitsLeft >= 8; i++) {
1112
- buf.writeUInt8(bs.readUint8(), i)
1113
- }
1114
-
1115
- let lastbyte = buf[len - 1]
1116
- while (
1117
- len > 0 &&
1118
- (lastbyte == 0xff || lastbyte == 32 || lastbyte == 0 || lastbyte == 64)
1119
- ) {
1120
- len--
1121
- lastbyte = buf[len - 1]
1122
- }
1123
-
1124
- //look for a zero byte, some proprietary Raymarine pgns do this
1125
- let zero = 0
1126
- while (zero < len) {
1127
- if (buf[zero] == 0) {
1128
- len = zero
1129
- break
1130
- }
1131
- zero++
1132
- }
1133
- len = zero
1134
- return len > 0 ? buf.toString('ascii', 0, len) : undefined
1135
- }
1136
-
1137
- fieldTypeReaders['BITLOOKUP'] = (pgn, field, bs) => {
1138
- const value: any[] = []
1139
- for (let i = 0; i < (field.BitLength as number); i++) {
1140
- if (bs.readBits(1, false)) {
1141
- value.push(
1142
- lookupBitEnumerationName(field.LookupBitEnumeration as string, i)
1143
- )
1144
- }
1145
- }
1146
- return value
1147
- }
1148
-
1149
- function lookupKeyBitLength(data: any, fields: Field[]): number | undefined {
1150
- const field = fields.find((field) => field.Name === 'Key')
1151
-
1152
- if (field) {
1153
- let val = data['Key'] || data['key']
1154
- if (typeof val === 'string') {
1155
- val = lookupFieldTypeEnumerationValue(
1156
- field.LookupFieldTypeEnumeration,
1157
- val
1158
- )
1159
- }
1160
- return lookupFieldTypeEnumerationBits(field.LookupFieldTypeEnumeration, val)
1161
- }
1162
- }
1163
-
1164
- fieldTypePostProcessors['DATE'] = (field, value) => {
1165
- if (value >= 0xfffd) {
1166
- value = undefined
1167
- } else {
1168
- const date = new Date(value * 86400 * 1000)
1169
- //const date = moment.unix(0).add(value+1, 'days').utc().toDate()
1170
- value = `${date.getUTCFullYear()}.${pad2(date.getUTCMonth() + 1)}.${pad2(date.getUTCDate())}`
1171
- }
1172
- return value
1173
- }
1174
-
1175
- fieldTypePostProcessors['TIME'] = (field, value) => {
1176
- if (value >= 0xfffffffd) {
1177
- value = undefined
1178
- } else {
1179
- let seconds = value * (field.Resolution as number)
1180
- let minutes = seconds / 60
1181
- seconds = seconds % 60
1182
- const hours = Math.floor(minutes / 60)
1183
- minutes = Math.floor(minutes % 60)
1184
-
1185
- value = `${pad2(hours)}:${pad2(minutes)}:${pad2(Math.floor(seconds))}`
1186
-
1187
- if (seconds % 1 > 0) {
1188
- value = value + (seconds % 1).toFixed(5).substring(1)
1189
- }
1190
- }
1191
- return value
1192
- }
1193
-
1194
- fieldTypePostProcessors['DURATION'] = fieldTypePostProcessors['TIME']
1195
-
1196
- fieldTypePostProcessors['Pressure'] = (field, value) => {
1197
- if (field.Unit) {
1198
- switch (field.Unit[0]) {
1199
- case 'h':
1200
- case 'H':
1201
- value *= 100
1202
- break
1203
- case 'k':
1204
- case 'K':
1205
- value *= 1000
1206
- break
1207
- case 'd':
1208
- value /= 10
1209
- break
1210
- }
1211
- }
1212
- return value
1213
- }
1214
-
1215
- fieldTypePostProcessors[RES_BINARY] = (field, value) => {
1216
- return value.toString()
1217
- }