UNPKG

@signalk/nmea0183-signalk

Version:

A node.js/javascript parser for NMEA0183 sentences. Sentences are parsed to Signal K format.

349 lines (325 loc) 11.8 kB
"use strict"; /** * Copyright 2019 Signal K <info@signalk.org>. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const debug_1 = __importDefault(require("debug")); const debug = (0, debug_1.default)('signalk-parser-nmea0183/PBVE'); const alpha = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); const schema = { /* Sentence: $PBVE,BJAAAOAAABNCANIIBDAAPHABAAAACCABAAADAAHCJPACDIBOACAAGL Parse as: $PBVE,B,J,AA,AOAA,AB,NCAN,IIBD,AAPH,AB,AAAA,CC,ABAA,ADAA,HC,JP,ACDI,BO,AC,AAGL (https://www.electronicspoint.com/forums/threads/engine-hour-meter-with-nmea-output.159207/) 0: (0:1) B : Product Code = B = RH30 1: (1:1) J : Software Version # 2: (2:2) AA : Spare NMV Byte (Ignore) 3: (4:4) AOAA : Display Damping 4: (8:2) AB : Ignore 5: (10:4) NCAN : Maximum RPM seen from last reset 6: (14:4) IIBD : High RPM Alarm value 7: (18:4) AAPH : Clock Speed Calibration # 8: (22:2) AB : Backlight Level 9: (24:4) AAAA : Maintenance count-down alarm 10: (28:2) CC : Engine Minutes 11: (30:4) ABAA : Engine Hours 12: (34:4) ADAA : RPM Calibration number 14: (38:2) HC : Mode 15: (40:2) JP : Non voltatile memory checksum 16: (42:4) ACDI : RPM 17: (46:2) BO : Elapsed Seconds 18: (48:2) AC : Elapsed Minutes 19: (50:4) AAGL : Elapsed Hours. Where A=0, B=1, C=2, ... , O=14, P=15 Decode RPM as: ADAA = 16*A + D + 4096*A + 256*A ADAA = 16*0 + 3 + 4096*0 + 256*0 ADAA = 3 RPM Decode Engine Minutes as: CC = 16*C + C CC = 16*2 + 2 CC = 32 Engine Minutes */ B: { meta: { description: 'CruzPro RH30/RH60/RH110 Digital RPM/Engine Hours/Elapsed Time', displayName: 'Engine RPM/Hours', shortName: 'ERPM', warnMethod: ['visual'], alarmMethod: ['sound'], gaugeAlarmOn: false, backlight: 0, zones: [], originalValue: null } }, /* Note: oil pressure calculation formulas are the same for all instruments OP30/OP60 digital oil pressure gauge sample NMEA sentence: Sentence: $PBVE,DGOIADNNACAEACAAABBLAAEBAACMCFAAEPAIKI*37 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 D G OIAD NNAC AE AC AA AB BLAA EBAA CM CFAA EPAI KI*37 0: Product code: D = OP 30/60 1: Software version: G = ??? 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, otherwise bar 7: Built in alarms armed: AB = 1 = true 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 = 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*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 //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 */ 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 toInts(alphas) { const replacement = []; const chars = alphas.split(''); for (let i = 0; i < chars.length; i++) { replacement.push(alpha.indexOf(chars[i])); } return replacement; } /* @function convertToValue @param values String @return Int */ function convertToValue(values) { const parts = toInts(values); return 16 * parts[0] + parts[1] + 4096 * parts[2] + 256 * parts[3]; } /* @function convertToAlarmValue @param values String @return Int */ function convertToAlarmValue(values) { const parts = toInts(values); return 16 * parts[0] + parts[1] + 256 * parts[2]; } /* @function convertToEngineMinutes @param values String @return Int */ function convertToEngineMinutes(values) { const parts = toInts(values); return 16 * parts[0] + parts[1]; } const PBVE = function (input, _session) { const { parts, tags } = input; const data = parts[0]; const productCode = data.substr(0, 1); const schemaTable = schema; //just retun right away if not supported product code if (productCode in schema === false) { debug('Unsupported product code: ', productCode); return null; } if (productCode === 'B') { // Engine minutes in seconds const engineMinutes = convertToEngineMinutes(data.substr(28, 2)) * 60; // Engine hours in seconds const engineHours = convertToValue(data.substr(30, 4)) * 3600; const rpm = convertToValue(data.substr(42, 4)) / 60; const runTime = engineHours + engineMinutes; return { updates: [ { source: tags.source, timestamp: tags.timestamp, values: [ { value: rpm, path: 'propulsion.0.revolutions', meta: { description: 'CruzPro RH30/RH60/RH110 Digital RPM', units: 'hz' } }, { value: runTime, path: 'propulsion.0.runTime', meta: { description: 'CruzPro RH30/RH60/RH110 Engine Hours', units: 's' } } ] } ] }; } else if (productCode === 'D' || productCode === 'E') { 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)); const schemaEntry = schemaTable[productCode]; let convertedValue; if (productCode === 'D') { const conditionalValue = (derivedValue) => gaugeUnits === 'AA' ? derivedValue * 6894.757 : derivedValue * 100000; convertedValue = { //convert to pascals from psi/bar value: conditionalValue(value), path: schemaEntry.path, meta: schemaEntry.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 { // productCode === 'E' const conditionalValue = (derivedValue) => gaugeUnits === 'AA' ? (derivedValue - 32) * (5 / 9) + 273.15 : derivedValue + 273.15; convertedValue = { //convert to C from F value: conditionalValue(value), path: schemaEntry.path, meta: schemaEntry.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' } ]; } //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; return { updates: [ { source: tags.source, timestamp: tags.timestamp, values: [convertedValue] } ] }; } return null; }; exports.default = PBVE; //# sourceMappingURL=PBVE.js.map