@canboat/canboatjs 3.0.0-beta.1 → 3.0.0-beta.10

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.
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Copyright 2025 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
+ const debug = require('debug')('canboatjs:n2kdevice')
18
+ const EventEmitter = require('events')
19
+ const _ = require('lodash')
20
+ const Uint64LE = require('int64-buffer').Uint64LE
21
+ const { defaultTransmitPGNs, getIndustryCode, getManufacturerCode, getDeviceClassCode } = require('./codes')
22
+ const { toPgn } = require('./toPgn')
23
+ let packageJson
24
+
25
+ try
26
+ {
27
+ packageJson = require('../' + 'package.json')
28
+ } catch (ex) {
29
+ }
30
+
31
+ const deviceTransmitPGNs = [ 60928, 59904, 126996, 126464 ]
32
+
33
+ class N2kDevice extends EventEmitter {
34
+ constructor (options) {
35
+ super()
36
+
37
+ if ( options.addressClaim ) {
38
+ this.addressClaim = options.addressClaim
39
+ this.addressClaim.pgn = 60928
40
+ this.addressClaim.dst = 255
41
+ this.addressClaim.prio = 6
42
+ } else {
43
+ this.addressClaim = {
44
+ pgn: 60928,
45
+ dst: 255,
46
+ prio:6,
47
+ "Unique Number": 1263,
48
+ "Manufacturer Code": 999,
49
+ "Device Function": 130, // PC gateway
50
+ "Device Class": 25, // Inter/Intranetwork Device
51
+ "Device Instance Lower": 0,
52
+ "Device Instance Upper": 0,
53
+ "System Instance": 0,
54
+ "Industry Group": 4, // Marine
55
+ "Reserved1": 1,
56
+ "Reserved2": 2
57
+ }
58
+ this.addressClaim["Unique Number"] = options.uniqueNumber || Math.floor(Math.random() * Math.floor(2097151))
59
+ }
60
+
61
+ let version = packageJson ? packageJson.version : "1.0"
62
+
63
+ if ( options.productInfo ) {
64
+ this.productInfo = options.productInfo
65
+ this.productInfo.pgn = 126996
66
+ this.productInfo.dst = 255
67
+ } else {
68
+ this.productInfo = {
69
+ pgn: 126996,
70
+ dst: 255,
71
+ "NMEA 2000 Version": 1300,
72
+ "Product Code": 667, // Just made up..
73
+ "Model ID": "Signal K",
74
+ "Model Version": "canboatjs",
75
+ "Model Serial Code": options.uniqueNumber ? options.uniqueNumber.toString() : "000001",
76
+ "Certification Level": 0,
77
+ "Load Equivalency": 1
78
+ }
79
+ }
80
+
81
+ this.productInfo["Software Version Code"] = version
82
+
83
+ if ( options.serverVersion && options.serverUrl ) {
84
+ this.configurationInfo = {
85
+ pgn: 126998,
86
+ dst: 255,
87
+ "Installation Description #1": options.serverUrl,
88
+ "Installation Description #2": options.serverDescription,
89
+ "Manufacturer Information": options.serverVersion
90
+ }
91
+ }
92
+
93
+ this.options = _.isUndefined(options) ? {} : options
94
+
95
+ this.address = _.isUndefined(options.preferredAddress) ? 100 : options.preferredAddress
96
+ this.cansend = false
97
+ this.foundConflict = false
98
+ this.heartbeatCounter = 0
99
+ this.devices = {}
100
+ this.sentAvailable = false
101
+ this.addressClaimDetectionTime = options.addressClaimDetectionTime !== undefined ? options.addressClaimDetectionTime : 5000
102
+
103
+ if ( !options.disableDefaultTransmitPGNs ) {
104
+ this.transmitPGNs = _.union(deviceTransmitPGNs, defaultTransmitPGNs)
105
+ } else {
106
+ this.transmitPGNs = [...deviceTransmitPGNs]
107
+ }
108
+
109
+ if ( this.options.transmitPGNs ) {
110
+ this.transmitPGNs = _.union(this.transmitPGNs,
111
+ this.options.transmitPGNs)
112
+ }
113
+ }
114
+
115
+ start() {
116
+ sendISORequest(this, 60928, 254)
117
+ setTimeout(() => {
118
+ sendAddressClaim(this)
119
+ }, 1000)
120
+ }
121
+
122
+ setStatus(msg) {
123
+ if ( this.options.app && this.options.app.setPluginStatus ) {
124
+ this.options.app.setProviderStatus(this.options.providerId, msg)
125
+ }
126
+ }
127
+
128
+ n2kMessage(pgn) {
129
+ if ( pgn.dst == 255 || pgn.dst == this.address ) {
130
+ try {
131
+ if ( pgn.pgn == 59904 ) {
132
+ handleISORequest(this, pgn)
133
+ } else if ( pgn.pgn == 126208 ) {
134
+ handleGroupFunction(this, pgn)
135
+ } else if ( pgn.pgn == 60928 ) {
136
+ handleISOAddressClaim(this, pgn)
137
+ } else if ( pgn.pgn == 126996 ) {
138
+ handleProductInformation(this, pgn)
139
+ }
140
+ } catch ( err ) {
141
+ console.error(err)
142
+ console.error(err.stack)
143
+ }
144
+
145
+ /*
146
+ var handler = this.handlers[pgn.pgn.toString()]
147
+ if ( pgn.dst == this.address )
148
+ debug(`handler ${handler}`)
149
+ if ( _.isFunction(handler) ) {
150
+ debug(`got handled PGN %j ${handled}`, pgn)
151
+ handler(pgn)
152
+ }
153
+ */
154
+ }
155
+ }
156
+
157
+ sendPGN(pgn, src) {
158
+ }
159
+ }
160
+
161
+ function handleISORequest(device, n2kMsg) {
162
+ debug('handleISORequest %j', n2kMsg)
163
+
164
+ switch (n2kMsg.fields.PGN) {
165
+ case 126996: // Product Information request
166
+ sendProductInformation(device)
167
+ break;
168
+ case 126998: // Config Information request
169
+ sendConfigInformation(device)
170
+ break;
171
+ case 60928: // ISO address claim request
172
+ debug('sending address claim %j', device.addressClaim)
173
+ device.sendPGN(device.addressClaim)
174
+ break;
175
+ case 126464:
176
+ sendPGNList(device)
177
+ break;
178
+ default:
179
+ if ( !device.options.disableNAKs ) {
180
+ debug(`Got unsupported ISO request for PGN ${n2kMsg.fields.PGN}. Sending NAK.`)
181
+ sendNAKAcknowledgement(device, n2kMsg.src, n2kMsg.fields.PGN)
182
+ }
183
+ }
184
+ }
185
+
186
+ function handleGroupFunction(device, n2kMsg) {
187
+ debug('handleGroupFunction %j', n2kMsg)
188
+ const functionCode = n2kMsg.fields.functionCode || n2kMsg.fields["Function Code"]
189
+ if(functionCode === 'Request') {
190
+ handleRequestGroupFunction(device, n2kMsg)
191
+ } else if(functionCode === 'Command') {
192
+ handleCommandGroupFunction(device, n2kMsg)
193
+ } else {
194
+ debug('Got unsupported Group Function PGN: %j', n2kMsg)
195
+ }
196
+
197
+ function handleRequestGroupFunction(device, n2kMsg) {
198
+ if ( !device.options.disableNAKs ) {
199
+ // We really don't support group function requests for any PGNs yet -> always respond with pgnErrorCode 1 = "PGN not supported"
200
+ debug("Sending 'PGN Not Supported' Group Function response for requested PGN", n2kMsg.fields.PGN)
201
+
202
+ const acknowledgement = {
203
+ pgn: 126208,
204
+ dst: n2kMsg.src,
205
+ "Function Code": 2,
206
+ "PGN": n2kMsg.fields.PGN,
207
+ "PGN error code": 4,
208
+ "Transmission interval/Priority error code": 0,
209
+ "# of Parameters": 0
210
+ }
211
+ device.sendPGN(acknowledgement)
212
+ }
213
+ }
214
+
215
+ function handleCommandGroupFunction(device, n2kMsg) {
216
+ if ( !device.options.disableNAKs ) {
217
+ // We really don't support group function commands for any PGNs yet -> always respond with pgnErrorCode 1 = "PGN not supported"
218
+ debug("Sending 'PGN Not Supported' Group Function response for commanded PGN", n2kMsg.fields.PGN)
219
+
220
+ const acknowledgement = {
221
+ pgn: 126208,
222
+ dst: n2kMsg.src,
223
+ "Function Code": 2,
224
+ "PGN": n2kMsg.fields.PGN,
225
+ "PGN error code": 4,
226
+ "Transmission interval/Priority error code": 0,
227
+ "# of Parameters": 0
228
+ }
229
+ device.sendPGN(acknowledgement)
230
+ }
231
+ }
232
+ }
233
+
234
+ function handleISOAddressClaim(device, n2kMsg) {
235
+ if ( n2kMsg.src != device.address ) {
236
+ if ( !device.devices[n2kMsg.src] ) {
237
+ debug(`registering device ${n2kMsg.src}`)
238
+ device.devices[n2kMsg.src] = { addressClaim: n2kMsg }
239
+ if ( device.cansend ) {
240
+ //sendISORequest(device, 126996, undefined, n2kMsg.src)
241
+ }
242
+ }
243
+ return
244
+ }
245
+
246
+ debug('Checking ISO address claim. %j', n2kMsg)
247
+
248
+ const uint64ValueFromReceivedClaim = getISOAddressClaimAsUint64(n2kMsg)
249
+ const uint64ValueFromOurOwnClaim = getISOAddressClaimAsUint64(device.addressClaim)
250
+
251
+ if(uint64ValueFromOurOwnClaim < uint64ValueFromReceivedClaim) {
252
+ debug(`Address conflict detected! Kept our address as ${device.address}.`)
253
+ sendAddressClaim(device) // We have smaller address claim data -> we can keep our address -> re-claim it
254
+ } else if(uint64ValueFromOurOwnClaim > uint64ValueFromReceivedClaim) {
255
+ this.foundConflict = true
256
+ increaseOwnAddress(device) // We have bigger address claim data -> we have to change our address
257
+ debug(`Address conflict detected! trying address ${device.address}.`)
258
+ sendAddressClaim(device)
259
+ }
260
+ }
261
+
262
+ function increaseOwnAddress(device) {
263
+ var start = device.address
264
+ do {
265
+ device.address = (device.address + 1) % 253
266
+ } while ( device.address != start && device.devices[device.address] )
267
+ }
268
+
269
+ function handleProductInformation(device, n2kMsg) {
270
+ if ( !device.devices[n2kMsg.src] ) {
271
+ device.devices[n2kMsg.src] = {}
272
+ }
273
+ debug('got product information %j', n2kMsg)
274
+ device.devices[n2kMsg.src].productInformation = n2kMsg
275
+ }
276
+
277
+ function sendHeartbeat(device)
278
+ {
279
+ device.heartbeatCounter = device.heartbeatCounter + 1
280
+ if ( device.heartbeatCounter > 252 )
281
+ {
282
+ device.heartbeatCounter = 0
283
+ }
284
+ device.sendPGN({
285
+ pgn: 126993,
286
+ dst: 255,
287
+ prio:7,
288
+ "Data transmit offset": "00:01:00",
289
+ "Sequence Counter": device.heartbeatCounter,
290
+ "Controller 1 State":"Error Active"
291
+ })
292
+ }
293
+
294
+
295
+ function sendAddressClaim(device) {
296
+ if ( device.devices[device.address] ) {
297
+ //someone already has this address, so find a free one
298
+ increaseOwnAddress(device)
299
+ }
300
+ debug(`Sending address claim ${device.address}`)
301
+ device.sendPGN(device.addressClaim)
302
+ device.setStatus(`Claimed address ${device.address}`)
303
+ device.addressClaimSentAt = Date.now()
304
+ if ( device.addressClaimChecker ) {
305
+ clearTimeout(device.addressClaimChecker)
306
+ }
307
+
308
+ device.addressClaimChecker = setTimeout(() => {
309
+ //if ( Date.now() - device.addressClaimSentAt > 1000 ) {
310
+ //device.addressClaimChecker = null
311
+ debug('claimed address %d', device.address)
312
+ device.cansend = true
313
+ if ( !device.sentAvailable ) {
314
+ if ( device.options.app ) {
315
+ device.options.app.emit('nmea2000OutAvailable')
316
+ }
317
+ device.emit('nmea2000OutAvailable')
318
+ device.sentAvailable = true
319
+ }
320
+ sendISORequest(device, 126996)
321
+ if ( !device.heartbeatInterval ) {
322
+ device.heartbeatInterval = setInterval(() => {
323
+ sendHeartbeat(device)
324
+ }, 60*1000)
325
+ }
326
+ //}
327
+ }, device.addressClaimDetectionTime)
328
+ }
329
+
330
+ function sendISORequest(device, pgn, src, dst=255) {
331
+ debug(`Sending iso request for ${pgn} to ${dst}`)
332
+
333
+ const isoRequest = {
334
+ pgn: 59904,
335
+ dst: dst,
336
+ "PGN": pgn
337
+ }
338
+ device.sendPGN(isoRequest, src)
339
+ }
340
+
341
+
342
+ function sendProductInformation(device) {
343
+ debug("Sending product info %j", device.productInfo)
344
+
345
+ device.sendPGN(device.productInfo)
346
+ }
347
+
348
+ function sendConfigInformation(device) {
349
+ if ( device.configurationInfo ) {
350
+ debug("Sending config info..")
351
+ device.sendPGN(device.configurationInfo)
352
+ }
353
+ }
354
+
355
+ function sendNAKAcknowledgement(device, src, requestedPGN) {
356
+ const acknowledgement = {
357
+ pgn: 59392,
358
+ dst: src,
359
+ Control: 1,
360
+ "Group Function": 255,
361
+ PGN: requestedPGN
362
+ }
363
+ device.sendPGN(acknowledgement)
364
+ }
365
+
366
+ function sendPGNList(device, src) {
367
+ //FIXME: for now, adding everything that signalk-to-nmea2000 supports
368
+ //need a way for plugins, etc. to register the pgns they provide
369
+ const pgnList = {
370
+ pgn: 126464,
371
+ dst: src,
372
+ "Function Code": 0,
373
+ list: device.transmitPGNs
374
+ }
375
+ device.sendPGN(pgnList)
376
+ }
377
+
378
+ function getISOAddressClaimAsUint64(pgn) {
379
+ return new Uint64LE(toPgn(pgn))
380
+ }
381
+
382
+ module.exports = N2kDevice
package/lib/simpleCan.js CHANGED
@@ -3,7 +3,7 @@ const debug = require('debug')('canboatjs:simpleCan')
3
3
  const { encodeCanId, parseCanId } = require('./canId')
