From 49f98963fc8a03c49974cae4967307efe2c0394d Mon Sep 17 00:00:00 2001 From: jncarter123 <30420708+jncarter123@users.noreply.github.com> Date: Sat, 7 Mar 2020 16:33:19 -0600 Subject: [PATCH] feature: adds support for alert PGNs 126983 and 126985 (#173) NMEA has standardized PGNs to handle alerts. The main alert is handled in 126983 with the alert text handled in 126985. The alert is sent ~1/sec with the alert text sent ~1/10 sec or possibly longer. Because the notifications schema only allows for a single value, we wait until we have data from both PGNs before we process the update and create the notification. --- pgns/126983.js | 86 +++++++++++++++++++++++++++++++++++ pgns/126985.js | 24 ++++++++++ pgns/index.js | 2 + test/126983_alert.js | 96 +++++++++++++++++++++++++++++++++++++++ test/126985_alert_text.js | 23 ++++++++++ 5 files changed, 231 insertions(+) create mode 100644 pgns/126983.js create mode 100644 pgns/126985.js create mode 100644 test/126983_alert.js create mode 100644 test/126985_alert_text.js diff --git a/pgns/126983.js b/pgns/126983.js new file mode 100644 index 0000000..a293e4f --- /dev/null +++ b/pgns/126983.js @@ -0,0 +1,86 @@ +// ALERT +const debug = require('debug')('n2k-signalk-126983') + +const alertTypes = [{ + "nmea": "Emergency Alarm", + "sk": "emergency" + }, + { + "nmea": "Alarm", + "sk": "alarm" + }, + { + "nmea": "Warning", + "sk": "warn" + }, + { + "nmea": "Caution", + "sk": "alert" + } +] + +module.exports = [{ + node: function(n2k, state) { + var alertType = n2k.fields['Alert Type'].replace(/ /g, '').toLowerCase() + + var alertCategory = n2k.fields['Alert Category'].toLowerCase() + + var path = 'notifications.nmea.' + alertType + '.' + alertCategory + '.' + n2k.fields['Alert System'] + '.' + n2k.fields['Alert ID'] + debug('126983 path: ' + path) + + return path + }, + value: function(n2k, state) { + debug('126983 value') + + var skstate = alertTypes.filter(alertType => alertType.nmea === n2k.fields['Alert Type']) + debug('126983 skstate: ' + skstate[0].sk) + + var alertId = n2k.fields['Alert ID'] + + var value = { + "state": skstate[0].sk, + "method": [ + "visual", + "sound" + ], + "message": state.alerts[alertId].textDescription, + "alertType": n2k.fields['Alert Type'], + "alertCategory": n2k.fields['Alert Category'], + "alertSystem": n2k.fields['Alert System'], + "alertId": n2k.fields['Alert ID'], + "dataSourceNetworkIDNAME": n2k.fields['Data Source Network ID NAME'], + "dataSourceInstance": n2k.fields['Data Source Instance'], + "dataSourceIndex-Source": n2k.fields['Data Source Index-Source'], + "occurrence": n2k.fields['Alert Occurrence Number'], + "temporarySilenceStatus": n2k.fields['Temporary Silence Status'], + "acknowledgeStatus": n2k.fields['Acknowledge Status'], + "escalationStatus": n2k.fields['Escalation Status'], + "temporarySilenceSupport": n2k.fields['Temporary Silence Support'], + "acknowledgeSupport": n2k.fields['Acknowledge Support'], + "escalationSupport": n2k.fields['Escalation Support'], + "acknowledgeSourceNetworkIDNAME": n2k.fields['Acknowledge Source Network ID NAME'], + "triggerCondition": n2k.fields['Trigger Condition'], + "thresholdStatus": n2k.fields['Threshold Status'], + "alertPriority": n2k.fields['Alert Priority'], + "alertState": n2k.fields['Alert State'] + } + + //if the alert is silenced or acknowledged then dont alert in SK + if (n2k.fields['Temporary Silence Status'] == 'Temporary Silence' || + n2k.fields['Acknowledge Status'] == 'Acknowledged') { + value.method = [] + } + debug('126983 value: ' + JSON.stringify(value)) + + return value + }, + filter: function(n2k, state) { + return ( + n2k.fields['Alert Type'] && + typeof state === 'object' && + state.alerts && + state.alerts[n2k.fields['Alert ID']] + ) + } +}] diff --git a/pgns/126985.js b/pgns/126985.js new file mode 100644 index 0000000..a7d4a61 --- /dev/null +++ b/pgns/126985.js @@ -0,0 +1,24 @@ +//ALERT TEXT +const debug = require('debug')('n2k-signalk-126985') + +module.exports = [{ + filter: function(n2k, state) { + if (typeof state !== 'undefined') { + var alertId = n2k.fields['Alert ID'] + var text = { + languageId: n2k.fields['Language ID'], + textDescription: n2k.fields['Alert Text Description'], + locationTextDescription: n2k.fields['Alert Location Text Description'] || '' + } + //store the alert text in state for use with PGN 126983 + if (!state.alerts) { + state.alerts = {} + } + state.alerts[alertId] = text + + debug('set alert state text: ' + JSON.stringify(text)) + } + return false + }, + source: 'Alert Text' +}] diff --git a/pgns/index.js b/pgns/index.js index 927f599..7d30821 100644 --- a/pgns/index.js +++ b/pgns/index.js @@ -1,4 +1,6 @@ module.exports = { + 126983: require('./126983.js'), + 126985: require('./126985.js'), 126992: require('./126992.js'), 127245: require('./127245.js'), 127250: require('./127250.js'), diff --git a/test/126983_alert.js b/test/126983_alert.js new file mode 100644 index 0000000..608a157 --- /dev/null +++ b/test/126983_alert.js @@ -0,0 +1,96 @@ +var chai = require('chai') +chai.Should() +chai.use(require('chai-things')) +chai.use(require('@signalk/signalk-schema').chaiModule) +var expect = chai.expect; + +var mapper = require('./testMapper') + +//provided by PGN 126985 Alert Text +var state = { + "40": { + "alerts": { + "23480": { + "languageId": "English (US)", + "locationTextDescription": "", + "textDescription": "TEST: Temperature over 0" + } + } + } +} + +var value = { + "state": "warn", + "method": [ + "visual", + "sound" + ], + "message": "TEST: Temperature over 0", + "alertType": "Warning", + "alertCategory": "Navigational", + "alertSystem": 20, + "alertId": 23480, + "dataSourceNetworkIDNAME": 6458553273545042000, + "dataSourceInstance": 0, + "dataSourceIndex-Source": 0, + "occurrence": 1, + "temporarySilenceStatus": "Not Temporary Silence", + "acknowledgeStatus": "Not Acknowledged", + "escalationStatus": "Not Escalated", + "temporarySilenceSupport": "Supported", + "acknowledgeSupport": "Supported", + "escalationSupport": "Not Supported", + "acknowledgeSourceNetworkIDNAME": 1233993542451364400, + "triggerCondition": "Auto", + "thresholdStatus": "Threshold Exceeded", + "alertPriority": 187, + "alertState": "Active" +} + +describe('126983 Alert', function() { + it('alert without silence or ackknowledgement', function() { + var msg = JSON.parse( + '{"canId":166725416,"prio":2,"src":40,"dst":255,"pgn":126983,"direction":"R","time":"15:48:36.090","fields":{"Alert Type":"Warning","Alert Category":"Navigational","Alert System":20,"Alert ID":23480,"Data Source Network ID NAME":6458553273545042000,"Data Source Instance":0,"Data Source Index-Source":0,"Alert Occurrence Number":1,"Temporary Silence Status":"Not Temporary Silence","Acknowledge Status":"Not Acknowledged","Escalation Status":"Not Escalated","Temporary Silence Support":"Supported","Acknowledge Support":"Supported","Escalation Support":"Not Supported","Acknowledge Source Network ID NAME":1233993542451364400,"Trigger Condition":"Auto","Threshold Status":"Threshold Exceeded","Alert Priority":187,"Alert State":"Active"},"description":"Alert","timestamp":"2020-03-03T15:48:36.494Z"}' + ) + var tree = mapper.toNested(msg, state) + tree.should.have.nested.property('notifications.nmea.warning.navigational.20.23480.value') + + expect(tree.notifications.nmea.warning.navigational[20][23480].value).to.deep.include(value) + + var delta = mapper.toDelta(msg) + delta.updates.length.should.equal(1) + }) + + it('alert with temporary silence', function() { + //method should be empty for this test + value.method = [] + value.temporarySilenceStatus = 'Temporary Silence' + + var msg = JSON.parse( + '{"canId":166725416,"prio":2,"src":40,"dst":255,"pgn":126983,"direction":"R","time":"15:48:36.090","fields":{"Alert Type":"Warning","Alert Category":"Navigational","Alert System":20,"Alert ID":23480,"Data Source Network ID NAME":6458553273545042000,"Data Source Instance":0,"Data Source Index-Source":0,"Alert Occurrence Number":1,"Temporary Silence Status":"Temporary Silence","Acknowledge Status":"Not Acknowledged","Escalation Status":"Not Escalated","Temporary Silence Support":"Supported","Acknowledge Support":"Supported","Escalation Support":"Not Supported","Acknowledge Source Network ID NAME":1233993542451364400,"Trigger Condition":"Auto","Threshold Status":"Threshold Exceeded","Alert Priority":187,"Alert State":"Active"},"description":"Alert","timestamp":"2020-03-03T15:48:36.494Z"}' + ) + var tree = mapper.toNested(msg, state) + tree.should.have.nested.property('notifications.nmea.warning.navigational.20.23480.value') + + expect(tree.notifications.nmea.warning.navigational[20][23480].value).to.deep.include(value) + + var delta = mapper.toDelta(msg) + delta.updates.length.should.equal(1) + }) + + it('alert with acknowledgement', function() { + value.temporarySilenceStatus = 'Not Temporary Silence' + value.acknowledgeStatus = 'Acknowledged' + + var msg = JSON.parse( + '{"canId":166725416,"prio":2,"src":40,"dst":255,"pgn":126983,"direction":"R","time":"15:48:36.090","fields":{"Alert Type":"Warning","Alert Category":"Navigational","Alert System":20,"Alert ID":23480,"Data Source Network ID NAME":6458553273545042000,"Data Source Instance":0,"Data Source Index-Source":0,"Alert Occurrence Number":1,"Temporary Silence Status":"Not Temporary Silence","Acknowledge Status":"Acknowledged","Escalation Status":"Not Escalated","Temporary Silence Support":"Supported","Acknowledge Support":"Supported","Escalation Support":"Not Supported","Acknowledge Source Network ID NAME":1233993542451364400,"Trigger Condition":"Auto","Threshold Status":"Threshold Exceeded","Alert Priority":187,"Alert State":"Active"},"description":"Alert","timestamp":"2020-03-03T15:48:36.494Z"}' + ) + var tree = mapper.toNested(msg, state) + tree.should.have.nested.property('notifications.nmea.warning.navigational.20.23480.value') + + expect(tree.notifications.nmea.warning.navigational[20][23480].value).to.deep.include(value) + + var delta = mapper.toDelta(msg) + delta.updates.length.should.equal(1) + }) +}) diff --git a/test/126985_alert_text.js b/test/126985_alert_text.js new file mode 100644 index 0000000..7201779 --- /dev/null +++ b/test/126985_alert_text.js @@ -0,0 +1,23 @@ +var chai = require('chai') +chai.Should() +chai.use(require('chai-things')) +chai.use(require('@signalk/signalk-schema').chaiModule) +var expect = chai.expect; + +const mapper = require('./testMapper') + +var state = {} + +describe('126985 alert text', function () { + it('alert text converts', function () { + mapper.toNested( + JSON.parse( + '{"canId":166725928,"prio":2,"src":40,"dst":255,"pgn":126985,"direction":"R","time":"15:52:04.346","fields":{"Alert Type":"Warning","Alert Category":"Navigational","Alert System":20,"Alert ID":23480,"Data Source Network ID NAME":6458553273545042000,"Data Source Instance":0,"Data Source Index-Source":0,"Alert Occurrence Number":1,"Language ID":"English (US)","Alert Text Description":"TEST: Temperature over 0"},"description":"Alert Text","timestamp":"2020-03-03T15:52:04.505Z"}' + ), + state + ) + + var text = {"40":{"alerts":{"23480":{"languageId":"English (US)","locationTextDescription": "","textDescription":"TEST: Temperature over 0"}}}} + expect(state).to.deep.include(text) + }) +})