Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add B&G WS320 related PGNs (65309 and 65312) #224

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions bandg/65309.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module.exports = [
{
source: 'Battery Status',
node: function(n2k){
return `instruments.wireless.${n2k.src}.batteryStatus`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think using src as the instance identifier makes sense, as it can change essentially randomly when the bus reshuffles addresses. @sbender9 should this be canId?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it should probably use canId. The canId may not be there initially, so should ignore messages until it is there.

}
},{
source: 'Battery Charge Status',
node: function(n2k){
return `instruments.wireless.${n2k.src}.batteryChargeStatus`
}
},{
source: 'Status',
node: function(n2k){
return `instruments.wireless.${n2k.src}.status`
}
},
]
8 changes: 8 additions & 0 deletions bandg/65312.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = [
{
source: 'Signal Strength',
node: function(n2k){
return `instruments.wireless.${n2k.src}.signalStrength`
}
}
]
5 changes: 5 additions & 0 deletions bandg/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
module.exports = {
65309: require('./65309.js'),
65312: require('./65312.js'),
}

147 changes: 84 additions & 63 deletions n2kMapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ var debug = require('debug')('signalk:n2k-signalk')
const toPgn = require('@canboat/canboatjs').toPgn
const Uint64LE = require('int64-buffer').Uint64LE

require('util').inherits(N2kMapper, EventEmitter);
require('util').inherits(N2kMapper, EventEmitter)

var n2kMappings = {}
Object.assign(n2kMappings, require('./pgns'))
Expand All @@ -14,41 +14,46 @@ Object.assign(n2kMappings, require('./raymarine'))
Object.assign(n2kMappings, require('./maretron'))
Object.assign(n2kMappings, require('./actisense'))
Object.assign(n2kMappings, require('./digitalyacht'))
Object.assign(n2kMappings, require('./bandg'))

