From 591c6e1db909ced50068c28e178e753814f10122 Mon Sep 17 00:00:00 2001 From: Tony Bentley Date: Mon, 19 Aug 2019 13:22:52 -0700 Subject: [PATCH] feature: PBVE - auxillary engine temperature (#144) --- hooks/proprietary/PBVE.js | 209 ++++++++++++++++++++++++++++++++------ test/PBVE.js | 74 +++++++++++++- test/PNKEP.js | 4 +- 3 files changed, 250 insertions(+), 37 deletions(-) diff --git a/hooks/proprietary/PBVE.js b/hooks/proprietary/PBVE.js index e7a3350d..9ebd1050 100644 --- a/hooks/proprietary/PBVE.js +++ b/hooks/proprietary/PBVE.js @@ -20,10 +20,15 @@ const debug = require('debug')('signalk-parser-nmea0183/PBVE') const utils = require('@signalk/nmea0183-utilities') +const alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('') +const schema = { /* -OP30 digital oil pressure gauge sample NMEA sentence: +Note: oil pressure calculation formulas are the same for all instruments + + +OP30/OP60 digital oil pressure gauge sample NMEA sentence: Sentence: $PBVE,DGOIADNNACAEACAAABBLAAEBAACMCFAAEPAIKI*37 @@ -34,85 +39,229 @@ $PBVE,x,x,xxxx,xxxx,xx,xx,xx,xx,xxxx,xxxx,xx,xxxx,xxxx,xx*xx 0: Product code: D = OP 30/60 1: Software version: G = ??? -2: Oil pressure calibration number: OIAD = 14 8 0 3 = ??? -3: A2D gain: NNAC = ??? +2: Oil pressure calibration number: OIAD = 14 8 0 3 = 256*3 + 16*14 +8 = 1000 = 1.000 +3: A2D gain: NNAC = 13 13 0 3 = 256*03 + 16*13 +13= 989= 0.989 4: Sender Type: AE = 4 5: Backlight level: AC = 2 -6: Units of measure: AA = psi +6: Units of measure: AA = psi, otherwise bar 7: Built in alarms armed: AB = 1 = true -8: Low pressure alarm value: BLAA = 1 11 0 0 = ??? -9: High pressure alarm value: EBAA = 4 1 0 0 = ??? +8: Low pressure alarm value: BLAA = 1 11 0 0 = 256*0 + 16*1 +11= 27 psi +9: High pressure alarm value: EBAA = 4 1 0 0 = 256*0 + 16*4 +1= 65 psi 10: Checksum for Non-Volatile Memory: CM = 2 12 = ??? 11: Oil Pressure: CFAA = 37 -12: Sensor Volts: EPAI = 4 15 0 8 = ??? +12: Sensor Volts: EPAI = 4 15 0 8 = 256*8 + 16*4 +15= 2116=2.116 VDC 13: Checksum: KI = 10 8 14: Checksum: 37 Where A=0, B=1, C=2, ... , O=14, P=15 Decode Oil Pressure as: -CFAA = 16*M + L + 4096*A + 256*A +CFAA = 16*C + F + 4096*A + 256*A CFAA = 16*2 + 5 + 4096*0 + 256*0 CFAA = 37 psi -//ALPHA is an array map for converting sentence codes from letters to numbers +//alpha is an array map for converting sentence codes from letters to numbers //A=0, B=1, etc + +*/ + D: { + path: 'propulsion.0.oilPressure', + meta: { + description: 'CruzPro OP30/OP60 Engine Oil Pressure Gauge', + units: 'pa', + displayName: 'Engine Oil Pressure', + shortName: 'EOP', + warnMethod: ['visual'], + alarmMethod: ['sound'], + gaugeAlarmOn: false, + backlight: 0, + zones: [], + originalValue: null, + } + + }, +/* + +T30/T60 digital temperature gauge sample NMEA sentence: + +Sentence: $PBVE,EDOIADOKACABABAAAACAPPCMABCGADABDOAEGL*20 + +Parse as: + 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 + | | | | | | | | | | | | | | | +$PBVE,x,x,xxxx,xxxx,xx,xx,xx,xx,xxxx,xxxx,xx,xxxx,xxxx,xx*xx +$PBVE,E D OIAD OKAC AB AB AA AA CAPP CMAB CG ADAB DOAE GL 20 + +0: Product Code = E = T30/T60 +1: Software Version # +2: Temperature Calibration Number +3: A2D gain +4: Sender Type +5: Backlight Level +6: Units of Measure (0= deg F, non-0= deg C) +7: Built-in Alarms Armed (AA=0 = NO) +8: Low temperature alarm value +9: High temperature alarm value +10: Checksum for Non-Volatile Memory +11: Engine Temperature +12: Sensor Volts +13: NMEA sentence checksum +14: Checksum + +Where A=0, B=1, C=2, ... , O=14, P=15 + +Decode Temperature as: +ADAB = 16*A + D + 4096*A + 256*B +ADAB = 16*0 + 3 + 4096*0 +256*1 +ADAB = 0 + 3 + 0 + 256 +ADAB = 259 deg F + */ -const ALPHA = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('') + E: { + path: 'propulsion.0.coolantTemperature', + meta: { + description: 'CruzPro T30/T60 Engine Coolant Temperature Gauge', + units: 'k', + displayName: 'Engine Coolant Temperature', + shortName: 'ECT', + warnMethod: ['visual'], + alarmMethod: ['sound'], + gaugeAlarmOn: false, + backlight: 0, + zones: [], + originalValue: null + } + } + +} /* @function convertAlphasToInts @param alphas String @return Array */ -function convertAlphasToInts(alphas){ +function toInts(alphas){ + let replacement = []; + let i; alphas = alphas.split('') - var replacement = []; - for(var i = 0; i < alphas.length;i++){ - const indexValue = ALPHA.indexOf(alphas[i]); - replacement.push(indexValue) + + for(i = 0; i < alphas.length; i++){ + replacement.push(alpha.indexOf(alphas[i])) } return replacement; } /* - @function convertToOilPressure - @param values Array + @function convertToValue + @param values String @return Int */ -function convertToOilPressure(values){ - return (16 * values[0]) + values[1] + (4096 * values[2]) + (256 * values[3]) +function convertToValue(values){ + const parts = toInts(values) + return (16 * parts[0]) + parts[1] + (4096 * parts[2]) + (256 * parts[3]) } -module.exports = function(input) { - const { id, sentence, parts, tags } = input +/* + @function convertToAlarmValue + @param values String + @return Int +*/ +function convertToAlarmValue(values){ + const parts = toInts(values) + return ( (16 * parts[0]) + parts[1] + (256 * parts[2]) ) +} - let values = [] +module.exports = function(input) { + let convertedValue = {} let delta = {} + const { id, sentence, parts, tags } = input const data = parts[0] const productCode = data.substr(0,1) - if(productCode !== 'D'){ - debug('Unsupported CruzPro instrument type') + //just retun right away if not supported product code + if (productCode in schema === false) { + debug('Unsupported product code: ', productCode) return; } - const convertedValue = convertAlphasToInts(data.substr(28,4)) - const oilPressure = convertToOilPressure(convertedValue) + const backlight = data.substr(8,2) + const gaugeUnits = data.substr(15,2) + const gaugeAlarmOn = data.substr(17,2) + const lower = convertToAlarmValue(data.substr(18,4)) + const upper = convertToAlarmValue(data.substr(22,4)) + const value = convertToValue(data.substr(28,4)) + + if (productCode === 'D') { + + const conditionalValue = function(derivedValue){ + return gaugeUnits === 'AA' ? derivedValue * 6894.757 : derivedValue * 100000 + } + + convertedValue = { + //convert to pascals from psi/bar + value: conditionalValue(value), + path: schema[productCode].path, + meta: schema[productCode].meta + } + convertedValue.meta.gaugeUnits = gaugeUnits === 'AA' ? 'psi' : 'bar' + //TODO: Add warning zone values + convertedValue.meta.zones = [ + { + lower: conditionalValue(lower), + state: 'alarm', + message: 'Engine oil pressure at lowest threshold' + }, + { + upper: conditionalValue(upper), + state: 'alarm', + message: 'Engine oil pressure at highest threshold' + } + ] + + } else if(productCode === 'E') { + + const conditionalValue = function(derivedValue){ + return (gaugeUnits === 'AA' ? (derivedValue - 32) * (5/9) + 273.15 : derivedValue + 273.15) + } + + convertedValue = { + //convert to C from F + value: conditionalValue(value), + path: schema[productCode].path, + meta: schema[productCode].meta + } + convertedValue.meta.gaugeUnits = gaugeUnits === 'AA' ? 'f' : 'c' + //TODO: Add warning zone values + convertedValue.meta.zones = [ + { + lower: conditionalValue(lower), + state: 'alarm', + message: 'Engine coolant temperature at lowest threshold' + }, + { + upper: conditionalValue(upper), + state: 'alarm', + message: 'Engine coolant temperature at highest threshold' + } + ] - const oilPressureValue = { - path: 'propulsion.0.oilPressure', - value: oilPressure } + //baclight is AA, AB, etc so just need second value + convertedValue.meta.backlight = toInts(backlight)[1] + // instrument gauge alarm is armed when second value is 1 (A,B) + convertedValue.meta.gaugeAlarmOn = toInts(gaugeAlarmOn)[1] === 1 + //store original value in meta + convertedValue.meta.originalValue = value; + delta = { updates: [{ source: tags.source, timestamp: tags.timestamp, - values: [oilPressureValue] + values: [convertedValue] }] } diff --git a/test/PBVE.js b/test/PBVE.js index 0e60d7b4..8303e499 100644 --- a/test/PBVE.js +++ b/test/PBVE.js @@ -19,14 +19,78 @@ const should = require('chai').Should() const toFull = require('./toFull') describe('PBVE', () => { - it('Converts OK using individual parser', () => { + + it('Converts engine oil pressure using individual parser', () => { const delta = new Parser().parse('$PBVE,DGOIADNNACAEACAAABBLAAEBAACMCFAAEPAIKI*37') delta.updates[0].values.should.contain.an.item.with.property('path', 'propulsion.0.oilPressure') - delta.updates[0].values[0].value.should.equal(37) + delta.updates[0].values[0].value.should.equal(255106.009) + delta.updates[0].values[0].meta.should.deep.equal( + { + description: 'CruzPro OP30/OP60 Engine Oil Pressure Gauge', + units: 'pa', + displayName: 'Engine Oil Pressure', + shortName: 'EOP', + warnMethod: [ 'visual' ], + alarmMethod: [ 'sound' ], + gaugeAlarmOn: true, + backlight: 2, + originalValue: 37, + gaugeUnits: 'psi', + zones: [ + { + lower: 186158.43899999998, + state: 'alarm', + message: 'Engine oil pressure at lowest threshold' + }, + { + upper: 448159.20499999996, + state: 'alarm', + message: 'Engine oil pressure at highest threshold' + } + ] + } + ) + + toFull(delta).should.be.validSignalK + }) + it('Converts engine coolant temperature using individual parser', () => { + const delta = new Parser().parse('$PBVE,EDOIADOKACABABAAAACAPPCMABCGADABDOAEGL*20') + delta.updates[0].values.should.contain.an.item.with.property('path', 'propulsion.0.coolantTemperature') + delta.updates[0].values[0].value.should.equal(399.26111111111106) + delta.updates[0].values[0].meta.should.deep.equal( + { + description: 'CruzPro T30/T60 Engine Coolant Temperature Gauge', + units: 'k', + displayName: 'Engine Coolant Temperature', + shortName: 'ECT', + warnMethod: ['visual'], + alarmMethod: ['sound'], + gaugeAlarmOn: false, + backlight: 2, + originalValue: 259, + gaugeUnits: 'f', + zones: [ + { + lower: 2406.4833333333336, + state: 'alarm', + message: 'Engine coolant temperature at lowest threshold' + }, + { + upper: 279.81666666666666, + state: 'alarm', + message: 'Engine coolant temperature at highest threshold' + } + ] + } + ) + toFull(delta).should.be.validSignalK }) - //currently only produce code D is supported so test that all others do not process - it('Will not convert if productCode not D', () => { - should.equal(new Parser().parse('$PBVE,FGLABCGJAAAHADEE*21'),undefined) + + it('Should not parse if product code is F, B, or A', () => { + should.not.exist(new Parser().parse('$PBVE,FGLABCGJAAAHADEE*21')) + should.not.exist(new Parser().parse('$PBVE,BEAAADAAABFKBCIIBDAGOOABAAAAADAAAAOIACAAONFMAKCAADAAAAHG*2A')) + should.not.exist(new Parser().parse('$PBVE,AQHKAAAACIAAAAABAAGEDOBCAAAAFLABCKAADIAADIAAAAAANDEN*3F')) }) + }) diff --git a/test/PNKEP.js b/test/PNKEP.js index b3708bac..c7f60475 100644 --- a/test/PNKEP.js +++ b/test/PNKEP.js @@ -15,11 +15,11 @@ */ const Parser = require('../lib') +const toFull = require("./toFull") const chai = require("chai") const should = chai.Should() -chai.use(require('@signalk/signalk-schema').chaiModule) -const toFull = require("./toFull") +chai.use(require('@signalk/signalk-schema').chaiModule) chai.use(require("chai-things")) describe("PNKEP", () => {