@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.
Files changed (222) hide show
  1. package/.github/workflows/test.yml +2 -2
  2. package/.github/workflows/test_canboat_changes.yml +91 -0
  3. package/.husky/pre-commit +4 -0
  4. package/.mocharc.js +7 -0
  5. package/.prettierrc.json +5 -0
  6. package/README.md +2 -7
  7. package/dist/actisense-serial.d.ts +17 -0
  8. package/dist/actisense-serial.d.ts.map +1 -0
  9. package/dist/actisense-serial.js +569 -0
  10. package/dist/actisense-serial.js.map +1 -0
  11. package/dist/bin/actisense-file.d.ts +3 -0
  12. package/dist/bin/actisense-file.d.ts.map +1 -0
  13. package/dist/bin/actisense-file.js +49 -0
  14. package/dist/bin/actisense-file.js.map +1 -0
  15. package/dist/bin/actisense-n2k-tcp.d.ts +3 -0
  16. package/dist/bin/actisense-n2k-tcp.d.ts.map +1 -0
  17. package/dist/bin/actisense-n2k-tcp.js +46 -0
  18. package/dist/bin/actisense-n2k-tcp.js.map +1 -0
  19. package/dist/bin/actisense-serialjs.d.ts +3 -0
  20. package/dist/bin/actisense-serialjs.d.ts.map +1 -0
  21. package/dist/bin/actisense-serialjs.js +51 -0
  22. package/dist/bin/actisense-serialjs.js.map +1 -0
  23. package/dist/bin/analyzerjs.d.ts +3 -0
  24. package/dist/bin/analyzerjs.d.ts.map +1 -0
  25. package/dist/bin/analyzerjs.js +61 -0
  26. package/dist/bin/analyzerjs.js.map +1 -0
  27. package/dist/bin/candumpanalyzerjs.d.ts +3 -0
  28. package/dist/bin/candumpanalyzerjs.d.ts.map +1 -0
  29. package/dist/bin/candumpanalyzerjs.js +31 -0
  30. package/dist/bin/candumpanalyzerjs.js.map +1 -0
  31. package/dist/bin/candumpjs.d.ts +3 -0
  32. package/dist/bin/candumpjs.d.ts.map +1 -0
  33. package/dist/bin/candumpjs.js +73 -0
  34. package/dist/bin/candumpjs.js.map +1 -0
  35. package/dist/bin/cansend.d.ts +3 -0
  36. package/dist/bin/cansend.d.ts.map +1 -0
  37. package/dist/bin/cansend.js +123 -0
  38. package/dist/bin/cansend.js.map +1 -0
  39. package/dist/bin/ikonvert-serial.d.ts +3 -0
  40. package/dist/bin/ikonvert-serial.d.ts.map +1 -0
  41. package/dist/bin/ikonvert-serial.js +36 -0
  42. package/dist/bin/ikonvert-serial.js.map +1 -0
  43. package/dist/bin/to-pgn.d.ts +3 -0
  44. package/dist/bin/to-pgn.d.ts.map +1 -0
  45. package/dist/bin/to-pgn.js +57 -0
  46. package/dist/bin/to-pgn.js.map +1 -0
  47. package/dist/canId.d.ts +21 -0
  48. package/dist/canId.d.ts.map +1 -0
  49. package/dist/canId.js +66 -0
  50. package/dist/canId.js.map +1 -0
  51. package/dist/canId.test.d.ts +2 -0
  52. package/dist/canId.test.d.ts.map +1 -0
  53. package/dist/canId.test.js +58 -0
  54. package/dist/canId.test.js.map +1 -0
  55. package/dist/canbus.d.ts +17 -0
  56. package/dist/canbus.d.ts.map +1 -0
  57. package/dist/canbus.js +261 -0
  58. package/dist/canbus.js.map +1 -0
  59. package/dist/candevice.d.ts +23 -0
  60. package/dist/candevice.d.ts.map +1 -0
  61. package/dist/candevice.js +38 -0
  62. package/dist/candevice.js.map +1 -0
  63. package/dist/codes.d.ts +2 -0
  64. package/dist/codes.d.ts.map +1 -0
  65. package/dist/codes.js +24 -0
  66. package/dist/codes.js.map +1 -0
  67. package/dist/discovery.d.ts +17 -0
  68. package/dist/discovery.d.ts.map +1 -0
  69. package/dist/discovery.js +119 -0
  70. package/dist/discovery.js.map +1 -0
  71. package/dist/fromPgn.d.ts +51 -0
  72. package/dist/fromPgn.d.ts.map +1 -0
  73. package/dist/fromPgn.js +989 -0
  74. package/dist/fromPgn.js.map +1 -0
  75. package/dist/fromPgnStream.d.ts +17 -0
  76. package/dist/fromPgnStream.d.ts.map +1 -0
  77. package/dist/fromPgnStream.js +47 -0
  78. package/dist/fromPgnStream.js.map +1 -0
  79. package/dist/ikonvert.d.ts +17 -0
  80. package/dist/ikonvert.d.ts.map +1 -0
  81. package/dist/ikonvert.js +224 -0
  82. package/dist/ikonvert.js.map +1 -0
  83. package/dist/index.d.ts +31 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +60 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/n2k-actisense.d.ts +24 -0
  88. package/dist/n2k-actisense.d.ts.map +1 -0
  89. package/dist/n2k-actisense.js +126 -0
  90. package/dist/n2k-actisense.js.map +1 -0
  91. package/dist/n2k-actisense.test.d.ts +2 -0
  92. package/dist/n2k-actisense.test.d.ts.map +1 -0
  93. package/dist/n2k-actisense.test.js +49 -0
  94. package/dist/n2k-actisense.test.js.map +1 -0
  95. package/dist/n2kDevice.d.ts +40 -0
  96. package/dist/n2kDevice.d.ts.map +1 -0
  97. package/dist/n2kDevice.js +387 -0
  98. package/dist/n2kDevice.js.map +1 -0
  99. package/dist/ncanbus.d.ts +17 -0
  100. package/dist/ncanbus.d.ts.map +1 -0
  101. package/dist/ncanbus.js +92 -0
  102. package/dist/ncanbus.js.map +1 -0
  103. package/dist/pgns.d.ts +22 -0
  104. package/dist/pgns.d.ts.map +1 -0
  105. package/dist/pgns.js +157 -0
  106. package/dist/pgns.js.map +1 -0
  107. package/dist/pgns.test.d.ts +2 -0
  108. package/dist/pgns.test.d.ts.map +1 -0
  109. package/dist/pgns.test.js +14 -0
  110. package/dist/pgns.test.js.map +1 -0
  111. package/dist/simpleCan.d.ts +17 -0
  112. package/dist/simpleCan.d.ts.map +1 -0
  113. package/dist/simpleCan.js +125 -0
  114. package/dist/simpleCan.js.map +1 -0
  115. package/dist/stringMsg.d.ts +33 -0
  116. package/dist/stringMsg.d.ts.map +1 -0
  117. package/dist/stringMsg.js +359 -0
  118. package/dist/stringMsg.js.map +1 -0
  119. package/dist/stringMsg.test.d.ts +2 -0
  120. package/dist/stringMsg.test.d.ts.map +1 -0
  121. package/dist/stringMsg.test.js +244 -0
  122. package/dist/stringMsg.test.js.map +1 -0
  123. package/dist/toPgn.d.ts +34 -0
  124. package/dist/toPgn.d.ts.map +1 -0
  125. package/dist/toPgn.js +500 -0
  126. package/dist/toPgn.js.map +1 -0
  127. package/dist/utilities.d.ts +26 -0
  128. package/dist/utilities.d.ts.map +1 -0
  129. package/dist/utilities.js +112 -0
  130. package/dist/utilities.js.map +1 -0
  131. package/dist/utilities.test.d.ts +2 -0
  132. package/dist/utilities.test.d.ts.map +1 -0
  133. package/dist/utilities.test.js +9 -0
  134. package/dist/utilities.test.js.map +1 -0
  135. package/dist/venus-mqtt.d.ts +11 -0
  136. package/dist/venus-mqtt.d.ts.map +1 -0
  137. package/dist/venus-mqtt.js +100 -0
  138. package/dist/venus-mqtt.js.map +1 -0
  139. package/dist/venus.d.ts +11 -0
  140. package/dist/venus.d.ts.map +1 -0
  141. package/dist/venus.js +71 -0
  142. package/dist/venus.js.map +1 -0
  143. package/dist/w2k01.d.ts +17 -0
  144. package/dist/w2k01.d.ts.map +1 -0
  145. package/dist/w2k01.js +119 -0
  146. package/dist/w2k01.js.map +1 -0
  147. package/dist/yddevice.d.ts +25 -0
  148. package/dist/yddevice.d.ts.map +1 -0
  149. package/dist/yddevice.js +45 -0
  150. package/dist/yddevice.js.map +1 -0
  151. package/dist/ydgw02.d.ts +17 -0
  152. package/dist/ydgw02.d.ts.map +1 -0
  153. package/dist/ydgw02.js +163 -0
  154. package/dist/ydgw02.js.map +1 -0
  155. package/dist/ydvr.d.ts +17 -0
  156. package/dist/ydvr.d.ts.map +1 -0
  157. package/dist/ydvr.js +120 -0
  158. package/dist/ydvr.js.map +1 -0
  159. package/eslint.config.js +58 -0
  160. package/jest.config.js +5 -0
  161. package/lib/actisense-serial.ts +644 -0
  162. package/lib/bin/actisense-file.ts +53 -0
  163. package/lib/bin/actisense-n2k-tcp.ts +50 -0
  164. package/lib/bin/actisense-serialjs.ts +55 -0
  165. package/{bin/analyzerjs → lib/bin/analyzerjs.ts} +16 -15
  166. package/{bin/candumpjs → lib/bin/candumpjs.ts} +19 -20
  167. package/lib/bin/cansend.ts +131 -0
  168. package/lib/bin/ikonvert-serial.ts +44 -0
  169. package/{bin/to-pgn → lib/bin/to-pgn.ts} +23 -17
  170. package/lib/bin/ydvr-file +33 -0
  171. package/lib/canId.test.js +35 -15
  172. package/lib/canId.ts +84 -0
  173. package/lib/canbus.ts +293 -0
  174. package/lib/{candevice.js → candevice.ts} +14 -21
  175. package/lib/codes.ts +21 -0
  176. package/lib/{discovery.js → discovery.ts} +32 -30
  177. package/lib/fromPgn.ts +1207 -0
  178. package/lib/{fromPgnStream.js → fromPgnStream.ts} +17 -12
  179. package/lib/{ikonvert.js → ikonvert.ts} +90 -85
  180. package/lib/index.ts +48 -0
  181. package/lib/n2k-actisense.test.js +31 -32
  182. package/lib/n2k-actisense.ts +152 -0
  183. package/lib/n2kDevice.ts +458 -0
  184. package/lib/pgns.test.ts +12 -0
  185. package/lib/pgns.ts +191 -0
  186. package/lib/simpleCan.ts +140 -0
  187. package/lib/stringMsg.test.js +58 -41
  188. package/lib/stringMsg.ts +464 -0
  189. package/lib/toPgn.ts +597 -0
  190. package/lib/{utilities.js → utilities.ts} +48 -37
  191. package/lib/venus-mqtt.js +69 -73
  192. package/lib/venus.js +8 -8
  193. package/lib/{w2k01.js → w2k01.ts} +57 -44
  194. package/lib/{yddevice.js → yddevice.ts} +18 -13
  195. package/lib/{ydgw02.js → ydgw02.ts} +55 -47
  196. package/lib/ydvr.js +65 -147
  197. package/package.json +54 -18
  198. package/tsconfig.json +19 -0
  199. package/tsconfig.tsbuildinfo +1 -0
  200. package/bin/actisense-file +0 -47
  201. package/bin/actisense-n2k-tcp +0 -45
  202. package/bin/actisense-serialjs +0 -52
  203. package/bin/candumpanalyzerjs +0 -29
  204. package/bin/cansend +0 -130
  205. package/bin/ikonvert-serial +0 -50
  206. package/bin/ydgw-analyzerjs +0 -36
  207. package/bin/ydvr-file +0 -33
  208. package/index.js +0 -53
  209. package/lib/canId.js +0 -64
  210. package/lib/canbus.js +0 -278
  211. package/lib/codes.js +0 -69
  212. package/lib/codes.test.js +0 -17
  213. package/lib/codesMfgs.json +0 -166
  214. package/lib/fromPgn.js +0 -1027
  215. package/lib/n2k-actisense.js +0 -108
  216. package/lib/n2kDevice.js +0 -390
  217. package/lib/pgns.js +0 -196
  218. package/lib/pgns.test.js +0 -13
  219. package/lib/serial.js +0 -647
  220. package/lib/simpleCan.js +0 -105
  221. package/lib/stringMsg.js +0 -339
  222. 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
+ }