function N2kMapper (options) {
this.state = { }
this.state = {}
this.unknownPGNs = {}
this.customPgns = {}
this.options = options || {}
if ( this.options.onPropertyValues ) {

if (this.options.onPropertyValues) {
this.options.onPropertyValues('pgn-to-signalk', values => {
values.filter(v => v !== undefined).forEach(pv => {
Object.entries(pv.value).forEach(([pgnNumber, mappings]) => {
if ( n2kMappings[pgnNumber] &&
!this.options.allowCustomPGNOverride ) {
console.error(`pgn ${pgnNumber} can't be overwritten`)
} else {
this.customPgns[pgnNumber] = mappings
debug('registered custom pgn %d by %s', pgnNumber, pv.setter)
}
values
.filter(v => v !== undefined)
.forEach(pv => {
Object.entries(pv.value).forEach(([pgnNumber, mappings]) => {
if (
n2kMappings[pgnNumber] &&
!this.options.allowCustomPGNOverride
) {
console.error(`pgn ${pgnNumber} can't be overwritten`)
} else {
this.customPgns[pgnNumber] = mappings
debug('registered custom pgn %d by %s', pgnNumber, pv.setter)
}
})
})
})
})
}
}

N2kMapper.prototype.n2kOutIsAvailable = function(listener, event) {
N2kMapper.prototype.n2kOutIsAvailable = function (listener, event) {
this.n2kOutEvent = event
this.n2kListener = listener
this.requestAllMeta()
}

N2kMapper.prototype.requestMetaData = function(dst, pgn) {
N2kMapper.prototype.requestMetaData = function (dst, pgn) {
const reqPgn = {
"pgn": 59904,
"dst": dst,
"PGN": pgn
pgn: 59904,
dst: dst,
PGN: pgn
}
debug(`requesting pgn ${pgn} from src ${dst}`)
return new Promise((resolve, reject) => {
Expand All @@ -59,81 +64,92 @@ N2kMapper.prototype.requestMetaData = function(dst, pgn) {
})
}

N2kMapper.prototype.requestMetaPGNs = async function(dst, pgns) {
for ( let i = 0; i < pgns.length; i++ ) {
N2kMapper.prototype.requestMetaPGNs = async function (dst, pgns) {
for (let i = 0; i < pgns.length; i++) {
await this.requestMetaData(dst, pgns[i])
}
}

N2kMapper.prototype.checkSrcMetasAndRetry = function(src) {
if ( src !== "255" ) {
N2kMapper.prototype.checkSrcMetasAndRetry = function (src) {
if (src !== '255') {
const neededPGNs = Object.keys(metaPGNs).filter(pgn => {
return !this.state[src].metaPGNsReceived ||
return (
!this.state[src].metaPGNsReceived ||
!this.state[src].metaPGNsReceived[pgn]
)
})
if ( neededPGNs.length > 0 ) {
if (neededPGNs.length > 0) {
debug('did not get meta pgns %j for src %d', neededPGNs, src)
this.requestMetaPGNs(src, neededPGNs)
.then(() => {
neededPGNs.forEach(pgn => {
if (!this.state[src].metaPGNsReceived ||
!this.state[src].metaPGNsReceived[pgn]) {
debug(`did not get meta pgn ${pgn} for src ${src}`)
this.emit('n2kSourceMetadataTimeout', pgn, src)
}
})
this.requestMetaPGNs(src, neededPGNs).then(() => {
neededPGNs.forEach(pgn => {
if (
!this.state[src].metaPGNsReceived ||
!this.state[src].metaPGNsReceived[pgn]
) {
debug(`did not get meta pgn ${pgn} for src ${src}`)
this.emit('n2kSourceMetadataTimeout', pgn, src)
}
})
})
}
}
}

N2kMapper.prototype.requestAllMeta = function() {
this.requestMetaPGNs(255, Object.keys(metaPGNs))
.then(() => {
Object.keys(this.state).forEach(src => this.checkSrcMetasAndRetry(src))
})
N2kMapper.prototype.requestAllMeta = function () {
this.requestMetaPGNs(255, Object.keys(metaPGNs)).then(() => {
Object.keys(this.state).forEach(src => this.checkSrcMetasAndRetry(src))
})
}

N2kMapper.prototype.toDelta = function(n2k) {
if ( metaPGNs[n2k.pgn] ) {
N2kMapper.prototype.toDelta = function (n2k) {
if (metaPGNs[n2k.pgn]) {
const meta = metaPGNs[n2k.pgn](n2k)
if ( ! this.state[n2k.src] ) {
if (!this.state[n2k.src]) {
this.state[n2k.src] = {}
}

if ( !this.state[n2k.src].metaPGNsReceived ) {
if (!this.state[n2k.src].metaPGNsReceived) {
this.state[n2k.src].metaPGNsReceived = {}
}

if ( n2k.pgn === 60928 ) {
if (n2k.pgn === 60928) {
const canName = new Uint64LE(toPgn(n2k)).toString(16)
if ( ! this.state[n2k.src] ) {
if (!this.state[n2k.src]) {
this.state[n2k.src] = {}
} else if ( this.state[n2k.src].canName && this.state[n2k.src].canName != canName ) {
} else if (
this.state[n2k.src].canName &&
this.state[n2k.src].canName != canName
) {
// clear out any existing state since the src addresses have changed
this.emit('n2kSourceChanged', n2k.src, this.state[n2k.src].canName, canName)
this.emit(
'n2kSourceChanged',
n2k.src,
this.state[n2k.src].canName,
canName
)
this.state[n2k.src] = {}
this.requestMetaData(n2k.src, 126996)
.then(() => {
return this.requestMetaData(n2k.src, 126998)
})
this.requestMetaData(n2k.src, 126996).then(() => {
return this.requestMetaData(n2k.src, 126998)
})
}
this.state[n2k.src].deviceInstance = meta.deviceInstance
meta.canName = canName
this.state[n2k.src].canName = canName
}

this.state[n2k.src].metaPGNsReceived[n2k.pgn] = Date.now()

this.emit('n2kSourceMetadata', n2k, meta)
} else {
if ( !n2kMappings[n2k.pgn] ) {
if ( !this.unknownPGNs[n2k.src] ) {
if (!n2kMappings[n2k.pgn]) {
if (!this.unknownPGNs[n2k.src]) {
this.unknownPGNs[n2k.src] = {}
}
if ( !this.unknownPGNs[n2k.src][n2k.pgn] ) {
if (!this.unknownPGNs[n2k.src][n2k.pgn]) {
this.unknownPGNs[n2k.src][n2k.pgn] = n2k
this.emit('n2kSourceMetadata', n2k, { unknownPGNs: this.unknownPGNs[n2k.src] })
this.emit('n2kSourceMetadata', n2k, {
unknownPGNs: this.unknownPGNs[n2k.src]
})
}
}
return toDelta(n2k, this.state, this.customPgns)
Expand All @@ -144,7 +160,10 @@ var toDelta = function (n2k, state, customPgns = {}) {
try {
var theMappings, customMappings

theMappings = [ ...customPgns[n2k.pgn] || [], ...n2kMappings[n2k.pgn] || []]
theMappings = [
...(customPgns[n2k.pgn] || []),
...(n2kMappings[n2k.pgn] || [])
]

var src_state
if (state) {
Expand All @@ -171,10 +190,10 @@ var toDelta = function (n2k, state, customPgns = {}) {
}
]
}
if ( src_state && src_state.canName ) {
if (src_state && src_state.canName) {
result.updates[0].source.canName = src_state.canName
}
if ( src_state && typeof src_state.deviceInstance !== 'undefined' ) {
if (src_state && typeof src_state.deviceInstance !== 'undefined') {
result.updates[0].source.deviceInstance = src_state.deviceInstance
}
if (
Expand Down Expand Up @@ -329,7 +348,7 @@ function addAsNested (pathValue, source, timestamp, result) {
}

const metaPGNs = {
60928: (n2k) => {
60928: n2k => {
return {
uniqueId: n2k.fields['Unique Number'],
manufacturerName: n2k.fields['Manufacturer Code'],
Expand All @@ -338,18 +357,20 @@ const metaPGNs = {
deviceInstanceLower: n2k.fields['Device Instance Lower'],
deviceInstanceUpper: n2k.fields['Device Instance Upper'],
systemInstance: n2k.fields['System Instance'],
deviceInstance: (n2k.fields['Device Instance Upper'] << 3) | n2k.fields['Device Instance Lower']
deviceInstance:
(n2k.fields['Device Instance Upper'] << 3) |
n2k.fields['Device Instance Lower']
}
},
126998: (n2k) => {
126998: n2k => {
return {
installationNote1: n2k.fields['Installation Description #1'],
installationNote2: n2k.fields['Installation Description #2'],
installationNote3: n2k.fields['Installation Description #3'],
manufacturerInfo: n2k.fields['Manufacturer Information']
}
},
126996: (n2k) => {
126996: n2k => {
return {
productName: n2k.fields['Model ID'],
hardwareVersion: n2k.fields['Model Version'],
Expand Down
82 changes: 82 additions & 0 deletions test/bandg.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
const chai = require('chai')
chai.Should()
chai.use(require('chai-things'))
chai.use(require('@signalk/signalk-schema').chaiModule)

const N2kMapper = require('../n2kMapper').N2kMapper
const toNested = require('./testMapper').toNested

describe('B&G PGNs work', function () {
const n2kMapper = new N2kMapper()

n2kMapper.toDelta({
canId: 486481152,
prio: 7,
src: 0,
dst: 255,
pgn: 65309,
time: '1628312401044;A;06:25:52.641',
input: ['1628312401044;A;06:25:52.641 R 1CFF1D00 13 99 00 4E 0A FF FF 7F'],
fields: {
'Manufacturer Code': 'Navico',
'Industry Code': 'Marine Industry',
Status: 0,
'Battery Status': 78,
'Battery Charge Status': 10,
Reserved2: 8388607
},
description: 'Navico: Wireless Battery Status',
timestamp: '2021-09-05T14:43:06.818Z'
})

it('65309 WS320 Battery status', function () {
var tree = toNested(
JSON.parse(
'{"canId":486481152,"prio":7,"src":0,"dst":255,"pgn":65309,"time":"1628312401044;A;06:25:52.641","input":["1628312401044;A;06:25:52.641 R 1CFF1D00 13 99 00 4E 0A FF FF 7F"],"fields":{"Manufacturer Code":"Navico","Industry Code":"Marine Industry","Status":0,"Battery Status":78,"Battery Charge Status":10,"Reserved2":8388607},"description":"Navico: Wireless Battery Status","timestamp":"2021-09-05T14:43:06.818Z"}'
),
n2kMapper.state
)

tree.should.have.nested.property(
'instruments.wireless.0.batteryStatus.value',
78
)
tree.should.have.nested.property(
'instruments.wireless.0.batteryChargeStatus.value',
10
)
tree.should.have.nested.property('instruments.wireless.0.status.value', 0)
})
n2kMapper.toDelta({
canId: 486481920,
prio: 7,
src: 0,
dst: 255,
pgn: 65312,
time: '1628312404394;A;06:25:56.008',
input: ['1628312404394;A;06:25:56.008 R 1CFF2000 13 99 00 2D 7F FF FF FF'],
fields: {
'Manufacturer Code': 'Navico',
'Industry Code': 'Marine Industry',
Unknown: 0,
'Signal Strength': 45,
Reserved2: 16777087
},
description: 'Navico: Wireless Signal Status',
timestamp: '2021-09-05T14:43:06.823Z'
})

it('65312 WS320 Signal status', function () {
var tree = toNested(
JSON.parse(
'{"canId":486481920,"prio":7,"src":0,"dst":255,"pgn":65312,"time":"1628312404394;A;06:25:56.008","input":["1628312404394;A;06:25:56.008 R 1CFF2000 13 99 00 2D 7F FF FF FF"],"fields":{"Manufacturer Code":"Navico","Industry Code":"Marine Industry","Unknown":0,"Signal Strength":45,"Reserved2":16777087},"description":"Navico: Wireless Signal Status","timestamp":"2021-09-05T14:43:06.823Z"}'
),
n2kMapper.state
)

tree.should.have.nested.property(
'instruments.wireless.0.signalStrength.value',
45
)
})
})