@canboat/canboatjs 3.0.0-beta.2 → 3.0.0-beta.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/publish.yml +3 -3
- package/bin/to-pgn +4 -3
- package/examples/signalk-device-emulator/index.js +1 -0
- package/examples/signalk-device-emulator/package.json +24 -0
- package/index.js +3 -0
- package/lib/candevice.js +8 -330
- package/lib/fromPgn.js +3 -3
- package/lib/n2kDevice.js +365 -0
- package/lib/simpleCan.js +5 -1
- package/lib/stringMsg.js +12 -0
- package/lib/toPgn.js +10 -3
- package/lib/yddevice.js +47 -0
- package/lib/ydgw02.js +19 -2
- package/package.json +2 -2
|
@@ -11,13 +11,13 @@ jobs:
|
|
|
11
11
|
with:
|
|
12
12
|
node-version: '18.x'
|
|
13
13
|
registry-url: 'https://registry.npmjs.org'
|
|
14
|
-
|
|
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/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/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
|
-
|
|
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
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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)
|
|
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 !== '
|
|
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 === '
|
|
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 === "
|
|
694
|
+
if ( field.BitLengthVariable && field.FieldType === "DYNAMIC_FIELD_VALUE" ) {
|
|
695
695
|
bitLength = lookupKeyBitLength(pgn.fields, fields)
|
|
696
696
|
} else {
|
|
697
697
|
bitLength = field.BitLength
|
package/lib/n2kDevice.js
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
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
|
+
|
|
101
|
+
if ( !options.disableDefaultTransmitPGNs ) {
|
|
102
|
+
this.transmitPGNs = _.union(deviceTransmitPGNs, defaultTransmitPGNs)
|
|
103
|
+
} else {
|
|
104
|
+
this.transmitPGNs = [...deviceTransmitPGNs]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if ( this.options.transmitPGNs ) {
|
|
108
|
+
this.transmitPGNs = _.union(this.transmitPGNs,
|
|
109
|
+
this.options.transmitPGNs)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
start() {
|
|
114
|
+
sendISORequest(this, 60928, 254)
|
|
115
|
+
setTimeout(() => {
|
|
116
|
+
sendAddressClaim(this)
|
|
117
|
+
}, 1000)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
n2kMessage(pgn) {
|
|
121
|
+
if ( pgn.dst == 255 || pgn.dst == this.address ) {
|
|
122
|
+
try {
|
|
123
|
+
if ( pgn.pgn == 59904 ) {
|
|
124
|
+
handleISORequest(this, pgn)
|
|
125
|
+
} else if ( pgn.pgn == 126208 ) {
|
|
126
|
+
handleGroupFunction(this, pgn)
|
|
127
|
+
} else if ( pgn.pgn == 60928 ) {
|
|
128
|
+
handleISOAddressClaim(this, pgn)
|
|
129
|
+
} else if ( pgn.pgn == 126996 ) {
|
|
130
|
+
handleProductInformation(this, pgn)
|
|
131
|
+
}
|
|
132
|
+
} catch ( err ) {
|
|
133
|
+
console.error(err)
|
|
134
|
+
console.error(err.stack)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/*
|
|
138
|
+
var handler = this.handlers[pgn.pgn.toString()]
|
|
139
|
+
if ( pgn.dst == this.address )
|
|
140
|
+
debug(`handler ${handler}`)
|
|
141
|
+
if ( _.isFunction(handler) ) {
|
|
142
|
+
debug(`got handled PGN %j ${handled}`, pgn)
|
|
143
|
+
handler(pgn)
|
|
144
|
+
}
|
|
145
|
+
*/
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
sendPGN(pgn, src) {
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function handleISORequest(device, n2kMsg) {
|
|
154
|
+
debug('handleISORequest %j', n2kMsg)
|
|
155
|
+
|
|
156
|
+
switch (n2kMsg.fields.PGN) {
|
|
157
|
+
case 126996: // Product Information request
|
|
158
|
+
sendProductInformation(device)
|
|
159
|
+
break;
|
|
160
|
+
case 126998: // Config Information request
|
|
161
|
+
sendConfigInformation(device)
|
|
162
|
+
break;
|
|
163
|
+
case 60928: // ISO address claim request
|
|
164
|
+
device.sendPGN(device.addressClaim)
|
|
165
|
+
break;
|
|
166
|
+
case 126464:
|
|
167
|
+
sendPGNList(device)
|
|
168
|
+
break;
|
|
169
|
+
default:
|
|
170
|
+
if ( !device.options.disableNAKs ) {
|
|
171
|
+
debug(`Got unsupported ISO request for PGN ${n2kMsg.fields.PGN}. Sending NAK.`)
|
|
172
|
+
sendNAKAcknowledgement(device, n2kMsg.src, n2kMsg.fields.PGN)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function handleGroupFunction(device, n2kMsg) {
|
|
178
|
+
debug('handleGroupFunction %j', n2kMsg)
|
|
179
|
+
if(n2kMsg.fields.functionCode === 'Request') {
|
|
180
|
+
handleRequestGroupFunction(device, n2kMsg)
|
|
181
|
+
} else if(n2kMsg.fields.functionCode === 'Command') {
|
|
182
|
+
handleCommandGroupFunction(device, n2kMsg)
|
|
183
|
+
} else {
|
|
184
|
+
debug('Got unsupported Group Function PGN: %j', n2kMsg)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function handleRequestGroupFunction(device, n2kMsg) {
|
|
188
|
+
if ( !device.options.disableNAKs ) {
|
|
189
|
+
// We really don't support group function requests for any PGNs yet -> always respond with pgnErrorCode 1 = "PGN not supported"
|
|
190
|
+
debug("Sending 'PGN Not Supported' Group Function response for requested PGN", n2kMsg.fields.PGN)
|
|
191
|
+
|
|
192
|
+
const acknowledgement = {
|
|
193
|
+
pgn: 126208,
|
|
194
|
+
dst: n2kMsg.src,
|
|
195
|
+
"Function Code": 2,
|
|
196
|
+
"PGN": n2kMsg.fields.PGN,
|
|
197
|
+
"PGN error code": 4,
|
|
198
|
+
"Transmission interval/Priority error code": 0,
|
|
199
|
+
"# of Parameters": 0
|
|
200
|
+
}
|
|
201
|
+
device.sendPGN(acknowledgement)
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function handleCommandGroupFunction(device, n2kMsg) {
|
|
206
|
+
if ( !device.options.disableNAKs ) {
|
|
207
|
+
// We really don't support group function commands for any PGNs yet -> always respond with pgnErrorCode 1 = "PGN not supported"
|
|
208
|
+
debug("Sending 'PGN Not Supported' Group Function response for commanded PGN", n2kMsg.fields.PGN)
|
|
209
|
+
|
|
210
|
+
const acknowledgement = {
|
|
211
|
+
pgn: 126208,
|
|
212
|
+
dst: n2kMsg.src,
|
|
213
|
+
"Function Code": 2,
|
|
214
|
+
"PGN": n2kMsg.fields.PGN,
|
|
215
|
+
"PGN error code": 4,
|
|
216
|
+
"Transmission interval/Priority error code": 0,
|
|
217
|
+
"# of Parameters": 0
|
|
218
|
+
}
|
|
219
|
+
device.sendPGN(acknowledgement)
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function handleISOAddressClaim(device, n2kMsg) {
|
|
225
|
+
if ( n2kMsg.src != device.address ) {
|
|
226
|
+
if ( !device.devices[n2kMsg.src] ) {
|
|
227
|
+
debug(`registering device ${n2kMsg.src}`)
|
|
228
|
+
device.devices[n2kMsg.src] = { addressClaim: n2kMsg }
|
|
229
|
+
if ( device.cansend ) {
|
|
230
|
+
//sendISORequest(device, 126996, undefined, n2kMsg.src)
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
debug('Checking ISO address claim. %j', n2kMsg)
|
|
237
|
+
|
|
238
|
+
const uint64ValueFromReceivedClaim = getISOAddressClaimAsUint64(n2kMsg)
|
|
239
|
+
const uint64ValueFromOurOwnClaim = getISOAddressClaimAsUint64(device.addressClaim)
|
|
240
|
+
|
|
241
|
+
if(uint64ValueFromOurOwnClaim < uint64ValueFromReceivedClaim) {
|
|
242
|
+
debug(`Address conflict detected! Kept our address as ${device.address}.`)
|
|
243
|
+
sendAddressClaim(device) // We have smaller address claim data -> we can keep our address -> re-claim it
|
|
244
|
+
} else if(uint64ValueFromOurOwnClaim > uint64ValueFromReceivedClaim) {
|
|
245
|
+
this.foundConflict = true
|
|
246
|
+
increaseOwnAddress(device) // We have bigger address claim data -> we have to change our address
|
|
247
|
+
sendAddressClaim(device)
|
|
248
|
+
debug(`Address conflict detected! Changed our address to ${device.address}.`)
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function increaseOwnAddress(device) {
|
|
253
|
+
var start = device.address
|
|
254
|
+
do {
|
|
255
|
+
device.address = (device.address + 1) % 253
|
|
256
|
+
} while ( device.address != start && device.devices[device.address] )
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function handleProductInformation(device, n2kMsg) {
|
|
260
|
+
if ( !device.devices[n2kMsg.src] ) {
|
|
261
|
+
device.devices[n2kMsg.src] = {}
|
|
262
|
+
}
|
|
263
|
+
debug('got production information %j', n2kMsg)
|
|
264
|
+
device.devices[n2kMsg.src].productInformation = n2kMsg
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function sendHeartbeat(device)
|
|
268
|
+
{
|
|
269
|
+
device.heartbeatCounter = device.heartbeatCounter + 1
|
|
270
|
+
if ( device.heartbeatCounter > 252 )
|
|
271
|
+
{
|
|
272
|
+
device.heartbeatCounter = 0
|
|
273
|
+
}
|
|
274
|
+
device.sendPGN({
|
|
275
|
+
pgn: 126993,
|
|
276
|
+
dst: 255,
|
|
277
|
+
prio:7,
|
|
278
|
+
"Data transmit offset": "00:01:00",
|
|
279
|
+
"Sequence Counter": device.heartbeatCounter,
|
|
280
|
+
"Controller 1 State":"Error Active"
|
|
281
|
+
})
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
function sendAddressClaim(device) {
|
|
286
|
+
if ( device.devices[device.address] ) {
|
|
287
|
+
//someone already has this address, so find a free one
|
|
288
|
+
increaseOwnAddress(device)
|
|
289
|
+
}
|
|
290
|
+
debug(`Sending address claim ${device.address}`)
|
|
291
|
+
device.sendPGN(device.addressClaim)
|
|
292
|
+
setTimeout(() => {
|
|
293
|
+
if ( !device.foundConflict ) {
|
|
294
|
+
debug('no address conflics, enabling send')
|
|
295
|
+
device.cansend = true
|
|
296
|
+
if ( device.options.app ) {
|
|
297
|
+
device.options.app.emit('nmea2000OutAvailable')
|
|
298
|
+
}
|
|
299
|
+
sendISORequest(device, 126996)
|
|
300
|
+
device.heartbeatInterval = setInterval(() => {
|
|
301
|
+
sendHeartbeat(device)
|
|
302
|
+
}, 60*1000)
|
|
303
|
+
/*
|
|
304
|
+
_.keys(device.devices).forEach((address) => {
|
|
305
|
+
sendISORequest(device, 126996, undefined, address)
|
|
306
|
+
})
|
|
307
|
+
*/
|
|
308
|
+
|
|
309
|
+
}
|
|
310
|
+
}, 250)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function sendISORequest(device, pgn, src, dst=255) {
|
|
314
|
+
debug(`Sending iso request for ${pgn} to ${dst}`)
|
|
315
|
+
|
|
316
|
+
const isoRequest = {
|
|
317
|
+
pgn: 59904,
|
|
318
|
+
dst: dst,
|
|
319
|
+
"PGN": pgn
|
|
320
|
+
}
|
|
321
|
+
device.sendPGN(isoRequest, src)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
function sendProductInformation(device) {
|
|
326
|
+
debug("Sending product info..")
|
|
327
|
+
|
|
328
|
+
device.sendPGN(device.productInfo)
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function sendConfigInformation(device) {
|
|
332
|
+
if ( device.configurationInfo ) {
|
|
333
|
+
debug("Sending config info..")
|
|
334
|
+
device.sendPGN(device.configurationInfo)
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function sendNAKAcknowledgement(device, src, requestedPGN) {
|
|
339
|
+
const acknowledgement = {
|
|
340
|
+
pgn: 59392,
|
|
341
|
+
dst: src,
|
|
342
|
+
Control: 1,
|
|
343
|
+
"Group Function": 255,
|
|
344
|
+
PGN: requestedPGN
|
|
345
|
+
}
|
|
346
|
+
device.sendPGN(device, acknowledgement)
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function sendPGNList(device, src) {
|
|
350
|
+
//FIXME: for now, adding everything that signalk-to-nmea2000 supports
|
|
351
|
+
//need a way for plugins, etc. to register the pgns they provide
|
|
352
|
+
const pgnList = {
|
|
353
|
+
pgn: 126464,
|
|
354
|
+
dst: src,
|
|
355
|
+
"Function Code": 0,
|
|
356
|
+
list: device.transmitPGNs
|
|
357
|
+
}
|
|
358
|
+
device.sendPGN(pgnList)
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function getISOAddressClaimAsUint64(pgn) {
|
|
362
|
+
return new Uint64LE(toPgn(pgn))
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
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 === "
|
|
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 === '
|
|
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/yddevice.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
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.
|
|
37
|
+
pgn.ydFullFormat = true
|
|
38
|
+
debug('Sending PGN %j', pgn)
|
|
39
|
+
this.app.emit(this.n2kOutEvent, pgn)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
sendActisenseFormat(msg) {
|
|
43
|
+
this.app.emit('ydFullRawOut', actisenseToYdgwFullRawFormat(msg))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = YdDevice
|
package/lib/ydgw02.js
CHANGED
|
@@ -20,7 +20,7 @@ const FromPgn = require('./fromPgn').Parser
|
|
|
20
20
|
const Parser = require('./fromPgn').Parser
|
|
21
21
|
const _ = require('lodash')
|
|
22
22
|
const { defaultTransmitPGNs } = require('./codes')
|
|
23
|
-
const { pgnToYdgwRawFormat, actisenseToYdgwRawFormat } = require('./toPgn')
|
|
23
|
+
const { pgnToYdgwRawFormat, pgnToYdgwFullRawFormat, actisenseToYdgwRawFormat } = require('./toPgn')
|
|
24
24
|
|
|
25
25
|
const pgnsSent = {}
|
|
26
26
|
|
|
@@ -63,6 +63,11 @@ function Ydgw02Stream (options, type) {
|
|
|
63
63
|
options.app.emit('connectionwrite', { providerId: options.providerId })
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
+
options.app.on('ydFullRawOut', (msgs) => {
|
|
67
|
+
this.sendYdgwFullPGN(msgs)
|
|
68
|
+
options.app.emit('connectionwrite', { providerId: options.providerId })
|
|
69
|
+
})
|
|
70
|
+
|
|
66
71
|
//this.sendString('$PDGY,N2NET_OFFLINE')
|
|
67
72
|
|
|
68
73
|
if ( type === 'usb' ) {
|
|
@@ -82,12 +87,24 @@ Ydgw02Stream.prototype.sendString = function (msg) {
|
|
|
82
87
|
Ydgw02Stream.prototype.sendPGN = function (pgn) {
|
|
83
88
|
let now = Date.now()
|
|
84
89
|
let lastSent = pgnsSent[pgn.pgn]
|
|
85
|
-
|
|
90
|
+
let msgs
|
|
91
|
+
if ( pgn.ydFullFormat === true ) {
|
|
92
|
+
msgs = pgnToYdgwFullRawFormat(pgn)
|
|
93
|
+
} else {
|
|
94
|
+
msgs = pgnToYdgwRawFormat(pgn)
|
|
95
|
+
}
|
|
96
|
+
msgs.forEach(raw => {
|
|
86
97
|
this.sendString(raw + '\r\n')
|
|
87
98
|
})
|
|
88
99
|
pgnsSent[pgn.pgn] = now
|
|
89
100
|
}
|
|
90
101
|
|
|
102
|
+
Ydgw02Stream.prototype.sendYdgwFullPGN = function (msgs) {
|
|
103
|
+
msgs.forEach(raw => {
|
|
104
|
+
this.sendString(raw + '\r\n')
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
91
108
|
Ydgw02Stream.prototype.sendYdgwPGN = function (msg) {
|
|
92
109
|
|
|
93
110
|
actisenseToYdgwRawFormat(msg).forEach(raw => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canboat/canboatjs",
|
|
3
|
-
"version": "3.0.0-beta.
|
|
3
|
+
"version": "3.0.0-beta.4",
|
|
4
4
|
"description": "Native javascript version of canboat",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
],
|
|
54
54
|
"license": "Apache-2.0",
|
|
55
55
|
"dependencies": {
|
|
56
|
-
"@canboat/pgns": "6.0.0-beta.
|
|
56
|
+
"@canboat/pgns": "6.0.0-beta.2",
|
|
57
57
|
"bit-buffer": "0.2.3",
|
|
58
58
|
"debug": "^4.3.4",
|
|
59
59
|
"dnssd": "^0.4.1",
|