4
4
  const { toActisenseSerialFormat, parseActisense } = require('./stringMsg')
5
5
  const { toPgn } = require('./toPgn')
6
- const { getPlainPGNs } = require('./utilities')
6
+ const { getPlainPGNs, binToActisense } = require('./utilities')
7
7
  const _ = require('lodash')
8
8
 
9
9
  function SimpleCan (options, messageCb) {
@@ -39,7 +39,7 @@ SimpleCan.prototype.start = function () {
39
39
  disableDefaultTransmitPGNs: true,
40
40
  disableNAKs: true
41
41
  },
42
- )
42
+ )
43
43
  this.candevice.start()
44
44
  }
45
45
 
@@ -98,18 +98,8 @@ SimpleCan.prototype.sendPGN = function (msg) {
98
98
  }
99
99
  }
100
100
 
101
- function binToActisense(pgn, data, length) {
102
- return (
103
- pgn.timestamp +
104
- `,${pgn.prio},${pgn.pgn},${pgn.src},${pgn.dst},${length},` +
105
- new Uint32Array(data)
106
- .reduce(function(acc, i) {
107
- acc.push(i.toString(16));
108
- return acc;
109
- }, [])
110
- .map(x => (x.length === 1 ? "0" + x : x))
111
- .join(",")
112
- );
101
+ SimpleCan.prototype.sendActisenseFormat = function (msg) {
102
+ this.sendPGN(msg)
113
103
  }
114
104
 
115
105
  module.exports = SimpleCan
package/lib/stringMsg.js CHANGED
@@ -8,6 +8,7 @@ const {
8
8
  const {
9
9
  buildCanId, encodeCanIdString, parseCanId, parseCanIdStr,
10
10
  } = require('./canId')
11
+ const moment = require('moment')
11
12
 
12
13
  /**
13
14
  * Helper function that helps merge canId fields with format, data, and others.
@@ -117,6 +118,17 @@ exports.encodeYDRAW = ({ data, ...canIdInfo }) => {
117
118
  return pgns.map(buffer => canId + ' ' + byteString(buffer, ' '))
118
119
  }
119
120
 
121
+ //16:29:27.082 R 19F51323 01 02<CR><LF>
122
+ exports.encodeYDRAWFull = ({ data, ...canIdInfo }) => {
123
+ const canId = encodeCanIdString(canIdInfo)
124
+ const pgns = data.length > 8 || canIdInfo.pgn == 126720 ? getPlainPGNs(data) : [ data ]
125
+ return pgns.map(buffer =>
126
+ moment().utc().format('hh:mm:ss.SSS')
127
+ + ' R '
128
+ + canId + ' '
129
+ + byteString(buffer, ' '))
130
+ }
131
+
120
132
  const get0183Sentence = (msg) => {
121
133
  let sentence = msg
122
134
  if (sentence.charAt(0) === '\\') {
package/lib/toPgn.js CHANGED
@@ -21,7 +21,7 @@ const BitStream = require('bit-buffer').BitStream
21
21
  const Int64LE = require('int64-buffer').Int64LE
22
22
  const Uint64LE = require('int64-buffer').Uint64LE
23
23
  const { getPlainPGNs } = require('./utilities')
24
- const { encodeActisense, encodeActisenseN2KACSII, encodeYDRAW, parseActisense, encodePCDIN, encodeMXPGN, encodePDGY } = require('./stringMsg')
24
+ const { encodeActisense, encodeActisenseN2KACSII, encodeYDRAW, encodeYDRAWFull, parseActisense, encodePCDIN, encodeMXPGN, encodePDGY } = require('./stringMsg')
25
25
  const { encodeN2KActisense } = require('./n2k-actisense')
26
26
  const { encodeCanId } = require('./canId')
27
27
  const { getIndustryCode, getManufacturerCode } = require('./codes')
@@ -147,7 +147,7 @@ function writeField(bs, pgn_number, field, data, value, fields, bitLength) {
147
147
  var startPos = bs.byteIndex
148
148
 
149
149
  if ( _.isUndefined(bitLength) ) {
150
- if ( field.BitLengthVariable && field.FieldType === "KEY_VALUE" ) {
150
+ if ( field.BitLengthVariable && field.FieldType === "DYNAMIC_FIELD_VALUE" ) {
151
151
  bitLength = lookupKeyBitLength(data, fields)
152
152
  } else {
153
153
  bitLength = field.BitLength
@@ -178,7 +178,7 @@ function writeField(bs, pgn_number, field, data, value, fields, bitLength) {
178
178
  } else if ( type && fieldTypeMappers[type] ) {
179
179
  value = fieldTypeMappers[type](field, value)
180
180
  } else if ((field.FieldType === 'LOOKUP' ||
181
- field.FieldType === 'FIELDTYPE_LOOKUP') && _.isString(value)) {
181
+ field.FieldType === 'DYNAMIC_FIELD_KEY') && _.isString(value)) {
182
182
  if (!(field.Id === "timeStamp" && value < 60)) {
183
183
  value = lookup(field, value)
184
184
  }
@@ -333,6 +333,10 @@ function pgnToYdgwRawFormat(info) {
333
333
  return encodeYDRAW({ ...info, data: toPgn(info) })
334
334
  }
335
335
 
336
+ function pgnToYdgwFullRawFormat(info) {
337
+ return encodeYDRAWFull({ ...info, data: toPgn(info) })
338
+ }
339
+
336
340
  function pgnToPCDIN(info) {
337
341
  return encodePCDIN({ ...info, data: toPgn(info) })
338
342
  }
@@ -342,6 +346,7 @@ function pgnToMXPGN(info) {
342
346
  }
343
347
 
344
348
  const actisenseToYdgwRawFormat = _.flow(parseActisense, encodeYDRAW)
349
+ const actisenseToYdgwFullRawFormat = _.flow(parseActisense, encodeYDRAWFull)
345
350
  const actisenseToPCDIN = _.flow(parseActisense, encodePCDIN)
346
351
  const actisenseToMXPGN = _.flow(parseActisense, encodeMXPGN)
347
352
  const actisenseToiKonvert = _.flow(parseActisense, encodePDGY)
@@ -498,7 +503,9 @@ module.exports.toPgn = toPgn
498
503
  module.exports.toiKonvertSerialFormat = toiKonvertSerialFormat
499
504
  module.exports.pgnToiKonvertSerialFormat = pgnToiKonvertSerialFormat
500
505
  module.exports.pgnToYdgwRawFormat = pgnToYdgwRawFormat
506
+ module.exports.pgnToYdgwFullRawFormat = pgnToYdgwFullRawFormat
501
507
  module.exports.actisenseToYdgwRawFormat = actisenseToYdgwRawFormat
508
+ module.exports.actisenseToYdgwFullRawFormat = actisenseToYdgwFullRawFormat
502
509
  module.exports.pgnToActisenseSerialFormat = pgnToActisenseSerialFormat
503
510
  module.exports.pgnToActisenseN2KAsciiFormat = pgnToActisenseN2KAsciiFormat
504
511
  module.exports.pgnToN2KActisenseFormat = pgnToN2KActisenseFormat
package/lib/utilities.js CHANGED
@@ -85,6 +85,21 @@ function compute0183Checksum (sentence) {
85
85
  return '*' + toHexString(c1)
86
86
  }
87
87
 
88
+ function binToActisense(pgn, data, length) {
89
+ return (
90
+ pgn.timestamp +
91
+ `,${pgn.prio},${pgn.pgn},${pgn.src},${pgn.dst},${length},` +
92
+ new Uint32Array(data)
93
+ .reduce(function(acc, i) {
94
+ acc.push(i.toString(16));
95
+ return acc;
96
+ }, [])
97
+ .map(x => (x.length === 1 ? "0" + x : x))
98
+ .join(",")
99
+ );
100
+ }
101
+
102
+ exports.binToActisense = binToActisense
88
103
  exports.compute0183Checksum = compute0183Checksum
89
104
  exports.trimWrap = trimChars('()<>[]')
90
105
  exports.rmChecksum = str => str.includes('*') ? str.split('*', 1)[0] : str
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Copyright 2025 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
+
18
+ const debug = require('debug')('canboatjs:n2kdevice')
19
+ const N2kDevice = require('./n2kDevice')
20
+
21
+ const { actisenseToYdgwFullRawFormat } = require('./toPgn')
22
+
23
+ class YdDevice extends N2kDevice {
24
+ constructor (options) {
25
+ super(options)
26
+ this.app = options.app
27
+ this.n2kOutEvent = options.jsonOutEvent || 'nmea2000JsonOut'
28
+
29
+ const analyzerOutEvent = options.analyzerOutEvent || 'N2KAnalyzerOut'
30
+
31
+ this.app.on(options.analyzerOutEvent || 'N2KAnalyzerOut', this.n2kMessage.bind(this))
32
+ }
33
+
34
+ sendPGN(pgn, src) {
35
+ pgn.src = src || this.address
36
+ pgn.ydFullFormat = true
37
+ debug('Sending PGN %j', pgn)
38
+ this.app.emit(this.n2kOutEvent, pgn)
39
+ }
40
+
41
+ sendActisenseFormat(msg) {
42
+ this.app.emit('ydFullRawOut', actisenseToYdgwFullRawFormat(msg))
43
+ }
44
+ }
45
+
46
+ module.exports = YdDevice
package/lib/ydgw02.js CHANGED
@@ -18,11 +18,12 @@ const debug = require('debug')('canboatjs:ydgw02')
18
18
  const Transform = require('stream').Transform
19
19
  const FromPgn = require('./fromPgn').Parser
20
20
  const Parser = require('./fromPgn').Parser
21
+ const YdDevice = require('./yddevice')
21
22
  const _ = require('lodash')
22
23
  const { defaultTransmitPGNs } = require('./codes')
23
- const { pgnToYdgwRawFormat, actisenseToYdgwRawFormat } = require('./toPgn')
24
+ const { pgnToYdgwRawFormat, pgnToYdgwFullRawFormat, actisenseToYdgwRawFormat, actisenseToYdgwFullRawFormat } = require('./toPgn')
24
25
 
25
- const pgnsSent = {}
26
+ //const pgnsSent = {}
26
27
 
27
28
  function Ydgw02Stream (options, type) {
28
29
  if (!(this instanceof Ydgw02Stream)) {
@@ -36,6 +37,7 @@ function Ydgw02Stream (options, type) {
36
37
  this.sentAvailable = false
37
38
  this.options = options
38
39
  this.outEvent = options.ydgwOutEvent || 'ydwg02-out'
40
+ this.device = undefined
39
41
 
40
42
  this.fromPgn = new FromPgn(options)
41
43
 
@@ -63,34 +65,73 @@ function Ydgw02Stream (options, type) {
63
65
  options.app.emit('connectionwrite', { providerId: options.providerId })
64
66
  })
65
67
 
68
+ options.app.on('ydFullRawOut', (msgs) => {
69
+ this.sendYdgwFullPGN(msgs)
70
+ options.app.emit('connectionwrite', { providerId: options.providerId })
71
+ })
72
+
66
73
  //this.sendString('$PDGY,N2NET_OFFLINE')
67
74
 
68
75
  if ( type === 'usb' ) {
69
76
  // set ydnu to RAW mode
70
77
  options.app.emit(this.outEvent, Buffer.from([0x30, 0x0a]))
71
78
  }
79
+
80
+ if ( options.createDevice === true || options.createDevice === undefined ) {
81
+ this.device = new YdDevice(options)
82
+ this.device.start()
83
+ }
84
+
72
85
  debug('started')
73
86
  }
74
87
 
75
88
  }
76
89
 
77
- Ydgw02Stream.prototype.sendString = function (msg) {
78
- debug('sending %s', msg)
79
- this.options.app.emit(this.outEvent, msg)
90
+ Ydgw02Stream.prototype.cansend = function (msg) {
91
+ return this.device ? this.device.cansend : true
92
+ }
93
+
94
+ Ydgw02Stream.prototype.sendString = function (msg, forceSend) {
95
+ if ( this.cansend() || forceSend === true ) {
96
+ debug('sending %s', msg)
97
+ this.options.app.emit(this.outEvent, msg)
98
+ }
80
99
  }
81
100
 
82
101
  Ydgw02Stream.prototype.sendPGN = function (pgn) {
83
- let now = Date.now()
84
- let lastSent = pgnsSent[pgn.pgn]
85
- pgnToYdgwRawFormat(pgn).forEach(raw => {
102
+ if ( this.cansend() || pgn.forceSend === true ) {
103
+ //let now = Date.now()
104
+ //let lastSent = pgnsSent[pgn.pgn]
105
+ let msgs
106
+ if ( pgn.ydFullFormat === true || this.device !== undefined ) {
107
+ msgs = pgnToYdgwFullRawFormat(pgn)
108
+ } else {
109
+ msgs = pgnToYdgwRawFormat(pgn)
110
+ }
111
+ msgs.forEach(raw => {
112
+ this.sendString(raw + '\r\n', pgn.forceSend)
113
+ })
114
+ //pgnsSent[pgn.pgn] = now
115
+ }
116
+ }
117
+
118
+ Ydgw02Stream.prototype.sendYdgwFullPGN = function (msgs) {
119
+ msgs.forEach(raw => {
86
120
  this.sendString(raw + '\r\n')
87
121
  })
88
- pgnsSent[pgn.pgn] = now
89
122
  }
90
123
 
91
124
  Ydgw02Stream.prototype.sendYdgwPGN = function (msg) {
92
125
 
93
- actisenseToYdgwRawFormat(msg).forEach(raw => {
126
+ let msgs
127
+
128
+ if ( this.device != undefined ) {
129
+ msgs = actisenseToYdgwFullRawFormat(msg)
130
+ } else {
131
+ msgs = actisenseToYdgwRawFormat(msg)
132
+ }
133
+
134
+ msgs.forEach(raw => {
94
135
  this.sendString(raw + '\r\n')
95
136
  })
96
137
 
@@ -126,7 +167,7 @@ Ydgw02Stream.prototype._transform = function (chunk, encoding, done) {
126
167
  let line = chunk.toString().trim()
127
168
  //line = line.substring(0, line.length) // take off the \r
128
169
 
129
- if ( !this.sentAvailable ) {
170
+ if ( this.device === undefined && !this.sentAvailable ) {
130
171
  debug('emit nmea2000OutAvailable')
131
172
  this.options.app.emit('nmea2000OutAvailable')
132
173
  this.sentAvailable = true