@canboat/canboatjs 3.0.0-beta.2 → 3.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,13 +11,13 @@ jobs:
11
11
  with:
12
12
  node-version: '18.x'
13
13
  registry-url: 'https://registry.npmjs.org'
14
- run: |
14
+ - run: |
15
15
  npm ci && npm cache clean --force
16
16
  if [[ "$tag" == *beta* ]];
17
17
  then
18
18
  npm publish --tag beta
19
19
  else
20
- npm publish
20
+ npm publish --access public
21
21
  fi
22
22
  env:
23
- NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
23
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
package/bin/candumpjs CHANGED
@@ -16,7 +16,8 @@ if ( argv['help'] ) {
16
16
  console.error(`Usage: ${process.argv[0]} [options] candevice
17
17
 
18
18
  Options:
19
- -h, --help output usage information`)
19
+ --format <format> json, actisense
20
+ -h, --help output usage information`)
20
21
  process.exit(1)
21
22
  }
22
23
 
@@ -25,6 +26,8 @@ if ( argv['_'].length === 0 ) {
25
26
  process.exit(1)
26
27
  }
27
28
 
29
+ const format = argv['format'] || 'json'
30
+
28
31
  /*
29
32
 
30
33
  let messageCb = (data) => {
@@ -69,8 +72,12 @@ channel.addListener('onMessage', (msg) => {
69
72
  pgn.timestamp = new Date().toISOString()
70
73
 
71
74
  let sourceString = binToActisense(pgn, msg.data, msg.data.length)
72
-
73
- parser.parse({ pgn, length: msg.data.length, data: msg.data, sourceString })
75
+
76
+ if ( format === 'json' ) {
77
+ parser.parse({ pgn, length: msg.data.length, data: msg.data, sourceString })
78
+ } else {
79
+ console.log(sourceString)
80
+ }
74
81
  })
75
82
 
76
83
  channel.start()
package/bin/to-pgn CHANGED
@@ -4,14 +4,14 @@ const argv = require('minimist')(process.argv.slice(2), {
4
4
  string: ['format'],
5
5
  alias: { h: 'help' }
6
6
  })
7
- const { pgnToActisenseSerialFormat, pgnToActisenseN2KAsciiFormat, pgnToiKonvertSerialFormat, pgnToYdgwRawFormat, pgnToPCDIN, pgnToMXPGN } = require('../index')
7
+ const { pgnToActisenseSerialFormat, pgnToActisenseN2KAsciiFormat, pgnToiKonvertSerialFormat, pgnToYdgwRawFormat, pgnToYdgwFullRawFormat, pgnToPCDIN, pgnToMXPGN } = require('../index')
8
8
  const { toActisenseSerialFormat } = require('../lib/stringMsg')
9
9
 
10
10
  if ( argv['help'] ) {
11
11
  console.error(`Usage: ${process.argv[0]} [options]
12
12
 
13
13
  Options:
14
- --format <format> actisense, actisensen2kascii, ikconvert, ydgw, pcdin, mxpgn
14
+ --format <format> actisense, actisensen2kascii, ikconvert, ydgw, yd-full, pcdin, mxpgn
15
15
  -h, --help output usage information`)
16
16
  process.exit(1)
17
17
  }
@@ -23,7 +23,8 @@ const formatters = {
23
23
  ikconvert: pgnToiKonvertSerialFormat,
24
24
  ydgw: pgnToYdgwRawFormat,
25
25
  'pcdin': pgnToPCDIN,
26
- 'mxpgn': pgnToMXPGN
26
+ 'mxpgn': pgnToMXPGN,
27
+ 'yd-full': pgnToYdgwFullRawFormat
27
28
  }
28
29
 
29
30
  const format = argv['format'] || 'actisense'
@@ -0,0 +1 @@
1
+ module.exports = require('./dist/').default
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "signalk-device-emulator",
3
+ "version": "1.0.0",
4
+ "description": "Signal K Plugin which emulates a device",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "format": "prettier-standard 'src/*.ts'",
8
+ "build": "tsc",
9
+ "watch": "npm run build -- -w"
10
+ },
11
+ "keywords": [
12
+ "signalk-node-server-plugin"
13
+ ],
14
+ "author": "scott@scottbender.net",
15
+ "license": "Apache-2.0",
16
+ "dependencies": {
17
+ "@canboat/canboatjs": "^2.10.0"
18
+ },
19
+ "devDependencies": {
20
+ "@types/node": "^14.14.10",
21
+ "prettier-standard": "^16.4.1",
22
+ "typescript": "^4.1.2"
23
+ }
24
+ }
package/index.js CHANGED
@@ -28,6 +28,7 @@ module.exports = {
28
28
  pgnToActisenseN2KAsciiFormat: require('./lib/toPgn').pgnToActisenseN2KAsciiFormat,
29
29
  pgnToiKonvertSerialFormat: require('./lib/toPgn').pgnToiKonvertSerialFormat,
30
30
  pgnToYdgwRawFormat: require('./lib/toPgn').pgnToYdgwRawFormat,
31
+ pgnToYdgwFullRawFormat: require('./lib/toPgn').pgnToYdgwFullRawFormat,
31
32
  pgnToPCDIN: require('./lib/toPgn').pgnToPCDIN,
32
33
  pgnToMXPGN: require('./lib/toPgn').pgnToMXPGN,
33
34
  canbus: require('./lib/canbus'),
@@ -39,6 +40,7 @@ module.exports = {
39
40
  VenusMQTT: require('./lib/venus-mqtt'),
40
41
  discover: require('./lib/discovery'),
41
42
  SimpleCan: require('./lib/simpleCan'),
43
+ YdDevice: require('./lib/yddevice'),
42
44
  addCustomPgns: pgns.addCustomPgns,
43
45
  lookupEnumerationValue: pgns.lookupEnumerationValue,
44
46
  lookupEnumerationName: pgns.lookupEnumerationName
@@ -48,3 +50,4 @@ try {
48
50
  module.exports.serial = require('./lib/serial')
49
51
  } catch ( ex ) {
50
52
  }
53
+
package/lib/canbus.js CHANGED
@@ -89,57 +89,26 @@ function CanbusStream (options) {
89
89
  }
90
90
 
91
91
  var canDevice = options.canDevice || 'can0'
92
- if ( !this.socketcan || this.options.useSocketCanWriter ) {
93
- this.socketCanWriter = null
94
- const spawn = require('child_process').spawn
95
- var hasWriter = spawn('sh', ['-c', 'which socketcan-writer'])
96
-
97
- const setProviderError = this.setProviderError.bind(this)
98
- const setProviderStatus = this.setProviderStatus.bind(this)
99
-
100
- hasWriter.on('close', code => {
101
- if ( code == 0 ) {
102
- debug('found socketcan-writer, starting...')
103
- setProviderStatus('Starting')
104
- this.socketCanWriter = spawn('sh',
105
- ['-c', `socketcan-writer ${canDevice}`])
106
- setProviderStatus(`Connected to ${canDevice}`)
107
- this.socketCanWriter.stderr.on('data', function (data) {
108
- console.error(data.toString())
109
- })
110
- this.socketCanWriter.on('close', function (code) {
111
- const msg = 'socketcan-writer process exited with code ' + code
112
- setProviderError(msg)
113
- console.error(msg)
114
- this.socketCanWriter = null
115
- })
116
- setTimeout(() => {
117
- this.candevice = new CanDevice(this, options)
118
- this.candevice.start()
119
- }, 5000)
120
- }
121
- })
122
- } else {
123
- this.connect()
124
-
125
- const noDataReceivedTimeout = typeof options.noDataReceivedTimeout !== 'undefined' ? options.noDataReceivedTimeout : -1
126
- if ( noDataReceivedTimeout > 0 ) {
127
- this.noDataInterval = setInterval(() => {
128
- if ( this.channel && this.lastDataReceived && Date.now() - this.lastDataReceived > noDataReceivedTimeout * 1000 ) {
129
- let channel = this.channel
130
- delete this.channel
131
- try {
132
- channel.stop()
133
- } catch ( error ) {
134
- }
135
- this.setProviderError('No data received, retrying...')
136
- if ( this.options.app ) {
137
- console.error('No data received, retrying...')
138
- }
139
- this.connect()
92
+
93
+ this.connect()
94
+
95
+ const noDataReceivedTimeout = typeof options.noDataReceivedTimeout !== 'undefined' ? options.noDataReceivedTimeout : -1
96
+ if ( noDataReceivedTimeout > 0 ) {
97
+ this.noDataInterval = setInterval(() => {
98
+ if ( this.channel && this.lastDataReceived && Date.now() - this.lastDataReceived > noDataReceivedTimeout * 1000 ) {
99
+ let channel = this.channel
100
+ delete this.channel
101
+ try {
102
+ channel.stop()
103
+ } catch ( error ) {
140
104
  }
141
- }, noDataReceivedTimeout * 1000)
142
- }
105
+ this.setProviderError('No data received, retrying...')
106
+ if ( this.options.app ) {
107
+ console.error('No data received, retrying...')
108
+ }
109
+ this.connect()
110
+ }
111
+ }, noDataReceivedTimeout * 1000)
143
112
  }
144
113
  }
145
114
 
@@ -181,9 +150,9 @@ CanbusStream.prototype.connect = function() {
181
150
  }
182
151
  })
183
152
  this.channel.start()
153
+ this.setProviderStatus('Connected to socketcan')
184
154
  this.candevice = new CanDevice(this, this.options)
185
155
  this.candevice.start()
186
- this.setProviderStatus('Connected')
187
156
  } catch (e) {
188
157
  console.error(`unable to open canbus ${canDevice}: ${e}`)
189
158
  console.error(e.stack)
@@ -211,9 +180,10 @@ require('util').inherits(CanbusStream, Transform)
211
180
  CanbusStream.prototype.start = function () {
212
181
  }
213
182
 
214
- CanbusStream.prototype.sendPGN = function (msg) {
183
+ CanbusStream.prototype.sendPGN = function (msg, force) {
215
184
  if ( this.candevice ) {
216
- if ( !this.candevice.cansend && (_.isString(msg) || msg.pgn !== 59904) ) {
185
+ //if ( !this.candevice.cansend && (_.isString(msg) || msg.pgn !== 59904) ) {
186
+ if ( !this.candevice.cansend && force !== true ) {
217
187
  //we have not completed address claim yet
218
188
  return
219
189
  }
@@ -279,66 +249,12 @@ CanbusStream.prototype.sendPGN = function (msg) {
279
249
  }
280
250
  }
281
251
 
282
- function readLine(that, line) {
283
- var candump_data_inc = CANDUMP_DATA_INC_3;
284
-
285
- if (line.length == 0 ) {
286
- return
287
- }
288
-
289
- if ( !that.format ) {
290
- //that.s
291
- if ( line.charAt(0) == '<' ) {
292
- that.format = FMT_1
293
- } else if ( line.charAt(0) == '(' ) {
294
- that.format = FMT_3
295
- console.error("candump format not supported")
296
- } else {
297
- that.format = FMT_2
298
- }
299
- }
300
-
301
-
302
- var canid
303
- var data
304
- var split = line.trim().split(' ').filter(s => s.length > 0)
305
- var len
306
- if ( that.format === FMT_3 ) {
307
- return
308
- } else if ( that.format === FMT_1 ) {
309
- canid = parseInt(split[0].substring(1, split[0].length-1), 16)
310
- data = split.slice(2)
311
- len = split[1].substring(1, split[1].length-1)
312
- } else if ( that.format === FMT_2 ) {
313
- canid = parseInt(split[1], 16)
314
- data = split.slice(3)
315
- len = split[2].substring(1, split[2].length-1)
316
- }
317
-
318
- //console.log(JSON.stringify(split))
319
- var pgn = parseCanId(canid)
320
-
321
- if ( that.candevice && pgn.src == that.candevice.address ) {
322
- //this is a message that we sent
323
- debug('got a message from me')
324
- return
325
- }
326
-
327
- pgn.timestamp = new Date().toISOString()
328
-
329
- that.push({ pgn: pgn, length: len, data, sourceString: line })
330
- }
331
252
 
332
253
  CanbusStream.prototype._transform = function (chunk, encoding, done) {
333
- readLine(this, chunk.toString())
334
254
  done()
335
255
  }
336
256
 
337
257
  CanbusStream.prototype.end = function () {
338
- if ( this.socketCanWriter ) {
339
- debug('end, killing socketcan-writer process')
340
- this.socketCanWriter.kill()
341
- }
342
258
  if ( this.channel ) {
343
259
  let channel = this.channel
344
260
  delete this.channel
package/lib/candevice.js CHANGED
@@ -20,8 +20,9 @@ const _ = require('lodash')
20
20
  const Uint64LE = require('int64-buffer').Uint64LE
21
21
  const { defaultTransmitPGNs, getIndustryCode, getManufacturerCode, getDeviceClassCode } = require('./codes')
22
22
  const { toPgn } = require('./toPgn')
23
- let packageJson
23
+ const N2kDevice = require('./n2kDevice')
24
24
 
25
+ let packageJson
25
26
  try
26
27
  {
27
28
  packageJson = require('../' + 'package.json')
@@ -30,344 +31,21 @@ try
30
31
 
31
32
  const deviceTransmitPGNs = [ 60928, 59904, 126996, 126464 ]
32
33
 
33
- class CanDevice extends EventEmitter {
34
+ class CanDevice extends N2kDevice {
34
35
  constructor (canbus, 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
-
36
+ super(options)
93
37
  this.canbus = canbus
94
- this.options = _.isUndefined(options) ? {} : options
95
-
96
- this.address = _.isUndefined(options.preferredAddress) ? 100 : options.preferredAddress
97
- this.cansend = false
98
- this.foundConflict = false
99
- this.heartbeatCounter = 0
100
- this.devices = {}
101
-
102
- if ( !options.disableDefaultTransmitPGNs ) {
103
- this.transmitPGNs = _.union(deviceTransmitPGNs, defaultTransmitPGNs)
104
- } else {
105
- this.transmitPGNs = [...deviceTransmitPGNs]
106
- }
107
-
108
- if ( this.options.transmitPGNs ) {
109
- this.transmitPGNs = _.union(this.transmitPGNs,
110
- this.options.transmitPGNs)
111
- }
112
38
 
113
39
  if ( options.app ) {
114
40
  options.app.on(options.analyzerOutEvent || 'N2KAnalyzerOut', this.n2kMessage.bind(this))
115
41
  }
116
42
  }
117
43
 
118
- start() {
119
- sendISORequest(this, 60928, 254)
120
- setTimeout(() => {
121
- sendAddressClaim(this)
122
- }, 1000)
123
- }
124
-
125
- n2kMessage(pgn) {
126
- if ( pgn.dst == 255 || pgn.dst == this.address ) {
127
- try {
128
- if ( pgn.pgn == 59904 ) {
129
- handleISORequest(this, pgn)
130
- } else if ( pgn.pgn == 126208 ) {
131
- handleGroupFunction(this, pgn)
132
- } else if ( pgn.pgn == 60928 ) {
133
- handleISOAddressClaim(this, pgn)
134
- } else if ( pgn.pgn == 126996 ) {
135
- handleProductInformation(this, pgn)
136
- }
137
- } catch ( err ) {
138
- console.error(err)
139
- console.error(err.stack)
140
- }
141
-
142
- /*
143
- var handler = this.handlers[pgn.pgn.toString()]
144
- if ( pgn.dst == this.address )
145
- debug(`handler ${handler}`)
146
- if ( _.isFunction(handler) ) {
147
- debug(`got handled PGN %j ${handled}`, pgn)
148
- handler(pgn)
149
- }
150
- */
151
- }
152
- }
153
- }
154
-
155
- function sendPGN(device, pgn, src, dest) {
156
- pgn.src = src || device.address
157
- debug('Sending PGN %j', pgn)
158
- device.canbus.sendPGN(pgn)
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
- sendPGN(device, device.addressClaim)
173
- break;
174
- case 126464:
175
- sendPGNList(device)
176
- break;
177
- default:
178
- if ( !device.options.disableNAKs ) {
179
- debug(`Got unsupported ISO request for PGN ${n2kMsg.fields.PGN}. Sending NAK.`)
180
- sendNAKAcknowledgement(device, n2kMsg.src, n2kMsg.fields.PGN)
181
- }
182
- }
183
- }
184
-
185
- function handleGroupFunction(device, n2kMsg) {
186
- debug('handleGroupFunction %j', n2kMsg)
187
- if(n2kMsg.fields.functionCodeRequest) {
188
- handleRequestGroupFunction(device, n2kMsg)
189
- } else if(n2kMsg.fields.functionCodeCommand) {
190
- handleCommandGroupFunction(device, n2kMsg)
191
- } else {
192
- debug('Got unsupported Group Function PGN: %j', n2kMsg)
193
- }
194
-
195
- function handleRequestGroupFunction(device, n2kMsg) {
196
- if ( !device.options.disableNAKs ) {
197
- // We really don't support group function requests for any PGNs yet -> always respond with pgnErrorCode 1 = "PGN not supported"
198
- debug("Sending 'PGN Not Supported' Group Function response for requested PGN", n2kMsg.fields.PGN)
199
-
200
- const acknowledgement = {
201
- pgn: 126208,
202
- dst: n2kMsg.src,
203
- "Function Code": 2,
204
- "PGN": n2kMsg.fields.PGN,
205
- "PGN error code": 4,
206
- "Transmission interval/Priority error code": 0,
207
- "# of Parameters": 0
208
- }
209
- sendPGN(device, acknowledgement)
210
- }
211
- }
212
-
213
- function handleCommandGroupFunction(device, n2kMsg) {
214
- if ( !device.options.disableNAKs ) {
215
- // We really don't support group function commands for any PGNs yet -> always respond with pgnErrorCode 1 = "PGN not supported"
216
- debug("Sending 'PGN Not Supported' Group Function response for commanded PGN", n2kMsg.fields.PGN)
217
-
218
- const acknowledgement = {
219
- pgn: 126208,
220
- dst: n2kMsg.src,
221
- "Function Code": 2,
222
- "PGN": n2kMsg.fields.PGN,
223
- "PGN error code": 4,
224
- "Transmission interval/Priority error code": 0,
225
- "# of Parameters": 0
226
- }
227
- sendPGN(device, acknowledgement)
228
- }
229
- }
230
- }
231
-
232
- function handleISOAddressClaim(device, n2kMsg) {
233
- if ( n2kMsg.src != device.address ) {
234
- if ( !device.devices[n2kMsg.src] ) {
235
- debug(`registering device ${n2kMsg.src}`)
236
- device.devices[n2kMsg.src] = { addressClaim: n2kMsg }
237
- if ( device.cansend ) {
238
- //sendISORequest(device, 126996, undefined, n2kMsg.src)
239
- }
240
- }
241
- return
242
- }
243
-
244
- debug('Checking ISO address claim. %j', n2kMsg)
245
-
246
- const uint64ValueFromReceivedClaim = getISOAddressClaimAsUint64(n2kMsg)
247
- const uint64ValueFromOurOwnClaim = getISOAddressClaimAsUint64(device.addressClaim)
248
-
249
- if(uint64ValueFromOurOwnClaim < uint64ValueFromReceivedClaim) {
250
- debug(`Address conflict detected! Kept our address as ${device.address}.`)
251
- sendAddressClaim(device) // We have smaller address claim data -> we can keep our address -> re-claim it
252
- } else if(uint64ValueFromOurOwnClaim > uint64ValueFromReceivedClaim) {
253
- this.foundConflict = true
254
- increaseOwnAddress(device) // We have bigger address claim data -> we have to change our address
255
- sendAddressClaim(device)
256
- debug(`Address conflict detected! Changed our address to ${device.address}.`)
257
- }
258
- }
259
-
260
- function increaseOwnAddress(device) {
261
- var start = device.address
262
- do {
263
- device.address = (device.address + 1) % 253
264
- } while ( device.address != start && device.devices[device.address] )
265
- }
266
-
267
- function handleProductInformation(device, n2kMsg) {
268
- if ( !device.devices[n2kMsg.src] ) {
269
- device.devices[n2kMsg.src] = {}
270
- }
271
- debug('got production information %j', n2kMsg)
272
- device.devices[n2kMsg.src].productInformation = n2kMsg
273
- }
274
-
275
- function sendHeartbeat(device)
276
- {
277
- device.heartbeatCounter = device.heartbeatCounter + 1
278
- if ( device.heartbeatCounter > 252 )
279
- {
280
- device.heartbeatCounter = 0
281
- }
282
- sendPGN(device,{
283
- pgn: 126993,
284
- dst: 255,
285
- prio:7,
286
- "Data transmit offset": "00:01:00",
287
- "Sequence Counter": device.heartbeatCounter,
288
- "Controller 1 State":"Error Active"
289
- })
290
- }
291
-
292
-
293
- function sendAddressClaim(device) {
294
- if ( device.devices[device.address] ) {
295
- //someone already has this address, so find a free one
296
- increaseOwnAddress(device)
297
- }
298
- debug(`Sending address claim ${device.address}`)
299
- sendPGN(device, device.addressClaim)
300
- setTimeout(() => {
301
- if ( !device.foundConflict ) {
302
- debug('no address conflics, enabling send')
303
- device.cansend = true
304
- if ( device.options.app ) {
305
- device.options.app.emit('nmea2000OutAvailable')
306
- }
307
- sendISORequest(device, 126996)
308
- device.heartbeatInterval = setInterval(() => {
309
- sendHeartbeat(device)
310
- }, 60*1000)
311
- /*
312
- _.keys(device.devices).forEach((address) => {
313
- sendISORequest(device, 126996, undefined, address)
314
- })
315
- */
316
-
317
- }
318
- }, 250)
319
- }
320
-
321
- function sendISORequest(device, pgn, src, dst=255) {
322
- debug(`Sending iso request for ${pgn} to ${dst}`)
323
-
324
- const isoRequest = {
325
- pgn: 59904,
326
- dst: dst,
327
- "PGN": pgn
328
- }
329
- sendPGN(device, isoRequest, src)
330
- }
331
-
332
-
333
- function sendProductInformation(device) {
334
- debug("Sending product info..")
335
-
336
- sendPGN(device, device.productInfo)
337
- }
338
-
339
- function sendConfigInformation(device) {
340
- if ( device.configurationInfo ) {
341
- debug("Sending config info..")
342
- sendPGN(device, device.configurationInfo)
44
+ sendPGN(pgn, src) {
45
+ pgn.src = src || this.address
46
+ debug('Sending PGN %j', pgn)
47
+ this.canbus.sendPGN(pgn, true)
343
48
  }
344
49
  }
345
50
 
346
- function sendNAKAcknowledgement(device, src, requestedPGN) {
347
- const acknowledgement = {
348
- pgn: 59392,
349
- dst: src,
350
- Control: 1,
351
- "Group Function": 255,
352
- PGN: requestedPGN
353
- }
354
- sendPGN(device, acknowledgement)
355
- }
356
-
357
- function sendPGNList(device, src) {
358
- //FIXME: for now, adding everything that signalk-to-nmea2000 supports
359
- //need a way for plugins, etc. to register the pgns they provide
360
- const pgnList = {
361
- pgn: 126464,
362
- dst: src,
363
- "Function Code": 0,
364
- list: device.transmitPGNs
365
- }
366
- sendPGN(device, pgnList)
367
- }
368
-
369
- function getISOAddressClaimAsUint64(pgn) {
370
- return new Uint64LE(toPgn(pgn))
371
- }
372
-
373
51
  module.exports = CanDevice
package/lib/fromPgn.js CHANGED
@@ -635,7 +635,7 @@ function readField(options, runPostProcessor, pgn, field, bs, fields) {
635
635
  }
636
636
  if ( options.checkForInvalidFields !== false && max !== 'undefined' &&
637
637
  field.FieldType !== 'LOOKUP' &&
638
- field.FieldType !== 'FIELDTYPE_LOOKUP' &&
638
+ field.FieldType !== 'DYNAMIC_FIELD_KEY' &&
639
639
  field.FieldType !== 'PGN' &&
640
640
  field.BitLength > 1 &&
641
641
  max - value < 0 ) {
@@ -660,7 +660,7 @@ function readField(options, runPostProcessor, pgn, field, bs, fields) {
660
660
  }
661
661
 
662
662
  if ((field.FieldType === 'LOOKUP' ||
663
- field.FieldType === 'FIELDTYPE_LOOKUP') &&
663
+ field.FieldType === 'DYNAMIC_FIELD_KEY') &&
664
664
  runPostProcessor &&
665
665
  (_.isUndefined(options.resolveEnums) ||
666
666
  options.resolveEnums)) {
@@ -691,7 +691,7 @@ function readField(options, runPostProcessor, pgn, field, bs, fields) {
691
691
  function readValue(options, pgn, field, bs, fields, bitLength) {
692
692
  var value
693
693
  if ( _.isUndefined(bitLength) ) {
694
- if ( field.BitLengthVariable && field.FieldType === "KEY_VALUE" ) {
694
+ if ( field.BitLengthVariable && field.FieldType === "DYNAMIC_FIELD_VALUE" ) {
695
695
  bitLength = lookupKeyBitLength(pgn.fields, fields)
696
696
  } else {
697
697
  bitLength = field.BitLength
@@ -0,0 +1,380 @@
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
+ device.sendPGN(device.addressClaim)
173
+ break;
174
+ case 126464:
175
+ sendPGNList(device)
176
+ break;
177
+ default:
178
+ if ( !device.options.disableNAKs ) {
179
+ debug(`Got unsupported ISO request for PGN ${n2kMsg.fields.PGN}. Sending NAK.`)
180
+ sendNAKAcknowledgement(device, n2kMsg.src, n2kMsg.fields.PGN)
181
+ }
182
+ }
183
+ }
184
+
185
+ function handleGroupFunction(device, n2kMsg) {
186
+ debug('handleGroupFunction %j', n2kMsg)
187
+ if(n2kMsg.fields.functionCode === 'Request') {
188
+ handleRequestGroupFunction(device, n2kMsg)
189
+ } else if(n2kMsg.fields.functionCode === 'Command') {
190
+ handleCommandGroupFunction(device, n2kMsg)
191
+ } else {
192
+ debug('Got unsupported Group Function PGN: %j', n2kMsg)
193
+ }
194
+
195
+ function handleRequestGroupFunction(device, n2kMsg) {
196
+ if ( !device.options.disableNAKs ) {
197
+ // We really don't support group function requests for any PGNs yet -> always respond with pgnErrorCode 1 = "PGN not supported"
198
+ debug("Sending 'PGN Not Supported' Group Function response for requested PGN", n2kMsg.fields.PGN)
199
+
200
+ const acknowledgement = {
201
+ pgn: 126208,
202
+ dst: n2kMsg.src,
203
+ "Function Code": 2,
204
+ "PGN": n2kMsg.fields.PGN,
205
+ "PGN error code": 4,
206
+ "Transmission interval/Priority error code": 0,
207
+ "# of Parameters": 0
208
+ }
209
+ device.sendPGN(acknowledgement)
210
+ }
211
+ }
212
+
213
+ function handleCommandGroupFunction(device, n2kMsg) {
214
+ if ( !device.options.disableNAKs ) {
215
+ // We really don't support group function commands for any PGNs yet -> always respond with pgnErrorCode 1 = "PGN not supported"
216
+ debug("Sending 'PGN Not Supported' Group Function response for commanded PGN", n2kMsg.fields.PGN)
217
+
218
+ const acknowledgement = {
219
+ pgn: 126208,
220
+ dst: n2kMsg.src,
221
+ "Function Code": 2,
222
+ "PGN": n2kMsg.fields.PGN,
223
+ "PGN error code": 4,
224
+ "Transmission interval/Priority error code": 0,
225
+ "# of Parameters": 0
226
+ }
227
+ device.sendPGN(acknowledgement)
228
+ }
229
+ }
230
+ }
231
+
232
+ function handleISOAddressClaim(device, n2kMsg) {
233
+ if ( n2kMsg.src != device.address ) {
234
+ if ( !device.devices[n2kMsg.src] ) {
235
+ debug(`registering device ${n2kMsg.src}`)
236
+ device.devices[n2kMsg.src] = { addressClaim: n2kMsg }
237
+ if ( device.cansend ) {
238
+ //sendISORequest(device, 126996, undefined, n2kMsg.src)
239
+ }
240
+ }
241
+ return
242
+ }
243
+
244
+ debug('Checking ISO address claim. %j', n2kMsg)
245
+
246
+ const uint64ValueFromReceivedClaim = getISOAddressClaimAsUint64(n2kMsg)
247
+ const uint64ValueFromOurOwnClaim = getISOAddressClaimAsUint64(device.addressClaim)
248
+
249
+ if(uint64ValueFromOurOwnClaim < uint64ValueFromReceivedClaim) {
250
+ debug(`Address conflict detected! Kept our address as ${device.address}.`)
251
+ sendAddressClaim(device) // We have smaller address claim data -> we can keep our address -> re-claim it
252
+ } else if(uint64ValueFromOurOwnClaim > uint64ValueFromReceivedClaim) {
253
+ this.foundConflict = true
254
+ increaseOwnAddress(device) // We have bigger address claim data -> we have to change our address
255
+ debug(`Address conflict detected! trying address ${device.address}.`)
256
+ sendAddressClaim(device)
257
+ }
258
+ }
259
+
260
+ function increaseOwnAddress(device) {
261
+ var start = device.address
262
+ do {
263
+ device.address = (device.address + 1) % 253
264
+ } while ( device.address != start && device.devices[device.address] )
265
+ }
266
+
267
+ function handleProductInformation(device, n2kMsg) {
268
+ if ( !device.devices[n2kMsg.src] ) {
269
+ device.devices[n2kMsg.src] = {}
270
+ }
271
+ debug('got product information %j', n2kMsg)
272
+ device.devices[n2kMsg.src].productInformation = n2kMsg
273
+ }
274
+
275
+ function sendHeartbeat(device)
276
+ {
277
+ device.heartbeatCounter = device.heartbeatCounter + 1
278
+ if ( device.heartbeatCounter > 252 )
279
+ {
280
+ device.heartbeatCounter = 0
281
+ }
282
+ device.sendPGN({
283
+ pgn: 126993,
284
+ dst: 255,
285
+ prio:7,
286
+ "Data transmit offset": "00:01:00",
287
+ "Sequence Counter": device.heartbeatCounter,
288
+ "Controller 1 State":"Error Active"
289
+ })
290
+ }
291
+
292
+
293
+ function sendAddressClaim(device) {
294
+ if ( device.devices[device.address] ) {
295
+ //someone already has this address, so find a free one
296
+ increaseOwnAddress(device)
297
+ }
298
+ debug(`Sending address claim ${device.address}`)
299
+ device.sendPGN(device.addressClaim)
300
+ device.setStatus(`Claimed address ${device.address}`)
301
+ device.addressClaimSentAt = Date.now()
302
+ if ( device.addressClaimChecker ) {
303
+ clearTimeout(device.addressClaimChecker)
304
+ }
305
+
306
+ device.addressClaimChecker = setTimeout(() => {
307
+ //if ( Date.now() - device.addressClaimSentAt > 1000 ) {
308
+ //device.addressClaimChecker = null
309
+ debug('claimed address %d', device.address)
310
+ device.cansend = true
311
+ if ( !device.sentAvailable ) {
312
+ if ( device.options.app ) {
313
+ device.options.app.emit('nmea2000OutAvailable')
314
+ }
315
+ device.emit('nmea2000OutAvailable')
316
+ device.sentAvailable = true
317
+ }
318
+ sendISORequest(device, 126996)
319
+ if ( !device.heartbeatInterval ) {
320
+ device.heartbeatInterval = setInterval(() => {
321
+ sendHeartbeat(device)
322
+ }, 60*1000)
323
+ }
324
+ //}
325
+ }, device.addressClaimDetectionTime)
326
+ }
327
+
328
+ function sendISORequest(device, pgn, src, dst=255) {
329
+ debug(`Sending iso request for ${pgn} to ${dst}`)
330
+
331
+ const isoRequest = {
332
+ pgn: 59904,
333
+ dst: dst,
334
+ "PGN": pgn
335
+ }
336
+ device.sendPGN(isoRequest, src)
337
+ }
338
+
339
+
340
+ function sendProductInformation(device) {
341
+ debug("Sending product info..")
342
+
343
+ device.sendPGN(device.productInfo)
344
+ }
345
+
346
+ function sendConfigInformation(device) {
347
+ if ( device.configurationInfo ) {
348
+ debug("Sending config info..")
349
+ device.sendPGN(device.configurationInfo)
350
+ }
351
+ }
352
+
353
+ function sendNAKAcknowledgement(device, src, requestedPGN) {
354
+ const acknowledgement = {
355
+ pgn: 59392,
356
+ dst: src,
357
+ Control: 1,
358
+ "Group Function": 255,
359
+ PGN: requestedPGN
360
+ }
361
+ device.sendPGN(device, acknowledgement)
362
+ }
363
+
364
+ function sendPGNList(device, src) {
365
+ //FIXME: for now, adding everything that signalk-to-nmea2000 supports
366
+ //need a way for plugins, etc. to register the pgns they provide
367
+ const pgnList = {
368
+ pgn: 126464,
369
+ dst: src,
370
+ "Function Code": 0,
371
+ list: device.transmitPGNs
372
+ }
373
+ device.sendPGN(pgnList)
374
+ }
375
+
376
+ function getISOAddressClaimAsUint64(pgn) {
377
+ return new Uint64LE(toPgn(pgn))
378
+ }
379
+
380
+ module.exports = N2kDevice
package/lib/simpleCan.js CHANGED
@@ -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,6 +98,10 @@ SimpleCan.prototype.sendPGN = function (msg) {
98
98
  }
99
99
  }
100
100
 
101
+ SimpleCan.prototype.sendActisenseFormat = function (msg) {
102
+ this.sendPGN(msg)
103
+ }
104
+
101
105
  function binToActisense(pgn, data, length) {
102
106
  return (
103
107
  pgn.timestamp +
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
@@ -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
+ msg.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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@canboat/canboatjs",
3
- "version": "3.0.0-beta.2",
3
+ "version": "3.0.0-beta.5",
4
4
  "description": "Native javascript version of canboat",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -53,13 +53,14 @@
53
53
  ],
54
54
  "license": "Apache-2.0",
55
55
  "dependencies": {
56
- "@canboat/pgns": "6.0.0-beta.1",
56
+ "@canboat/pgns": "6.0.0-beta.3",
57
57
  "bit-buffer": "0.2.3",
58
58
  "debug": "^4.3.4",
59
59
  "dnssd": "^0.4.1",
60
60
  "int64-buffer": "^0.1.10",
61
61
  "lodash": "^4.17.4",
62
62
  "minimist": "^1.2.0",
63
+ "moment": "^2.30.1",
63
64
  "mqtt": "^2.18.8",
64
65
  "split": "^1.0.1"
65
66
  },
@@ -73,7 +74,6 @@
73
74
  "chai-things": "^0.2.0",
74
75
  "jest": "^24.7.1",
75
76
  "mocha": "^5.0.0",
76
- "moment": "^2.24.0",
77
77
  "nyc": "^15.1.0",
78
78
  "webpack-cli": "^5.1.4"
79
79
  },