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