├── .npmrc ├── .DS_Store ├── .prettierrc.json ├── test ├── .DS_Store ├── toFull.js ├── invalid_checksum.js ├── proprietary.js ├── MWV.js ├── VDO.js ├── RSA.js ├── ROT.js ├── VPW.js ├── DSC.js ├── HDT.js ├── HDM.js ├── RPM.js ├── tagblock.js ├── XTE.js ├── VDR.js ├── MTW.js ├── DBK.js ├── DBS.js ├── info.js ├── VLW.js ├── MTA.js ├── HSC.js ├── ZDA.js ├── DBT.js ├── GLL.js ├── PNKEP.js ├── customSentenceParser.js ├── VHW.js ├── VWR.js ├── BOD.js ├── VTG.js ├── BWR.js ├── APB.js ├── HDG.js ├── BWC.js ├── RMB.js ├── DPT.js ├── PBVE.js └── MWD.js ├── .github ├── dependabot.yml └── workflows │ ├── require_pr_label.yml │ ├── publish.yml │ ├── test.yml │ └── release_on_tag.yml ├── custom-sentence-plugin ├── package.json └── index.js ├── LICENSE ├── hooks ├── seatalk │ ├── index.js │ ├── 0x52.js │ ├── 0x22.js │ ├── 0x21.js │ ├── 0x20.js │ ├── 0x27.js │ ├── 0x57.js │ ├── 0x11.js │ ├── 0x10.js │ ├── 0x53.js │ ├── 0x56.js │ ├── 0x99.js │ ├── 0x25.js │ ├── 0x50.js │ ├── 0x51.js │ ├── 0x54.js │ ├── 0x00.js │ ├── 0x26.js │ └── 0x9C.js ├── VDO.js ├── ALK.js ├── index.js ├── ROT.js ├── MTW.js ├── proprietary │ ├── PSMDST.js │ └── PNKEP.js ├── RPM.js ├── RSA.js ├── MTA.js ├── VPW.js ├── DBK.js ├── DBS.js ├── VDR.js ├── HDT.js ├── HDM.js ├── VLW.js ├── MWV.js ├── HSC.js ├── DBT.js ├── VHW.js ├── VWR.js ├── DPT.js ├── XTE.js ├── BOD.js ├── GLL.js ├── MWD.js ├── ZDA.js ├── VTG.js ├── BWR.js ├── BWC.js ├── HDG.js ├── VWT.js ├── RMB.js ├── RMC.js └── MDA.js ├── bin └── nmea0183-signalk ├── index.js ├── .gitignore ├── lib ├── transformSource.js ├── CompatParser.js ├── getTagBlock.js └── index.js └── package.json /.npmrc: -------------------------------------------------------------------------------- 1 | test 2 | .travis.yml 3 | -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignalK/nmea0183-signalk/HEAD/.DS_Store -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true 4 | } 5 | -------------------------------------------------------------------------------- /test/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SignalK/nmea0183-signalk/HEAD/test/.DS_Store -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'npm' 4 | directory: '/' 5 | schedule: 6 | interval: 'daily' 7 | -------------------------------------------------------------------------------- /.github/workflows/require_pr_label.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Labels 2 | on: 3 | pull_request: 4 | types: [opened, labeled, unlabeled, synchronize] 5 | jobs: 6 | label: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: mheap/github-action-required-labels@v1 10 | with: 11 | mode: exactly 12 | count: 1 13 | labels: 'fix, feature, doc, chore, test, ignore, other, dependencies' 14 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish to npm 2 | on: 3 | release: 4 | types: [created] 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | - uses: actions/setup-node@v1 11 | with: 12 | node-version: '16.x' 13 | registry-url: 'https://registry.npmjs.org' 14 | - run: npm publish --access public 15 | env: 16 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 17 | -------------------------------------------------------------------------------- /custom-sentence-plugin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "signalk-nmea0183-custom-sentence-plugin", 3 | "version": "1.0.0", 4 | "description": "Example of a plugin for Signal K Server that adds a custom sentence to the NMEA0183 parser", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "signalk-node-server-plugin" 11 | ], 12 | "author": "teppo.kurki@iki.fi", 13 | "license": "Apache-2.0" 14 | } 15 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: CI test 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | strategy: 10 | matrix: 11 | node-version: [16.x, 18.x] 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - name: Use Node.js ${{ matrix.node-version }} 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: ${{ matrix.node-version }} 19 | - run: npm install 20 | - run: npm test 21 | env: 22 | CI: true 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2016/2017 Signal K and Fabian Tollenaar . 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /test/toFull.js: -------------------------------------------------------------------------------- 1 | const signalkSchema = require('@signalk/signalk-schema') 2 | const debug = require('debug')('signalk-parser-nmea0183:test') 3 | 4 | module.exports = (delta) => { 5 | if (!delta.context) { 6 | delta.context = 'vessels.' + signalkSchema.fakeMmsiId 7 | } 8 | delta.updates.forEach((update) => { 9 | if (!update.timestamp) { 10 | update.timestamp = new Date().toISOString() 11 | } 12 | if (!update.source.label) { 13 | update.source.label = 'DUMMY_LABEL' 14 | } 15 | }) 16 | const result = signalkSchema.deltaToFull(delta) 17 | if (debug.enabled) { 18 | debug(JSON.stringify(delta, null, 2)) 19 | debug(JSON.stringify(result, null, 2)) 20 | } 21 | return result 22 | } 23 | -------------------------------------------------------------------------------- /hooks/seatalk/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | '0x00': require('./0x00.js'), 3 | '0x9C': require('./0x9C.js'), 4 | '0x10': require('./0x10.js'), 5 | '0x11': require('./0x11.js'), 6 | '0x20': require('./0x20.js'), 7 | '0x21': require('./0x21.js'), 8 | '0x22': require('./0x22.js'), 9 | '0x25': require('./0x25.js'), 10 | '0x26': require('./0x26.js'), 11 | '0x27': require('./0x27.js'), 12 | '0x50': require('./0x50.js'), 13 | '0x51': require('./0x51.js'), 14 | '0x52': require('./0x52.js'), 15 | '0x53': require('./0x53.js'), 16 | '0x54': require('./0x54.js'), 17 | '0x56': require('./0x56.js'), 18 | '0x57': require('./0x57.js'), 19 | '0x84': require('./0x84.js'), 20 | '0x99': require('./0x99.js'), 21 | } 22 | -------------------------------------------------------------------------------- /bin/nmea0183-signalk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const Parser = require('../lib/index.js') 4 | const parser = new Parser({ 5 | validateChecksum: false 6 | }) 7 | 8 | process.stdin.resume() 9 | process.stdin.setEncoding('utf8') 10 | 11 | process.stdin.pipe(require('split')()).on('data', data => { 12 | if (typeof data !== 'string') { 13 | return 14 | } 15 | 16 | try { 17 | const delta = parser.parse(data.trim()) 18 | if (delta !== null) { 19 | console.log(JSON.stringify(delta)) 20 | } 21 | } 22 | catch(e) { 23 | console.error('Encountered an error:', e.message) 24 | } 25 | }) 26 | 27 | process.stdin.on('error', err => { 28 | console.error('Encountered an input error:', err.message) 29 | process.exit(1) 30 | }) 31 | -------------------------------------------------------------------------------- /hooks/VDO.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the 'License'); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an 'AS IS' BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | module.exports = require('./VDM') 20 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016/2017 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | module.exports = require('./lib') 20 | -------------------------------------------------------------------------------- /.github/workflows/release_on_tag.yml: -------------------------------------------------------------------------------- 1 | name: 'Release on tag' 2 | on: 3 | push: 4 | tags: 5 | - '*' 6 | 7 | jobs: 8 | release: 9 | permissions: 10 | contents: write 11 | if: startsWith(github.ref, 'refs/tags/') 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Build Changelog 15 | id: github_release 16 | uses: mikepenz/release-changelog-builder-action@v1 17 | env: 18 | GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }} 19 | 20 | - name: Create Release 21 | uses: actions/create-release@v1 22 | with: 23 | tag_name: ${{ github.ref }} 24 | release_name: ${{ github.ref }} 25 | body: ${{steps.github_release.outputs.changelog}} 26 | env: 27 | GITHUB_TOKEN: ${{ secrets.RELEASE_PAT }} 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional eslint cache 38 | .eslintcache 39 | 40 | # Optional REPL history 41 | .node_repl_history 42 | 43 | # Output of 'npm pack' 44 | *.tgz 45 | 46 | # Yarn Integrity file 47 | .yarn-integrity 48 | 49 | .DS_Store 50 | *.db 51 | 52 | .vscode 53 | -------------------------------------------------------------------------------- /custom-sentence-plugin/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * To test the plugin 3 | * - install the plugin (for example with npm link) 4 | * - activate the plugin 5 | * - add an UDP NMEA0183 connection to the server 6 | * - send data via udp 7 | * echo '$IIXXX,1,2,3,foobar,D*17' | nc -u -w 0 127.0.0.1 7777 8 | */ 9 | 10 | module.exports = function (app) { 11 | const plugin = {} 12 | plugin.id = 13 | plugin.name = 14 | plugin.description = 15 | 'signalk-nmea0183-custom-sentence-plugin' 16 | 17 | plugin.start = function () { 18 | app.emitPropertyValue('nmea0183sentenceParser', { 19 | sentence: 'XXX', 20 | parser: ({ id, sentence, parts, tags }, session) => { 21 | return { 22 | updates: [ 23 | { 24 | values: [ 25 | { path: 'navigation.speedOverGround', value: Number(parts[0]) }, 26 | ], 27 | }, 28 | ], 29 | } 30 | }, 31 | }) 32 | } 33 | 34 | plugin.stop = function () {} 35 | plugin.schema = {} 36 | return plugin 37 | } 38 | -------------------------------------------------------------------------------- /lib/transformSource.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * transformSource.js 5 | * 6 | * Checks if the "source" key in each update is an object, 7 | * creates the object if it's a string. 8 | * 9 | * @param "data": signalk delta object 10 | */ 11 | module.exports = function transformSource(data, sentence, talker) { 12 | if (typeof data !== 'object' || data === null) { 13 | return data 14 | } 15 | 16 | if (!Array.isArray(data.updates)) { 17 | return data 18 | } 19 | 20 | data.updates = data.updates.map((update) => { 21 | if (typeof update.source === 'object' && update.source !== null) { 22 | return update 23 | } 24 | 25 | const _source = update.source || '' 26 | const tagSentence = _source.split(':')[1] 27 | let tagTalker = _source.split(':')[0] 28 | 29 | if (talker === 'nmea0183') { 30 | talker = 'SK' 31 | } 32 | 33 | update.source = { 34 | sentence: tagSentence || sentence, 35 | talker: tagTalker || talker, 36 | type: 'NMEA0183', 37 | } 38 | 39 | return update 40 | }) 41 | 42 | return data 43 | } 44 | -------------------------------------------------------------------------------- /lib/CompatParser.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Transform = require('stream').Transform 4 | const ParentParser = require('../') 5 | 6 | console.log('ParentParser', ParentParser) 7 | 8 | const CompatParser = function CompatParser(opts) { 9 | if (!(this instanceof CompatParser)) { 10 | return new CompatParser(opts) 11 | } 12 | 13 | const options = Object.assign({}, opts) 14 | 15 | if (typeof options.stream !== 'object' || options.stream === null) { 16 | options.stream = {} 17 | } 18 | 19 | options.stream.objectMode = true 20 | Transform.call(this, options.stream) 21 | 22 | this.parser = new ParentParser(opts) 23 | this.stream = this.parser.stream() 24 | 25 | this.stream.on('data', (delta) => { 26 | this.emit('delta', delta) 27 | this.push(delta) 28 | }) 29 | 30 | this.stream.on('nmea0183', (sentence) => { 31 | this.emit('nmea0183', sentence) 32 | }) 33 | } 34 | 35 | require('util').inherits(CompatParser, Transform) 36 | module.exports = CompatParser 37 | 38 | CompatParser.prototype._transform = function (chunk, encoding, done) { 39 | this.stream.write(chunk) 40 | done() 41 | } 42 | -------------------------------------------------------------------------------- /test/invalid_checksum.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | chai.use(require('chai-things')) 23 | 24 | const nmeaLine = '$GPROT,35.6,A*FF' 25 | 26 | describe('Invalid checksum', () => { 27 | it('throws exception on invalid checksum', () => { 28 | should.Throw(() => { 29 | new Parser().parse(nmeaLine) 30 | }, /is invalid/) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /test/proprietary.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | chai.use(require('chai-things')) 23 | 24 | const nmeaLine = '$PMGNST,02.12,3,T,534,05.0,+03327,00*40' 25 | 26 | describe('Proprietary sentences', () => { 27 | it("Don't break the parser", () => { 28 | const delta = new Parser().parse(nmeaLine) 29 | should.equal(delta, null) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /test/MWV.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Parser = require('../lib') 4 | const chai = require('chai') 5 | const should = chai.Should() 6 | 7 | chai.use(require('chai-things')) 8 | 9 | describe('MWV', () => { 10 | it('True wind converts ok', () => { 11 | const delta = new Parser().parse('$IIMWV,074,T,05.85,N,A*2E') 12 | 13 | delta.updates[0].values.should.contain.an.item.with.property( 14 | 'path', 15 | 'environment.wind.angleTrueWater' 16 | ) 17 | delta.updates[0].values.should.contain.an.item.with.property( 18 | 'value', 19 | 1.2915436467707015 20 | ) 21 | }) 22 | 23 | it('Apparent wind converts ok', () => { 24 | const delta = new Parser().parse('$IIMWV,336,R,13.41,N,A*22') 25 | 26 | delta.updates[0].values.should.contain.an.item.with.property( 27 | 'path', 28 | 'environment.wind.angleApparent' 29 | ) 30 | delta.updates[0].values.should.contain.an.item.with.property( 31 | 'value', 32 | -0.41887902057428156 33 | ) 34 | }) 35 | 36 | it("Doesn't choke on empty sentences", () => { 37 | const delta = new Parser().parse('$IIMWV,,,,*4C') 38 | should.equal(delta, null) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /test/VDO.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Parser = require('../lib') 4 | const chai = require('chai') 5 | const should = chai.Should() 6 | 7 | chai.use(require('chai-things')) 8 | 9 | const sentences = [ 10 | '!AIVDM,2,1,0,A,53brRt4000010SG700iE@LE8@Tp4000000000153P615t0Ht0SCkjH4jC1C,0*25\n', 11 | '!AIVDM,2,2,0,A,`0000000001,2*75\n', 12 | ] 13 | 14 | // FIXME: This is testing VDM - not VDO 15 | describe('VDO', function () { 16 | it('Multiline converts ok', () => { 17 | const parser = new Parser() 18 | let delta = parser.parse(sentences[0]) 19 | should.equal(delta, null) 20 | 21 | delta = parser.parse(sentences[1]) 22 | 23 | delta.context.should.equal('vessels.urn:mrn:imo:mmsi:246326000') 24 | delta.updates[0].values[0].value.mmsi.should.equal('246326000') 25 | delta.updates[0].values[1].value.name.should.equal('UTGERDINA') 26 | }) 27 | 28 | it('Single line converts ok', () => { 29 | const delta = new Parser().parse( 30 | '!AIVDM,1,1,,A,13aEOK?P00PD2wVMdLDRhgvL289?,0*26\n' 31 | ) 32 | delta.context.should.equal('vessels.urn:mrn:imo:mmsi:244670316') 33 | }) 34 | 35 | it("Doesn't choke on empty sentences", () => { 36 | const delta = new Parser().parse('!AIVDM,,,,,,*57') 37 | should.equal(delta, null) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/RSA.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | 22 | const nmeaLine = '$IIRSA,10.5,A,,V*4D' 23 | 24 | chai.Should() 25 | chai.use(require('chai-things')) 26 | 27 | describe('RSA', () => { 28 | it('Converts OK using individual parser', () => { 29 | const delta = new Parser().parse(nmeaLine) 30 | delta.updates[0].values.should.contain.an.item.with.property( 31 | 'path', 32 | 'steering.rudderAngle' 33 | ) 34 | delta.updates[0].values[0].value.should.be.closeTo( 35 | (10.5 / 180) * Math.PI, 36 | 0.1 37 | ) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/ROT.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | 22 | const nmeaLine = '$GPROT,35.6,A*01' 23 | 24 | chai.Should() 25 | chai.use(require('chai-things')) 26 | 27 | describe('ROT', () => { 28 | it('Converts OK using individual parser', () => { 29 | const delta = new Parser().parse(nmeaLine) 30 | delta.updates[0].values.should.contain.an.item.with.property( 31 | 'path', 32 | 'navigation.rateOfTurn' 33 | ) 34 | delta.updates[0].values[0].value.should.be.closeTo( 35 | ((35.6 / 180) * Math.PI) / 60, 36 | 0.0005 37 | ) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/VPW.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 Parser = require('../lib') 18 | const chai = require('chai') 19 | const nmeaLine = '$IIVPW,4.5,N,6.7,M*52' 20 | const nmeaLineKnots = '$IIVPW,4.5,N,,*30' // FIXME: add a test for knots? 21 | 22 | chai.Should() 23 | chai.use(require('chai-things')) 24 | 25 | describe('VPW', () => { 26 | it('Converts OK using individual parser', () => { 27 | const delta = new Parser().parse(nmeaLine) 28 | delta.updates[0].values.should.contain.an.item.with.property( 29 | 'path', 30 | 'performance.velocityMadeGood' 31 | ) 32 | delta.updates[0].values.should.contain.an.item.with.property('value', 6.7) 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /test/DSC.js: -------------------------------------------------------------------------------- 1 | const Parser = require('../lib') 2 | const chai = require('chai') 3 | const should = chai.Should() 4 | chai.use(require('chai-things')) 5 | 6 | const nmeaLinePos = '$CDDSC,20,3381581370,00,21,26,1423108312,1902,,,B,E*7B' 7 | const nmeaLineDistress = 8 | '$CDDSC,12,3380400790,12,06,00,1423108312,2019,,,S,E*6A' 9 | const emptyNmeaLine = '$CDDSC,,,,,,,,,,,*7F' 10 | 11 | describe('DSC', () => { 12 | it('Position converts ok', () => { 13 | const delta = new Parser().parse(nmeaLinePos) 14 | 15 | delta.updates[0].values.should.contain.an.item.with.property( 16 | 'path', 17 | 'navigation.position' 18 | ) 19 | delta.context.should.equal('vessels.urn:mrn:imo:mmsi:338158137') 20 | }) 21 | 22 | it('Distress converts ok', () => { 23 | const delta = new Parser().parse(nmeaLineDistress) 24 | 25 | delta.updates[0].values.should.contain.an.item.with.property( 26 | 'path', 27 | 'navigation.position' 28 | ) 29 | delta.updates[0].values.should.contain.an.item.with.property( 30 | 'path', 31 | 'notifications.adrift' 32 | ) 33 | delta.context.should.equal('vessels.urn:mrn:imo:mmsi:338040079') 34 | }) 35 | 36 | it("Doesn't choke on empty sentences", () => { 37 | const delta = new Parser().parse(emptyNmeaLine) 38 | should.equal(delta, null) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /hooks/ALK.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | /* 20 | 0 1 2 3 21 | | | | | 22 | $STALK,xx,yy,nn*CS 23 | where: 24 | STALK Raymarine Seatalk1 datagram sentence 25 | 0 00-9C Datagram type 26 | 1 hex First datagram content 27 | 2 hex Last datagram content 28 | 3 hex Checksum 29 | */ 30 | 31 | const seatalkHooks = require('./seatalk') 32 | 33 | module.exports = function (input, session) { 34 | const { id, sentence, parts, tags } = input 35 | const key = '0x' + parts[0].toUpperCase() 36 | if (typeof seatalkHooks[key] === 'function') { 37 | return seatalkHooks[key](input, session) 38 | } else { 39 | return null 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/HDT.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.use(require('chai-things')) 24 | 25 | describe('HDT', () => { 26 | it('Converts OK using individual parser', () => { 27 | const delta = new Parser().parse('$GPHDT,123.456,T*32') 28 | delta.updates[0].values.should.contain.an.item.with.property( 29 | 'path', 30 | 'navigation.headingTrue' 31 | ) 32 | delta.updates[0].values[0].value.should.be.closeTo(2.155, 0.005) 33 | }) 34 | 35 | it("Doesn't choke on empty sentences", () => { 36 | const delta = new Parser().parse('$SKHDT,,*40') 37 | should.equal(delta, null) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /test/HDM.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.use(require('chai-things')) 24 | 25 | describe('HDM', () => { 26 | it('Converts OK using individual parser', () => { 27 | const delta = new Parser().parse('$04HDM,186.5,M*2C') 28 | 29 | delta.updates[0].values.should.contain.an.item.with.property( 30 | 'path', 31 | 'navigation.headingMagnetic' 32 | ) 33 | delta.updates[0].values[0].value.should.be.closeTo(3.26, 0.005) 34 | }) 35 | 36 | it("Doesn't choke on empty sentences", () => { 37 | const delta = new Parser().parse('$SKHDM,,*59') 38 | should.equal(delta, null) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /hooks/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ALK: require('./ALK.js'), 3 | APB: require('./APB.js'), 4 | DBT: require('./DBT.js'), 5 | DBK: require('./DBK.js'), 6 | DBS: require('./DBS.js'), 7 | DPT: require('./DPT.js'), 8 | DSC: require('./DSC.js'), 9 | GNS: require('./GNS.js'), 10 | GGA: require('./GGA.js'), 11 | GLL: require('./GLL.js'), 12 | GSV: require('./GSV.js'), 13 | HDG: require('./HDG.js'), 14 | HDM: require('./HDM.js'), 15 | HDT: require('./HDT.js'), 16 | PBVE: require('./proprietary/PBVE.js'), 17 | PNKEP: require('./proprietary/PNKEP.js'), 18 | PSMDST: require('./proprietary/PSMDST.js'), 19 | MDA: require('./MDA.js'), 20 | MTA: require('./MTA.js'), 21 | MTW: require('./MTW.js'), 22 | MWD: require('./MWD.js'), 23 | MWV: require('./MWV.js'), 24 | RMB: require('./RMB.js'), 25 | RMC: require('./RMC.js'), 26 | ROT: require('./ROT.js'), 27 | RPM: require('./RPM.js'), 28 | RSA: require('./RSA.js'), 29 | VDM: require('./VDM.js'), 30 | VDO: require('./VDO.js'), 31 | VDR: require('./VDR.js'), 32 | VHW: require('./VHW.js'), 33 | VLW: require('./VLW.js'), 34 | VPW: require('./VPW.js'), 35 | VTG: require('./VTG.js'), 36 | VWR: require('./VWR.js'), 37 | VWT: require('./VWT.js'), 38 | ZDA: require('./ZDA.js'), 39 | XTE: require('./XTE.js'), 40 | BOD: require('./BOD.js'), 41 | BWC: require('./BWC.js'), 42 | BWR: require('./BWR.js'), 43 | HSC: require('./HSC.js'), 44 | } 45 | -------------------------------------------------------------------------------- /hooks/seatalk/0x52.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 52 01 XX XX Speed over Ground: XXXX/10 Knots 23 | */ 24 | 25 | module.exports = function (input) { 26 | const { id, sentence, parts, tags } = input 27 | 28 | var XXXX = parseInt(parts[2], 16) + 256 * parseInt(parts[3], 16) 29 | var speedOverGround = XXXX / 10.0 30 | var pathValues = [] 31 | 32 | pathValues.push({ 33 | path: 'navigation.speedOverGround', 34 | value: utils.transform(utils.float(speedOverGround), 'knots', 'ms'), 35 | }) 36 | 37 | return { 38 | updates: [ 39 | { 40 | source: tags.source, 41 | timestamp: tags.timestamp, 42 | values: pathValues, 43 | }, 44 | ], 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /test/RPM.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 Parser = require('../lib') 18 | const chai = require('chai') 19 | const should = chai.Should() 20 | chai.use(require('chai-things')) 21 | 22 | const nmeaLine = '$IIRPM,E,1,2418.2,10.5,A*5F' 23 | 24 | describe('RPM', () => { 25 | it('Converts OK using individual parser', () => { 26 | const delta = new Parser().parse(nmeaLine) 27 | 28 | delta.updates[0].values.should.contain.an.item.with.property( 29 | 'path', 30 | 'propulsion.engine_1.revolutions' 31 | ) 32 | delta.updates[0].values[0].value.should.be.closeTo(2418.2 / 60, 0.0005) 33 | }) 34 | 35 | /* FIXME! 36 | it('Doesn\'t choke on empty sentences', () => { 37 | const delta = new Parser().parse('$IIRPM,,,,,*63') 38 | should.equal(delta, null) 39 | }) 40 | */ 41 | }) 42 | -------------------------------------------------------------------------------- /test/tagblock.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | chai.Should() 22 | chai.use(require('chai-things')) 23 | 24 | const nmeaLine = 25 | '\\s:compass,c:1438489697*13\\$IIDBT,035.53,f,010.83,M,005.85,F*23' 26 | 27 | describe('NMEA0183v4 tag block', () => { 28 | it('Converts OK using individual parser', () => { 29 | const delta = new Parser().parse(nmeaLine) 30 | 31 | delta.updates[0].source.should.be.an('object') 32 | delta.updates[0].source.talker.should.equal('compass') 33 | delta.updates[0].timestamp.should.equal('2015-08-02T04:28:17.000Z') 34 | delta.updates[0].values.should.contain.an.item.with.property( 35 | 'path', 36 | 'environment.depth.belowTransducer' 37 | ) 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /hooks/seatalk/0x22.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 22 02 XX XX 00 Total Mileage: XXXX/10 nautical miles 23 | */ 24 | 25 | module.exports = function (input) { 26 | const { id, sentence, parts, tags } = input 27 | const nauticalMilesToMeters = 1852 28 | var totalMileage = 29 | ((parseInt(parts[2], 16) + 256 * parseInt(parts[3], 16)) * 30 | nauticalMilesToMeters) / 31 | 10.0 32 | var pathValues = [] 33 | 34 | pathValues.push({ 35 | path: 'navigation.log', 36 | value: utils.float(totalMileage), 37 | }) 38 | 39 | return { 40 | updates: [ 41 | { 42 | source: tags.source, 43 | timestamp: tags.timestamp, 44 | values: pathValues, 45 | }, 46 | ], 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/XTE.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.Should() 24 | chai.use(require('chai-things')) 25 | 26 | describe('XTE', () => { 27 | it('Converts OK using individual parser', () => { 28 | const delta = new Parser().parse('$GPXTE,A,A,0.67,L,N*6F') 29 | // console.log(JSON.stringify(delta, null, 2)) 30 | 31 | delta.should.be.an('object') 32 | delta.updates[0].values[0].path.should.equal( 33 | 'navigation.courseRhumbline.crossTrackError' 34 | ) 35 | delta.updates[0].values[0].value.should.be.closeTo(1240.84, 0.001) 36 | }) 37 | 38 | it("Doesn't choke on an empty sentence", () => { 39 | const delta = new Parser().parse('$GPXTE,,,,,*72') 40 | should.equal(delta, null) 41 | }) 42 | }) 43 | -------------------------------------------------------------------------------- /hooks/seatalk/0x21.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 21 02 XX XX 0X Trip Mileage: XXXXX/100 nautical miles 23 | */ 24 | 25 | module.exports = function (input) { 26 | const { id, sentence, parts, tags } = input 27 | 28 | var XXXX = parseInt(parts[2], 16) + 256 * parseInt(parts[3], 16) 29 | var X = parseInt(parts[4], 16) & 0x0f 30 | var trip = (XXXX + X * 65536) / 100.0 31 | var pathValues = [] 32 | 33 | pathValues.push({ 34 | path: 'navigation.trip', 35 | value: utils.transform(utils.float(trip), 'nm', 'km') * 1000, 36 | }) 37 | 38 | return { 39 | updates: [ 40 | { 41 | source: tags.source, 42 | timestamp: tags.timestamp, 43 | values: pathValues, 44 | }, 45 | ], 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /hooks/seatalk/0x20.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 20 01 XX XX Speed through water: XXXX/10 Knots 23 | Corresponding NMEA sentence: VHW 24 | */ 25 | 26 | module.exports = function (input) { 27 | const { id, sentence, parts, tags } = input 28 | 29 | var speedThroughWater = 30 | ((parseInt(parts[2], 16)) + parseInt(parts[3], 16)) / 10.0 31 | var pathValues = [] 32 | 33 | pathValues.push({ 34 | path: 'navigation.speedThroughWater', 35 | value: utils.transform(utils.float(speedThroughWater), 'knots', 'ms'), 36 | }) 37 | 38 | return { 39 | updates: [ 40 | { 41 | source: tags.source, 42 | timestamp: tags.timestamp, 43 | values: pathValues, 44 | }, 45 | ], 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /hooks/seatalk/0x27.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 027 01 XX XX Water temperature: (XXXX-100)/10 deg Celsius 23 | Corresponding NMEA sentence: MTW 24 | */ 25 | 26 | module.exports = function (input) { 27 | const { id, sentence, parts, tags } = input 28 | 29 | var XXXX = parseInt(parts[2], 16) + 256 * parseInt(parts[3], 16) 30 | var waterTemperature = (XXXX - 100) / 10.0 31 | 32 | var pathValues = [] 33 | 34 | pathValues.push({ 35 | path: 'environment.water.temperature', 36 | value: utils.float(waterTemperature) + 273.15, 37 | }) 38 | 39 | return { 40 | updates: [ 41 | { 42 | source: tags.source, 43 | timestamp: tags.timestamp, 44 | values: pathValues, 45 | }, 46 | ], 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /test/VDR.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and contributors. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | 22 | chai.Should() 23 | chai.use(require('chai-things')) 24 | chai.use(require('@signalk/signalk-schema').chaiModule) 25 | const toFull = require('./toFull') 26 | 27 | const nmeaLine = '$IIVDR,10.1,T,12.3,M,1.2,N*3A' 28 | 29 | describe('VDR', () => { 30 | it('Converts OK using individual parser', () => { 31 | const delta = new Parser().parse(nmeaLine) 32 | 33 | delta.updates[0].values.should.contain.an.item.with.property( 34 | 'path', 35 | 'environment.current' 36 | ) 37 | delta.updates[0].values[0].value.should.deep.equal({ 38 | setTrue: 0.1762782544916768, 39 | setMagnetic: 0.21467549804431932, 40 | drift: 0.6173334897244841, 41 | }) 42 | toFull(delta).should.be.validSignalK 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /test/MTW.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | 22 | chai.Should() 23 | chai.use(require('chai-things')) 24 | 25 | describe('MTW', () => { 26 | it('Converts OK using individual parser', () => { 27 | const delta = new Parser().parse('$YXMTW,15.2,C*14') 28 | 29 | delta.updates[0].values.should.contain.an.item.with.property( 30 | 'path', 31 | 'environment.water.temperature' 32 | ) 33 | delta.updates[0].values[0].value.should.be.closeTo(288.35, 0.005) 34 | }) 35 | 36 | it('Converts empty value to null', () => { 37 | const delta = new Parser().parse('$RAMTW,,C*1E') 38 | delta.updates[0].values.length.should.equal(1) 39 | delta.updates[0].values[0].path.should.equal( 40 | 'environment.water.temperature' 41 | ) 42 | chai.expect(delta.updates[0].values[0].value).to.be.null 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /test/DBK.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.use(require('chai-things')) 24 | 25 | describe('DBK', () => { 26 | it('Converts OK using individual parser', () => { 27 | const delta = new Parser().parse('$IIDBK,035.53,f,010.83,M,005.85,F*3C') 28 | delta.updates[0].values.should.contain.an.item.with.property( 29 | 'path', 30 | 'environment.depth.belowKeel' 31 | ) 32 | delta.updates[0].values.should.contain.an.item.with.property('value', 10.83) 33 | }) 34 | 35 | it('Converts empty value to null', () => { 36 | const delta = new Parser().parse('$IIDBK,,,,,,*4D') 37 | delta.updates[0].values.length.should.equal(1) 38 | delta.updates[0].values[0].path.should.equal('environment.depth.belowKeel') 39 | should.equal(delta.updates[0].values[0].value, null) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /hooks/seatalk/0x57.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 57 S0 DD 23 | Sat Info: S number of sats, DD horiz. dillution of position, if S=1 -> DD=0x94 24 | */ 25 | 26 | module.exports = function (input) { 27 | const { id, sentence, parts, tags } = input 28 | 29 | var S = (parseInt(parts[1], 16) & 0xf0) >> 4 30 | var DD = parseInt(parts[2], 16) 31 | if (S == 1) { 32 | DD = 0x94 33 | } 34 | 35 | var pathValues = [] 36 | 37 | pathValues.push({ 38 | path: 'navigation.gnss.satellites', 39 | value: utils.float(S), 40 | }) 41 | 42 | pathValues.push({ 43 | path: 'navigation.gnss.horizontalDilution', 44 | value: utils.float(DD), 45 | }) 46 | 47 | return { 48 | updates: [ 49 | { 50 | source: tags.source, 51 | timestamp: tags.timestamp, 52 | values: pathValues, 53 | }, 54 | ], 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /test/DBS.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.use(require('chai-things')) 24 | 25 | describe('DBS', () => { 26 | it('Converts OK using individual parser', () => { 27 | const delta = new Parser().parse('$IIDBS,035.53,f,010.83,M,005.85,F*24') 28 | delta.updates[0].values.should.contain.an.item.with.property( 29 | 'path', 30 | 'environment.depth.belowSurface' 31 | ) 32 | delta.updates[0].values.should.contain.an.item.with.property('value', 10.83) 33 | }) 34 | 35 | it('Converts empty value to null', () => { 36 | const delta = new Parser().parse('$IIDBS,,,,,,*55') 37 | delta.updates[0].values.length.should.equal(1) 38 | delta.updates[0].values[0].path.should.equal( 39 | 'environment.depth.belowSurface' 40 | ) 41 | should.equal(delta.updates[0].values[0].value, null) 42 | }) 43 | }) 44 | -------------------------------------------------------------------------------- /hooks/ROT.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | # ROT - Rate Of Turn 23 | # 0 1 2 24 | # | | | 25 | # $--ROT,x.x,A*hh 26 | # Field Number: 27 | # 28 | # 0 - Rate Of Turn, degrees per minute, "-" means bow turns to port 29 | # 1 - Status, A means data is valid 30 | # 2 - Checksum 31 | # 32 | # 33 | */ 34 | 35 | module.exports = function (input) { 36 | const { id, sentence, parts, tags } = input 37 | 38 | if (String(parts[1]).toUpperCase() !== 'A') { 39 | return null 40 | } 41 | 42 | const delta = { 43 | updates: [ 44 | { 45 | source: tags.source, 46 | timestamp: tags.timestamp, 47 | values: [ 48 | { 49 | path: 'navigation.rateOfTurn', 50 | value: utils.transform(utils.float(parts[0]), 'deg', 'rad') / 60, 51 | }, 52 | ], 53 | }, 54 | ], 55 | } 56 | 57 | return delta 58 | } 59 | -------------------------------------------------------------------------------- /hooks/MTW.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | * MTW - Mean Temperature of Water 23 | * 24 | * 0 1 2 25 | * | | | 26 | * $--MTW,x.x,C*hh 27 | * 28 | * Field Number: 29 | * 0. Degrees 30 | * 1. Unit of Measurement, Celsius 31 | * 2. Checksum 32 | * 33 | */ 34 | 35 | module.exports = function (input) { 36 | const { id, sentence, parts, tags } = input 37 | 38 | const delta = { 39 | updates: [ 40 | { 41 | source: tags.source, 42 | timestamp: tags.timestamp, 43 | values: [ 44 | { 45 | path: 'environment.water.temperature', 46 | value: 47 | parts.length > 0 && parts[0].trim().length > 0 48 | ? utils.transform(utils.float(parts[0]), 'c', 'k') 49 | : null, 50 | }, 51 | ], 52 | }, 53 | ], 54 | } 55 | 56 | return delta 57 | } 58 | -------------------------------------------------------------------------------- /test/info.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const pkg = require('../package.json') 22 | chai.Should() 23 | chai.use(require('chai-things')) 24 | 25 | describe('Package info', () => { 26 | it(`Retrieves name "${pkg.name}" successfully`, (done) => { 27 | const data = new Parser().name 28 | data.should.equal(pkg.name) 29 | done() 30 | }) 31 | 32 | it(`Retrieves license "${pkg.license}" successfully`, (done) => { 33 | const data = new Parser().license 34 | data.should.equal(pkg.license) 35 | done() 36 | }) 37 | 38 | it(`Retrieves version "${pkg.version}" successfully`, (done) => { 39 | const data = new Parser().version 40 | data.should.equal(pkg.version) 41 | done() 42 | }) 43 | 44 | it(`Retrieves author "${pkg.author}" successfully`, (done) => { 45 | const data = new Parser().author 46 | data.should.equal(pkg.author) 47 | done() 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /hooks/proprietary/PSMDST.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | /* 20 | 0 1 2 3 4 5 21 | | | | | | | 22 | $PSMDST,Z,xx,yy,nn*CS 23 | where: 24 | 0 PSMDST Raymarine Seatalk1 datagram sentence 25 | 1 C/R R for Received messages, C for sent messages *Note: This field only exists in later firmware versions of the ShipModul Miniplex 26 | 2 00-9C Datagram type 27 | 3 hex First datagram content 28 | 4 hex Last datagram content 29 | 5 hex Checksum 30 | */ 31 | 32 | const seatalkHooks = require('../seatalk') 33 | 34 | module.exports = function (input, session) { 35 | const { id, sentence, parts, tags } = input 36 | if (parts[0].toUpperCase() === 'R') { 37 | input.parts = parts.slice(1, input.parts.length) 38 | } 39 | const key = '0x' + input.parts[0].toUpperCase() 40 | if (typeof seatalkHooks[key] === 'function') { 41 | return seatalkHooks[key](input, session) 42 | } else { 43 | return null 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /test/VLW.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const utils = require('@signalk/nmea0183-utilities') 22 | 23 | chai.Should() 24 | 25 | chai.use(require('chai-things')) 26 | 27 | describe('VLW', () => { 28 | it('total cumulative distance', () => { 29 | const delta = new Parser().parse('$IIVLW,10.1,N,3.2,N*7C') 30 | 31 | delta.updates[0].values.should.contain.an.item.with.property( 32 | 'path', 33 | 'navigation.log' 34 | ) 35 | delta.updates[0].values[0].value.should.be.closeTo( 36 | 10.1 * 1.852 * 1000, 37 | 0.001 38 | ) 39 | }) 40 | 41 | it('trip distance', () => { 42 | const delta = new Parser().parse('$IIVLW,115.2,N,12.3,N*7A') 43 | 44 | delta.updates[0].values.should.contain.an.item.with.property( 45 | 'path', 46 | 'navigation.trip.log' 47 | ) 48 | delta.updates[0].values[1].value.should.be.closeTo( 49 | 12.3 * 1.852 * 1000, 50 | 0.001 51 | ) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /hooks/RPM.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | # 0 1 2 3 4 5 23 | # | | | | | | 24 | # $--RPM,a,x,x.x,x.x,A*hh Field Number: 25 | # 0) Source, S = Shaft, E = Engine 1) Engine or shaft number 2) Speed, 26 | # Revolutions per minute 3) Propeller pitch, % of maximum, "-" means 27 | # astern 4) Status, A means data is valid 5) Checksum 28 | */ 29 | 30 | module.exports = function (input) { 31 | const { id, sentence, parts, tags } = input 32 | 33 | const delta = { 34 | updates: [ 35 | { 36 | source: tags.source, 37 | timestamp: tags.timestamp, 38 | values: [ 39 | { 40 | path: `propulsion.${ 41 | parts[0].toUpperCase() === 'S' ? 'shaft' : 'engine' 42 | }_${parts[1]}.revolutions`, 43 | value: utils.float(parts[2]) / 60, 44 | }, 45 | ], 46 | }, 47 | ], 48 | } 49 | 50 | return delta 51 | } 52 | -------------------------------------------------------------------------------- /hooks/RSA.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /** 22 | Rudder Sensor Angle: 23 | $--RSA,x.x,A,x.x,A*hh 24 | Field Number: 25 | 1 Starboard (or single) rudder sensor, "-" means Turn To Port 26 | 2 Status, A means data is valid 27 | 3 Port rudder sensor 28 | 4 Status, A means data is valid 29 | 5 Checksum 30 | */ 31 | 32 | // Note: Only single rudder setup is currently supported 33 | 34 | module.exports = function (input) { 35 | const { id, sentence, parts, tags } = input 36 | 37 | if (String(parts[1]).toUpperCase() !== 'A') { 38 | return null 39 | } 40 | 41 | const delta = { 42 | updates: [ 43 | { 44 | source: tags.source, 45 | timestamp: tags.timestamp, 46 | values: [ 47 | { 48 | path: 'steering.rudderAngle', 49 | value: utils.transform(utils.float(parts[0]), 'deg', 'rad'), 50 | }, 51 | ], 52 | }, 53 | ], 54 | } 55 | 56 | return delta 57 | } 58 | -------------------------------------------------------------------------------- /hooks/seatalk/0x11.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 11 01 XX 0Y Apparent Wind Speed: (XX & 0x7F) + Y/10 Knots 23 | Units flag: XX&0x80=0 => Display value in Knots 24 | XX&0x80=0x80 => Display value in Meter/Second 25 | Corresponding NMEA sentence: MWV 26 | */ 27 | 28 | module.exports = function (input) { 29 | const { id, sentence, parts, tags } = input 30 | 31 | var XX = parseInt(parts[2], 16) 32 | var Y = parseInt(parts[3], 16) 33 | var apparentWindSpeed = (XX & 0x7f) + Y / 10.0 34 | var pathValues = [] 35 | 36 | pathValues.push({ 37 | path: 'environment.wind.speedApparent', 38 | value: utils.transform(utils.float(apparentWindSpeed), 'knots', 'ms'), 39 | }) 40 | 41 | return { 42 | updates: [ 43 | { 44 | source: tags.source, 45 | timestamp: tags.timestamp, 46 | values: pathValues, 47 | }, 48 | ], 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /hooks/MTA.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | * MTA - Mean Temperature of Air 23 | * 24 | * 0 1 2 25 | * | | | 26 | * $--MTA,x.x,C*hh 27 | * 28 | * Field Number: 29 | * 0. Degrees 30 | * 1. Unit of Measurement, Celsius 31 | * 2. Checksum 32 | * 33 | */ 34 | 35 | module.exports = function (input) { 36 | const { id, sentence, parts, tags } = input 37 | 38 | if (parts[1] != 'C') { 39 | return null 40 | } 41 | const delta = { 42 | updates: [ 43 | { 44 | source: tags.source, 45 | timestamp: tags.timestamp, 46 | values: [ 47 | { 48 | path: 'environment.outside.temperature', 49 | value: 50 | parts.length > 0 && parts[0].trim().length > 0 51 | ? utils.transform(utils.float(parts[0]), 'c', 'k') 52 | : null, 53 | }, 54 | ], 55 | }, 56 | ], 57 | } 58 | 59 | return delta 60 | } 61 | -------------------------------------------------------------------------------- /test/MTA.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.use(require('chai-things')) 24 | 25 | describe('MTA', () => { 26 | it('Converts OK using individual parser', () => { 27 | const delta = new Parser().parse('$IIMTA,26.,C*31') 28 | 29 | delta.updates[0].values[0].path.should.equal( 30 | 'environment.outside.temperature' 31 | ) 32 | delta.updates[0].values[0].value.should.be.closeTo(299.15, 0.005) 33 | }) 34 | it('Does not accept units other than Celsius', () => { 35 | var should = chai.should() 36 | const delta = new Parser().parse('$IIMTA,26.,F*34') 37 | 38 | should.equal(delta, null) 39 | }) 40 | 41 | it('Converts empty value to null', () => { 42 | const delta = new Parser().parse('$RAMTA,,C*08') 43 | delta.updates[0].values.length.should.equal(1) 44 | delta.updates[0].values[0].path.should.equal( 45 | 'environment.outside.temperature' 46 | ) 47 | chai.expect(delta.updates[0].values[0].value).to.be.null 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /hooks/VPW.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | VPW - Speed parallell to wind 23 | 0 1 2 3 4 24 | | | | | | 25 | $--VPW,x.x,N,x.x,M*hh 26 | Field Number: 27 | 0 - Speed, "-" means downwind , knots 28 | 1 - N = Knots 29 | 2 - Speed, "-" means downwind , m/s 30 | 3 - M = Meters per second 31 | 4 - Checksum 32 | */ 33 | 34 | module.exports = function (input) { 35 | var velocityValue 36 | const { id, sentence, parts, tags } = input 37 | if (parts[2]) { 38 | velocityValue = utils.float(parts[2]) 39 | } else if (parts[0]) { 40 | velocityValue = utils.transform(utils.float(parts[0]), 'knots', 'ms') 41 | } else { 42 | return null 43 | } 44 | 45 | return { 46 | updates: [ 47 | { 48 | source: tags.source, 49 | timestamp: tags.timestamp, 50 | values: [ 51 | { 52 | path: 'performance.velocityMadeGood', 53 | value: velocityValue, 54 | }, 55 | ], 56 | }, 57 | ], 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /hooks/DBK.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/DBT') 20 | const utils = require('@signalk/nmea0183-utilities') 21 | 22 | /* 23 | === DBK - Depth below keel === 24 | ------------------------------------------------------------------------------ 25 | *******0 1 2 3 4 5 6 26 | *******| | | | | | | 27 | $--DBK,x.x,f,x.x,M,x.x,F*hh 28 | ------------------------------------------------------------------------------ 29 | Field Number: 30 | 0. Depth, feet 31 | 1. f = feet 32 | 2. Depth, meters 33 | 3. M = meters 34 | 4. Depth, Fathoms 35 | 5. F = Fathoms 36 | 6. Checksum 37 | */ 38 | 39 | module.exports = function (input) { 40 | const { parts, tags } = input 41 | return { 42 | updates: [ 43 | { 44 | source: tags.source, 45 | timestamp: tags.timestamp, 46 | values: [ 47 | { 48 | path: 'environment.depth.belowKeel', 49 | value: parts[2].length > 0 ? utils.float(parts[2]) : null, 50 | }, 51 | ], 52 | }, 53 | ], 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /hooks/DBS.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/DBT') 20 | const utils = require('@signalk/nmea0183-utilities') 21 | 22 | /* 23 | === DBS - Depth below surface === 24 | ------------------------------------------------------------------------------ 25 | *******0 1 2 3 4 5 6 26 | *******| | | | | | | 27 | $--DBS,x.x,f,x.x,M,x.x,F*hh 28 | ------------------------------------------------------------------------------ 29 | Field Number: 30 | 0. Depth, feet 31 | 1. f = feet 32 | 2. Depth, meters 33 | 3. M = meters 34 | 4. Depth, Fathoms 35 | 5. F = Fathoms 36 | 6. Checksum 37 | */ 38 | 39 | module.exports = function (input) { 40 | const { parts, tags } = input 41 | return { 42 | updates: [ 43 | { 44 | source: tags.source, 45 | timestamp: tags.timestamp, 46 | values: [ 47 | { 48 | path: 'environment.depth.belowSurface', 49 | value: parts[2].length > 0 ? utils.float(parts[2]) : null, 50 | }, 51 | ], 52 | }, 53 | ], 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /hooks/VDR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | VDR - Set and Drift 23 | 0 1 2 3 4 5 6 24 | | | | | | | | 25 | $--VDR,x.x,T,x.x,M,x.x,N*hh 26 | Field Number: 27 | 0 - Degress True 28 | 1 - T = True 29 | 2 - Degrees Magnetic 30 | 3 - M = Magnetic 31 | 4 - Knots (speed of current) 32 | 5 - N = Knots 33 | 6 - Checksum 34 | */ 35 | 36 | module.exports = function (input) { 37 | const { id, sentence, parts, tags } = input 38 | 39 | const delta = { 40 | updates: [ 41 | { 42 | source: tags.source, 43 | timestamp: tags.timestamp, 44 | values: [ 45 | { 46 | path: 'environment.current', 47 | value: { 48 | setTrue: utils.transform(utils.float(parts[0]), 'deg', 'rad'), 49 | setMagnetic: utils.transform(utils.float(parts[2]), 'deg', 'rad'), 50 | drift: utils.transform(utils.float(parts[4]), 'knots', 'ms'), 51 | }, 52 | }, 53 | ], 54 | }, 55 | ], 56 | } 57 | 58 | return delta 59 | } 60 | -------------------------------------------------------------------------------- /hooks/seatalk/0x10.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 10 01 XX YY Apparent Wind Angle: XXYY/2 degrees right of bow 23 | Used for autopilots Vane Mode (WindTrim) 24 | Corresponding NMEA sentence: MWV 25 | */ 26 | 27 | module.exports = function (input) { 28 | const { id, sentence, parts, tags } = input 29 | 30 | var XX = parseInt(parts[2], 16) 31 | var YY = parseInt(parts[3], 16) 32 | // console.log("XX:"+XX) 33 | // console.log("YY:"+YY) 34 | var apparentWindAngle = (256 * XX + YY) / 2.0 35 | if (apparentWindAngle > 180) { 36 | apparentWindAngle = apparentWindAngle - 360 37 | } 38 | // console.log("apparentWindAngle:"+apparentWindAngle) 39 | var pathValues = [] 40 | 41 | pathValues.push({ 42 | path: 'environment.wind.angleApparent', 43 | value: utils.transform(utils.float(apparentWindAngle), 'deg', 'rad'), 44 | }) 45 | 46 | return { 47 | updates: [ 48 | { 49 | source: tags.source, 50 | timestamp: tags.timestamp, 51 | values: pathValues, 52 | }, 53 | ], 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/HSC.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.Should() 24 | chai.use(require('chai-things')) 25 | 26 | describe('HSC', () => { 27 | it('Converts OK using individual parser', () => { 28 | const delta = new Parser().parse('$FTHSC,40.12,T,39.11,M*5E') 29 | // console.log(JSON.stringify(delta, null, 2)) 30 | 31 | delta.should.be.an('object') 32 | delta.updates[0].values.should.contain.an.item.with.property( 33 | 'path', 34 | 'steering.autopilot.target.headingTrue' 35 | ) 36 | delta.updates[0].values.should.contain.an.item.with.property( 37 | 'value', 38 | 0.7002260960600073 39 | ) 40 | delta.updates[0].values.should.contain.an.item.with.property( 41 | 'path', 42 | 'steering.autopilot.target.headingMagnetic' 43 | ) 44 | delta.updates[0].values.should.contain.an.item.with.property( 45 | 'value', 46 | 0.6825982706108397 47 | ) 48 | }) 49 | 50 | it("Doesn't choke on an empty sentence", () => { 51 | const delta = new Parser().parse('$FTHSC,,,,*4A') 52 | should.equal(delta, null) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /hooks/HDT.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/DBT') 20 | const utils = require('@signalk/nmea0183-utilities') 21 | 22 | /* 23 | === HDT - Heading True === 24 | ------------------------------------------------------------------------------ 25 | *******0 1 2 26 | *******| | | 27 | $--HDT,x.x,T*hh 28 | ------------------------------------------------------------------------------ 29 | Field Number: 30 | 0. Heading True 31 | 1. T = True 32 | 2. Checksum 33 | */ 34 | 35 | module.exports = function (input) { 36 | const { id, sentence, parts, tags } = input 37 | 38 | if ( 39 | (typeof parts[0] !== 'string' && typeof parts[0] !== 'number') || 40 | (typeof parts[0] === 'string' && parts[0].trim() === '') 41 | ) { 42 | return null 43 | } 44 | 45 | const delta = { 46 | updates: [ 47 | { 48 | source: tags.source, 49 | timestamp: tags.timestamp, 50 | values: [ 51 | { 52 | path: 'navigation.headingTrue', 53 | value: utils.transform(utils.float(parts[0]), 'deg', 'rad'), 54 | }, 55 | ], 56 | }, 57 | ], 58 | } 59 | 60 | return delta 61 | } 62 | -------------------------------------------------------------------------------- /hooks/seatalk/0x53.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 53 U0 VW Magnetic Course in degrees: 23 | The two lower bits of U * 90 + 24 | the six lower bits of VW * 2 + 25 | the two higher bits of U / 2 = 26 | (U & 0x3) * 90 + (VW & 0x3F) * 2 + (U & 0xC) / 8 27 | The Magnetic Course may be offset by the Compass Variation (see datagram 99) to get the Course Over Ground (COG). 28 | */ 29 | 30 | module.exports = function (input) { 31 | const { id, sentence, parts, tags } = input 32 | 33 | var U = (parseInt(parts[1], 16) & 0xf0) >> 4 34 | var VW = parseInt(parts[2], 16) 35 | var magneticCourse = (U & 0x3) * 90.0 + (VW & 0x3f) * 2.0 + (U & 0xc) / 8.0 36 | 37 | var pathValues = [] 38 | 39 | pathValues.push({ 40 | path: 'navigation.courseOverGroundMagnetic', 41 | value: utils.transform(utils.float(magneticCourse), 'deg', 'rad'), 42 | }) 43 | 44 | return { 45 | updates: [ 46 | { 47 | source: tags.source, 48 | timestamp: tags.timestamp, 49 | values: pathValues, 50 | }, 51 | ], 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /hooks/HDM.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/HDM') 20 | const utils = require('@signalk/nmea0183-utilities') 21 | 22 | /* 23 | * === HDM - Heading Magnetic === 24 | * ------------------------------------------------------------------------------ 25 | * 0 1 2 26 | * | | | 27 | * $--HDM,x.x,T*hh 28 | * ------------------------------------------------------------------------------ 29 | * Field Number: 30 | * 0. Heading Magnetic 31 | * 1. T = True 32 | * 2. Checksum 33 | */ 34 | 35 | module.exports = function (input) { 36 | const { id, sentence, parts, tags } = input 37 | 38 | if ( 39 | (typeof parts[0] !== 'string' && typeof parts[0] !== 'number') || 40 | (typeof parts[0] === 'string' && parts[0].trim() === '') 41 | ) { 42 | return null 43 | } 44 | 45 | const delta = { 46 | updates: [ 47 | { 48 | source: tags.source, 49 | timestamp: tags.timestamp, 50 | values: [ 51 | { 52 | path: 'navigation.headingMagnetic', 53 | value: utils.transform(utils.float(parts[0]), 'deg', 'rad'), 54 | }, 55 | ], 56 | }, 57 | ], 58 | } 59 | 60 | return delta 61 | } 62 | -------------------------------------------------------------------------------- /test/ZDA.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 Parser = require('../lib') 18 | const chai = require('chai') 19 | const should = chai.Should() 20 | chai.use(require('chai-things')) 21 | 22 | const nmeaLine = '$GPZDA,160012.71,11,03,2004,-1,00*7D' 23 | const emptyNmeaLine = '$GPZDA,,,,,,*48' 24 | 25 | describe('ZDA', () => { 26 | it('Converts OK using individual parser', () => { 27 | const delta = new Parser().parse(nmeaLine) 28 | delta.updates[0].values.should.contain.an.item.with.property( 29 | 'path', 30 | 'navigation.datetime' 31 | ) 32 | delta.updates[0].values.should.contain.an.item.with.property( 33 | 'value', 34 | '2004-03-11T16:00:12.710Z' 35 | ) 36 | }) 37 | 38 | it("Doesn't choke on empty sentences", () => { 39 | const delta = new Parser().parse(emptyNmeaLine) 40 | should.equal(delta, null) 41 | }) 42 | 43 | it('Doesn\t choke when the number of seconds is 0', () => { 44 | const delta = new Parser().parse('$IIZDA,085400,22,07,2021,,*50') 45 | delta.updates[0].values.should.contain.an.item.with.property( 46 | 'path', 47 | 'navigation.datetime' 48 | ) 49 | delta.updates[0].values.should.contain.an.item.with.property( 50 | 'value', 51 | '2021-07-22T08:54:00.000Z' 52 | ) 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /hooks/VLW.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | VLW - Distance Traveled through Water 23 | ------------------------------------------------------------------------------ 24 | 0 1 2 3 4 25 | | | | | | 26 | $--VLW,x.x,N,x.x,N*hh 27 | ------------------------------------------------------------------------------ 28 | Field Number: 29 | 0. Total cumulative distance 30 | 1. N = Nautical Miles 31 | 2. Distance since Reset 32 | 3. N = Nautical Miles 33 | 4. Checksum 34 | 35 | */ 36 | 37 | module.exports = function (input) { 38 | var velocityValue 39 | const { id, sentence, parts, tags } = input 40 | var pathValues = [] 41 | 42 | if (parts[0] != '') { 43 | pathValues.push({ 44 | path: 'navigation.log', 45 | value: utils.transform(utils.float(parts[0]), 'nm', 'm'), 46 | }) 47 | } 48 | if (parts[2] != '') { 49 | pathValues.push({ 50 | path: 'navigation.trip.log', 51 | value: utils.transform(utils.float(parts[2]), 'nm', 'm'), 52 | }) 53 | } 54 | 55 | return { 56 | updates: [ 57 | { 58 | source: tags.source, 59 | timestamp: tags.timestamp, 60 | values: pathValues, 61 | }, 62 | ], 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@signalk/nmea0183-signalk", 3 | "version": "3.16.0", 4 | "description": "A node.js/javascript parser for NMEA0183 sentences. Sentences are parsed to Signal K format.", 5 | "main": "index.js", 6 | "scripts": { 7 | "prettier:check": "prettier --check .", 8 | "prettier": "prettier --write .", 9 | "format": "npm run prettier", 10 | "test": "mocha", 11 | "deploy": "npm test && npm run release && npm publish --access public --scope @signalk .", 12 | "create-release": "github-create-release --owner signalk --repository nmea0183-signalk", 13 | "release": "git tag -d v$npm_package_version && git tag v$npm_package_version && git push --tags && git push && npm run create-release" 14 | }, 15 | "bin": { 16 | "nmea0183-signalk": "./bin/nmea0183-signalk", 17 | "nmea2signalk": "./bin/nmea0183-signalk" 18 | }, 19 | "keywords": [ 20 | "nmea", 21 | "0183", 22 | "nmea0183", 23 | "signalk", 24 | "signal", 25 | "k", 26 | "parser" 27 | ], 28 | "author": "Fabian Tollenaar (http://signalk.org)", 29 | "license": "Apache-2.0", 30 | "dependencies": { 31 | "@signalk/nmea0183-utilities": "^0.9.0", 32 | "@signalk/signalk-schema": "^1.7.1", 33 | "ggencoder": "^1.0.8", 34 | "moment-timezone": "^0.5.21", 35 | "split": "^1.0.1" 36 | }, 37 | "devDependencies": { 38 | "@signalk/github-create-release": "^1.2.0", 39 | "chai": "^4.1.2", 40 | "chai-things": "^0.2.0", 41 | "debug": "^4.0.1", 42 | "mocha": "^9.1.2", 43 | "prettier": "^2.4.1" 44 | }, 45 | "repository": { 46 | "type": "git", 47 | "url": "https://github.com/signalk/signalk-parser-nmea0183" 48 | }, 49 | "bugs": { 50 | "url": "https://github.com/signalk/signalk-parser-nmea0183/issues" 51 | }, 52 | "homepage": "https://github.com/signalk/signalk-parser-nmea0183", 53 | "standard": { 54 | "globals": [ 55 | "describe", 56 | "it" 57 | ] 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /hooks/seatalk/0x56.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 56 M1 DD YY Date: YY year, M month, DD day in month 23 | */ 24 | 25 | module.exports = function (input, session) { 26 | const { id, sentence, parts, tags } = input 27 | 28 | var M = (parseInt(parts[1], 16) & 0xf0) >> 4 29 | var DD = parseInt(parts[2], 16) 30 | var YY = parseInt(parts[3], 16) 31 | 32 | var year = 2000 + YY 33 | var month = M 34 | var day = DD 35 | 36 | session['date'] = { year: year, month: month, day: day } 37 | 38 | var pathValues = [] 39 | 40 | if (session.hasOwnProperty('date') && session.hasOwnProperty('time')) { 41 | const d = new Date( 42 | Date.UTC( 43 | session['date'].year, 44 | session['date'].month - 1, 45 | session['date'].day, 46 | session['time'].hour, 47 | session['time'].minute, 48 | session['time'].second, 49 | session['time'].milliSecond 50 | ) 51 | ) 52 | const ts = d.toISOString() 53 | 54 | pathValues.push({ 55 | path: 'navigation.datetime', 56 | value: ts, 57 | }) 58 | } 59 | 60 | return { 61 | updates: [ 62 | { 63 | source: tags.source, 64 | timestamp: tags.timestamp, 65 | values: pathValues, 66 | }, 67 | ], 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /hooks/seatalk/0x99.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 99 00 XX Compass variation sent by ST40 compass instrument 23 | or ST1000, ST2000, ST4000+, E-80 every 10 seconds 24 | but only if the variation is set on the instrument 25 | Positive XX values: Variation West, Negative XX values: Variation East 26 | Examples (XX => variation): 00 => 0, 01 => -1 west, 02 => -2 west ... 27 | FF => +1 east, FE => +2 east ... 28 | Corresponding NMEA sentences: RMC, HDG 29 | */ 30 | 31 | module.exports = function (input) { 32 | const { id, sentence, parts, tags } = input 33 | 34 | var XX = parseInt(parts[2], 16) 35 | var value = 128 - (XX & 0x7f) 36 | var s = -1 37 | if (XX & (0x80 != 0)) { 38 | s = 1 39 | } 40 | var magneticVariation = s * value 41 | 42 | var pathValues = [] 43 | 44 | pathValues.push({ 45 | path: 'navigation.magneticVariation', 46 | value: utils.transform(utils.float(magneticVariation), 'deg', 'rad'), 47 | }) 48 | 49 | return { 50 | updates: [ 51 | { 52 | source: tags.source, 53 | timestamp: tags.timestamp, 54 | values: pathValues, 55 | }, 56 | ], 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /hooks/seatalk/0x25.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | https://github.com/mariokonrad/marnav/blob/01c55205736fcc8157891b84e3efe387a221ff3a/src/marnav/seatalk/message_25.cpp#L21-L26 23 | 25 Z4 XX YY UU VV AW Total & Trip Log 24 | total= (XX+YY*256+Z* 65536)/ 10 [max=104857.5] nautical miles 25 | trip = (UU+VV*256+W*65536)/100 [max=10485.75] nautical miles 26 | */ 27 | 28 | module.exports = function (input) { 29 | const { id, sentence, parts, tags } = input 30 | 31 | var Z = parseInt(parts[1], 16) >> 4 32 | var XX = parseInt(parts[2], 16) 33 | var YY = parseInt(parts[3], 16) 34 | var UU = parseInt(parts[4], 16) 35 | var VV = parseInt(parts[5], 16) 36 | var W = parseInt(parts[6], 16) & 0x0f 37 | 38 | var total = (XX + YY * 256 + Z * 65536) / 10.0 39 | var trip = (UU + VV * 256 + W * 65536) / 100.0 40 | 41 | var pathValues = [] 42 | 43 | pathValues.push({ 44 | path: 'navigation.trip', 45 | value: utils.transform(utils.float(trip), 'nm', 'm'), 46 | }) 47 | 48 | pathValues.push({ 49 | path: 'navigation.log', 50 | value: utils.transform(utils.float(total), 'nm', 'm'), 51 | }) 52 | 53 | return { 54 | updates: [ 55 | { 56 | source: tags.source, 57 | timestamp: tags.timestamp, 58 | values: pathValues, 59 | }, 60 | ], 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/getTagBlock.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/getTagBlock') 20 | const moment = require('moment-timezone') 21 | const tags = ['s:', 'c:'] 22 | 23 | module.exports = function getTagBlock(sentence) { 24 | let split = [] 25 | let block = [] 26 | let tags = {} 27 | 28 | // There could be a tag block... 29 | if (sentence.charAt(0) === '\\') { 30 | split = sentence.split('\\') 31 | split = split.filter((part) => { 32 | if (part.trim() === '') { 33 | return false 34 | } 35 | return true 36 | }) 37 | } 38 | 39 | if (split.length < 2) { 40 | return false 41 | } 42 | 43 | if (split[1].trim().charAt(0) === '$' || split[1].trim().charAt(0) === '!') { 44 | sentence = split[1].trim() 45 | block = split[0].trim().split(',') 46 | } 47 | 48 | block.forEach((t) => { 49 | if (t.indexOf('c:') !== -1) { 50 | tags.timestamp = parseInt(t.replace('c:', '').split('*')[0], 10) 51 | } 52 | 53 | if (t.indexOf('s:') !== -1) { 54 | tags.source = t.replace('s:', '') 55 | } 56 | }) 57 | 58 | if (typeof tags.timestamp === 'number') { 59 | let len = String(tags.timestamp).length 60 | 61 | if (len <= 12) { 62 | tags.timestamp *= 1000 63 | } 64 | 65 | tags.timestamp = moment.tz(tags.timestamp, 'UTC').toISOString() 66 | } 67 | 68 | return { 69 | sentence, 70 | tags, 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /test/DBT.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.use(require('chai-things')) 24 | 25 | describe('DBT', () => { 26 | it('Converts OK using individual parser', () => { 27 | const delta = new Parser().parse('$IIDBT,035.53,f,010.83,M,005.85,F*23') 28 | delta.updates[0].values.should.contain.an.item.with.property( 29 | 'path', 30 | 'environment.depth.belowTransducer' 31 | ) 32 | delta.updates[0].values.should.contain.an.item.with.property('value', 10.83) 33 | }) 34 | 35 | it('Converts with only feet in the sentence', () => { 36 | const delta = new Parser().parse('$IIDBT,432.8,f,,M,,F*1C') 37 | delta.updates.length.should.equal(1) 38 | delta.updates[0].values.length.should.equal(1) 39 | delta.updates[0].values.should.contain.an.item.with.property( 40 | 'path', 41 | 'environment.depth.belowTransducer' 42 | ) 43 | delta.updates[0].values.should.contain.an.item.with.property( 44 | 'value', 45 | 131.91744 46 | ) 47 | }) 48 | 49 | it('Converts empty value to null', () => { 50 | const delta = new Parser().parse('$IIDBT,,,,,,*52') 51 | delta.updates[0].values.length.should.equal(1) 52 | delta.updates[0].values[0].path.should.equal( 53 | 'environment.depth.belowTransducer' 54 | ) 55 | should.equal(delta.updates[0].values[0].value, null) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /test/GLL.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | chai.use(require('chai-things')) 23 | chai.use(require('@signalk/signalk-schema').chaiModule) 24 | 25 | describe('GLL', () => { 26 | it('Converts OK using individual parser', () => { 27 | const delta = new Parser().parse( 28 | '$GPGLL,5958.613,N,02325.928,E,121022,A,D*40' 29 | ) 30 | 31 | delta.updates[0].values[0].path.should.equal('navigation.position') 32 | delta.updates[0].values[0].value.latitude.should.be.closeTo( 33 | 59.9768833, 34 | 0.000005 35 | ) 36 | delta.updates[0].values[0].value.longitude.should.be.closeTo( 37 | 23.432133, 38 | 0.000005 39 | ) 40 | // delta.should.be.validSignalKDelta 41 | }) 42 | 43 | it('Converts OK using individual parser, invalid lat/lng', () => { 44 | const delta = new Parser({ validateChecksum: false }).parse( 45 | // note this malformed lat value is pulled from a real validated malformed RMC example. see test/RMC.js 46 | '$GPGLL,1547\x0E70800,N,02325.928,E,121022,A,D*40' 47 | ) 48 | 49 | delta.updates[0].values[0].path.should.equal('navigation.position') 50 | should.equal(delta.updates[0].values[0].value, null) 51 | }) 52 | 53 | it("Doesn't choke on empty sentences", () => { 54 | const delta = new Parser().parse('$GPGLL,,,,,,,*7C') 55 | should.equal(delta, null) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /test/PNKEP.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2018 Signal K and contributors. 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 Parser = require('../lib') 18 | const toFull = require('./toFull') 19 | const chai = require('chai') 20 | const should = chai.Should() 21 | 22 | chai.use(require('@signalk/signalk-schema').chaiModule) 23 | chai.use(require('chai-things')) 24 | 25 | describe('PNKEP', () => { 26 | it('Polarspeed data ', () => { 27 | const delta = new Parser().parse('$PNKEP,01,8.3,N,15.5,K*52') 28 | delta.updates[0].values.should.contain.an.item.with.property( 29 | 'path', 30 | 'performance.targetSpeed' 31 | ) 32 | delta.updates[0].values[0].value.should.be.closeTo( 33 | 4.269889970594349, 34 | 0.0005 35 | ) 36 | toFull(delta).should.be.validSignalK 37 | }) 38 | 39 | it('Course on next track data', () => { 40 | const delta = new Parser().parse('$PNKEP,02,344.4*6B') 41 | 42 | delta.updates[0].values.should.contain.an.item.with.property( 43 | 'path', 44 | 'performance.tackMagnetic' 45 | ) 46 | delta.updates[0].values[0].value.should.be.closeTo(6.0109139439, 0.00005) 47 | toFull(delta).should.be.validSignalK 48 | }) 49 | 50 | it('Direction data', () => { 51 | const delta = new Parser().parse('$PNKEP,03,152.0,55.2,67.1*69') 52 | 53 | delta.updates[0].values.should.contain.an.item.with.property( 54 | 'path', 55 | 'performance.targetAngle' 56 | ) 57 | delta.updates[0].values[0].value.should.be.closeTo(2.652900463, 0.00005) 58 | toFull(delta).should.be.validSignalK 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /test/customSentenceParser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2021 Signal K and Teppo Kurki 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 Parser = require('../lib') 18 | const chai = require('chai') 19 | const { expect } = require('chai') 20 | const should = chai.Should() 21 | chai.use(require('chai-things')) 22 | 23 | describe('Custom Sentence Parser', () => { 24 | it('works', () => { 25 | const TEST_SENTENCE_PARTS = ['1', '2', '3', 'foobar', 'D'] 26 | const TEST_CUSTOM_SENTENCE = `$IIXXX,${TEST_SENTENCE_PARTS.join(',')}*17` 27 | const DELTA = { 28 | updates: [ 29 | { 30 | values: [{ path: 'a.b.c', value: 3.14 }], 31 | }, 32 | ], 33 | } 34 | let onPropValuesCallCount = 0 35 | const options = { 36 | onPropertyValues: (propertyName, cb) => { 37 | onPropValuesCallCount++ 38 | cb(undefined) 39 | cb([ 40 | { 41 | value: { 42 | sentence: 'XXX', 43 | parser: ({ id, sentence, parts, tags }, session) => { 44 | id.should.equal('XXX') 45 | sentence.should.equal(TEST_CUSTOM_SENTENCE) 46 | parts.should.have.members(TEST_SENTENCE_PARTS) 47 | expect(typeof session).to.equal('object') 48 | return DELTA 49 | }, 50 | }, 51 | }, 52 | ]) 53 | }, 54 | } 55 | const parser = new Parser(options) 56 | onPropValuesCallCount.should.equal(1) 57 | const delta = parser.parse(TEST_CUSTOM_SENTENCE) 58 | delta.should.deep.equal(DELTA) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /hooks/MWV.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/MWV') 20 | const utils = require('@signalk/nmea0183-utilities') 21 | 22 | function convertToWindAngle(angle) { 23 | const numAngle = utils.float(angle) % 360 24 | if (numAngle > 180 && numAngle <= 360) { 25 | return numAngle - 360 26 | } 27 | return numAngle 28 | } 29 | 30 | module.exports = function (input) { 31 | const { id, sentence, parts, tags } = input 32 | 33 | if (!parts[4] || parts[4].toUpperCase() !== 'A') { 34 | return null 35 | } 36 | 37 | let wsu = parts[3].toUpperCase() 38 | 39 | if (wsu === 'K') { 40 | wsu = 'kph' 41 | } else if (wsu === 'N') { 42 | wsu = 'knots' 43 | } else { 44 | wsu = 'ms' 45 | } 46 | 47 | const angle = convertToWindAngle(parts[0]) 48 | const speed = utils.transform(parts[2], wsu, 'ms') 49 | const valueType = parts[1].toUpperCase() == 'R' ? 'Apparent' : 'True' 50 | const angleType = parts[1].toUpperCase() == 'R' ? 'Apparent' : 'TrueWater' 51 | 52 | const delta = { 53 | updates: [ 54 | { 55 | source: tags.source, 56 | timestamp: tags.timestamp, 57 | values: [ 58 | { 59 | path: 'environment.wind.speed' + valueType, 60 | value: speed, 61 | }, 62 | { 63 | path: 'environment.wind.angle' + angleType, 64 | value: utils.transform(angle, 'deg', 'rad'), 65 | }, 66 | ], 67 | }, 68 | ], 69 | } 70 | 71 | return delta 72 | } 73 | -------------------------------------------------------------------------------- /hooks/seatalk/0x50.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 50 Z2 XX YY YY LAT position: XX degrees, (YYYY & 0x7FFF)/100 minutes 23 | MSB of Y = YYYY & 0x8000 = South if set, North if cleared 24 | Z= 0xA or 0x0 (reported for Raystar 120 GPS), meaning unknown 25 | Stable filtered position, for raw data use command 58 26 | Corresponding NMEA sentences: RMC, GAA, GLL 27 | */ 28 | 29 | module.exports = function (input, session) { 30 | const { id, sentence, parts, tags } = input 31 | 32 | var Z = (parseInt(parts[1], 16) & 0xf0) >> 4 33 | var XX = parseInt(parts[2], 16) 34 | var YYYY = parseInt(parts[3], 16) + 256 * parseInt(parts[4], 16) 35 | var s = 1 36 | if ((YYYY & 0x8000) != 0) { 37 | s = -1 38 | } 39 | var minutes = (YYYY & 0x7fff) / 100.0 40 | session['latitude'] = s * (XX + minutes / 60) 41 | 42 | var pathValues = [] 43 | 44 | if ( 45 | session.hasOwnProperty('latitude') && 46 | session.hasOwnProperty('longitude') 47 | ) { 48 | pathValues.push({ 49 | path: 'navigation.position', 50 | value: { 51 | longitude: utils.float(session['longitude']), 52 | latitude: utils.float(session['latitude']), 53 | }, 54 | }) 55 | } 56 | 57 | return { 58 | updates: [ 59 | { 60 | source: tags.source, 61 | timestamp: tags.timestamp, 62 | values: pathValues, 63 | }, 64 | ], 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /hooks/seatalk/0x51.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 51 Z2 XX YY YY LON position: XX degrees, (YYYY & 0x7FFF)/100 minutes 23 | MSB of Y = YYYY & 0x8000 = East if set, West if cleared 24 | Z= 0xA or 0x0 (reported for Raystar 120 GPS), meaning unknown 25 | Stable filtered position, for raw data use command 58 26 | Corresponding NMEA sentences: RMC, GAA, GLL 27 | */ 28 | 29 | module.exports = function (input, session) { 30 | const { id, sentence, parts, tags } = input 31 | 32 | var Z = (parseInt(parts[1], 16) & 0xf0) >> 4 33 | var XX = parseInt(parts[2], 16) 34 | var YYYY = parseInt(parts[3], 16) + 256 * parseInt(parts[4], 16) 35 | var s = 1 36 | if ((YYYY & 0x8000) == 0) { 37 | s = -1 38 | } 39 | var minutes = (YYYY & 0x7fff) / 100.0 40 | session['longitude'] = s * (XX + minutes / 60.0) 41 | 42 | var pathValues = [] 43 | 44 | if ( 45 | session.hasOwnProperty('latitude') && 46 | session.hasOwnProperty('longitude') 47 | ) { 48 | pathValues.push({ 49 | path: 'navigation.position', 50 | value: { 51 | longitude: utils.float(session['longitude']), 52 | latitude: utils.float(session['latitude']), 53 | }, 54 | }) 55 | } 56 | 57 | return { 58 | updates: [ 59 | { 60 | source: tags.source, 61 | timestamp: tags.timestamp, 62 | values: pathValues, 63 | }, 64 | ], 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /test/VHW.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.use(require('chai-things')) 24 | 25 | describe('VHW', () => { 26 | it('speed data only', () => { 27 | const delta = new Parser().parse('$IIVHW,,T,,M,06.12,N,11.33,K*50') 28 | 29 | delta.updates[0].values.should.contain.an.item.with.property( 30 | 'path', 31 | 'navigation.speedThroughWater' 32 | ) 33 | delta.updates[0].values[0].value.should.be.closeTo(3.148400797594869, 0.005) 34 | }) 35 | 36 | it('speed & direction data', () => { 37 | const delta = new Parser().parse('$SDVHW,182.5,T,181.8,M,0.0,N,0.0,K*4C') 38 | 39 | delta.updates[0].values.should.contain.an.item.with.property( 40 | 'path', 41 | 'navigation.speedThroughWater' 42 | ) 43 | delta.updates[0].values[2].value.should.be.closeTo(0, 0.00005) 44 | delta.updates[0].values.should.contain.an.item.with.property( 45 | 'path', 46 | 'navigation.headingMagnetic' 47 | ) 48 | delta.updates[0].values[1].value.should.be.closeTo( 49 | 3.1730085801256913, 50 | 0.00005 51 | ) 52 | delta.updates[0].values.should.contain.an.item.with.property( 53 | 'path', 54 | 'navigation.headingTrue' 55 | ) 56 | delta.updates[0].values[0].value.should.be.closeTo( 57 | 3.1852258848896517, 58 | 0.00005 59 | ) 60 | }) 61 | 62 | /* FIXME! 63 | it('Doesn\'t choke on empty sentences', () => { 64 | const delta = new Parser().parse('$IIRPM,,,,,*63') 65 | should.equal(delta, null) 66 | }) */ 67 | }) 68 | -------------------------------------------------------------------------------- /hooks/HSC.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Signal K and Fabian Tollenaar . 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')('signalk-parser-nmea0183/HSC') 18 | const utils = require('@signalk/nmea0183-utilities') 19 | 20 | /** 21 | * $FTHSC,40.12,T,39.11,M*5E 22 | * 23 | * Heading Steering Command 24 | * 25 | * 0) 40.12 Heading to steer, degrees true 26 | * 1) T True 27 | * 2) 39.11 Heading to steer, degrees magnetic 28 | * 3) M Magnetic 29 | **/ 30 | 31 | module.exports = function HSCHook(input) { 32 | const { id, sentence, parts, tags } = input 33 | const upper = (str) => str.trim().toUpperCase() 34 | 35 | debug(`[HSCHook] decoding sentence ${id} => ${sentence}`) 36 | 37 | if ( 38 | upper(parts[1]) === '' || 39 | upper(parts[3]) === '' || 40 | upper(parts[0]) === '' || 41 | upper(parts[2]) === '' 42 | ) { 43 | return null 44 | } 45 | 46 | const headingToSteer = {} 47 | headingToSteer[upper(parts[1]) === 'T' ? 'True' : 'Magnetic'] = 48 | utils.transform(parts[0], 'deg', 'rad') 49 | headingToSteer[upper(parts[3]) === 'T' ? 'True' : 'Magnetic'] = 50 | utils.transform(parts[2], 'deg', 'rad') 51 | 52 | return { 53 | updates: [ 54 | { 55 | source: tags.source, 56 | timestamp: tags.timestamp, 57 | values: [ 58 | { 59 | path: 'steering.autopilot.target.headingTrue', 60 | value: headingToSteer.True || null, 61 | }, 62 | { 63 | path: 'steering.autopilot.target.headingMagnetic', 64 | value: headingToSteer.Magnetic || null, 65 | }, 66 | ], 67 | }, 68 | ], 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /hooks/DBT.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/DBT') 20 | const utils = require('@signalk/nmea0183-utilities') 21 | 22 | const FEET_TO_METERS = 0.3048 23 | 24 | /* 25 | === DBT - Depth below transducer === 26 | ------------------------------------------------------------------------------ 27 | *******0 1 2 3 4 5 6 28 | *******| | | | | | | 29 | $--DBT,x.x,f,x.x,M,x.x,F*hh 30 | ------------------------------------------------------------------------------ 31 | Field Number: 32 | 0. Depth, feet 33 | 1. f = feet 34 | 2. Depth, meters 35 | 3. M = meters 36 | 4. Depth, Fathoms 37 | 5. F = Fathoms 38 | 6. Checksum 39 | */ 40 | 41 | module.exports = function (input) { 42 | const { id, sentence, parts, tags } = input 43 | 44 | let meterValue = parts[2] 45 | 46 | if (hasNoValue(meterValue)) { 47 | const feetValue = parts[0] 48 | if (hasNoValue(feetValue)) { 49 | meterValue = null 50 | } else { 51 | meterValue = utils.float(feetValue) * FEET_TO_METERS 52 | } 53 | } else { 54 | meterValue = utils.float(meterValue) 55 | } 56 | 57 | const delta = { 58 | updates: [ 59 | { 60 | source: tags.source, 61 | timestamp: tags.timestamp, 62 | values: [ 63 | { 64 | path: 'environment.depth.belowTransducer', 65 | value: meterValue, 66 | }, 67 | ], 68 | }, 69 | ], 70 | } 71 | 72 | return delta 73 | } 74 | 75 | const hasNoValue = (value) => 76 | (typeof value !== 'string' && typeof value !== 'number') || 77 | (typeof value === 'string' && value.trim() === '') 78 | -------------------------------------------------------------------------------- /hooks/seatalk/0x54.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 54 T1 RS HH GMT-time: 23 | HH hours, 6 MSBits of RST = minutes = (RS & 0xFC) / 4 24 | 6 LSBits of RST = seconds = ST & 0x3F 25 | */ 26 | 27 | module.exports = function (input, session) { 28 | const { id, sentence, parts, tags } = input 29 | 30 | var T = (parseInt(parts[1], 16) & 0xf0) >> 4 31 | var S = parseInt(parts[2], 16) & 0x0f 32 | var RS = parseInt(parts[2], 16) 33 | var HH = parseInt(parts[3], 16) 34 | 35 | var ST = (S << 4) + T 36 | 37 | var hour = HH 38 | var minute = (RS & 0xfc) / 4 39 | var second = ST & 0x3f 40 | var milliSecond = 0 41 | 42 | session['time'] = { 43 | hour: hour, 44 | minute: minute, 45 | second: second, 46 | milliSecond: milliSecond, 47 | } 48 | 49 | var pathValues = [] 50 | 51 | if (session.hasOwnProperty('date') && session.hasOwnProperty('time')) { 52 | const d = new Date( 53 | Date.UTC( 54 | session['date'].year, 55 | session['date'].month - 1, 56 | session['date'].day, 57 | session['time'].hour, 58 | session['time'].minute, 59 | session['time'].second, 60 | session['time'].milliSecond 61 | ) 62 | ) 63 | const ts = d.toISOString() 64 | 65 | pathValues.push({ 66 | path: 'navigation.datetime', 67 | value: ts, 68 | }) 69 | } 70 | return { 71 | updates: [ 72 | { 73 | source: tags.source, 74 | timestamp: tags.timestamp, 75 | values: pathValues, 76 | }, 77 | ], 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /hooks/VHW.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | === VHW - Water speed and heading === 23 | ------------------------------------------------------------------------------ 24 | 0 1 2 3 4 5 6 7 8 25 | | | | | | | | | | 26 | $--VHW,x.x,T,x.x,M,x.x,N,x.x,K*hh 27 | ------------------------------------------------------------------------------ 28 | $IIVHW,,T,,M,06.14,N,11.37,K*52 29 | Field Number: 30 | 0: Degress True 31 | 1: T = True 32 | 2: Degrees Magnetic 33 | 3: M = Magnetic 34 | 4: Knots (speed of vessel relative to the water) 35 | 5: N = Knots 36 | 6. Kilometers (speed of vessel relative to the water) 37 | 7. K = Kilometers 38 | 8. Checksum 39 | */ 40 | 41 | module.exports = function (input) { 42 | var velocityValue 43 | const { id, sentence, parts, tags } = input 44 | var pathValues = [] 45 | 46 | if (parts[0] != '') { 47 | pathValues.push({ 48 | path: 'navigation.headingTrue', 49 | value: utils.transform(utils.float(parts[0]), 'deg', 'rad'), 50 | }) 51 | } 52 | if (parts[2] != '') { 53 | pathValues.push({ 54 | path: 'navigation.headingMagnetic', 55 | value: utils.transform(utils.float(parts[2]), 'deg', 'rad'), 56 | }) 57 | } 58 | if (parts[4] != '') { 59 | pathValues.push({ 60 | path: 'navigation.speedThroughWater', 61 | value: utils.transform(utils.float(parts[4]), 'knots', 'ms'), 62 | }) 63 | } 64 | 65 | const delta = { 66 | updates: [ 67 | { 68 | source: tags.source, 69 | timestamp: tags.timestamp, 70 | values: pathValues, 71 | }, 72 | ], 73 | } 74 | 75 | return delta 76 | } 77 | -------------------------------------------------------------------------------- /test/VWR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 Parser = require('../lib') 18 | const chai = require('chai') 19 | const should = chai.Should() 20 | chai.use(require('chai-things')) 21 | 22 | describe('VWR', () => { 23 | it('Converts OK using individual parser', () => { 24 | const delta = new Parser().parse('$IIVWR,75,R,1.0,N,0.51,M,1.85,K*6C') 25 | 26 | delta.updates[0].values.should.contain.an.item.with.property( 27 | 'path', 28 | 'environment.wind.angleApparent' 29 | ) 30 | delta.updates[0].values.should.contain.an.item.with.property( 31 | 'value', 32 | 1.30899693929463 33 | ) 34 | delta.updates[0].values.should.contain.an.item.with.property( 35 | 'path', 36 | 'environment.wind.speedApparent' 37 | ) 38 | delta.updates[0].values.should.contain.an.item.with.property( 39 | 'value', 40 | 0.5144445747704034 41 | ) 42 | }) 43 | 44 | it('Handles shorter valid sentences', () => { 45 | const delta = new Parser().parse('$IIVWR,024,L,018,N,,,,*5e') 46 | 47 | delta.updates[0].values.should.contain.an.item.with.property( 48 | 'path', 49 | 'environment.wind.angleApparent' 50 | ) 51 | delta.updates[0].values.should.contain.an.item.with.property( 52 | 'value', 53 | -0.41887902057428156 54 | ) 55 | delta.updates[0].values.should.contain.an.item.with.property( 56 | 'path', 57 | 'environment.wind.speedApparent' 58 | ) 59 | delta.updates[0].values.should.contain.an.item.with.property( 60 | 'value', 61 | 9.260002345867262 62 | ) 63 | }) 64 | 65 | it("Doesn't choke on empty sentences", () => { 66 | const delta = new Parser().parse('$IIVWR,,,,,,,,*53') 67 | should.equal(delta, null) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /test/BOD.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.Should() 24 | chai.use(require('chai-things')) 25 | 26 | describe('BOD', () => { 27 | it('Converts OK using individual parser', () => { 28 | const delta = new Parser().parse('$GPBOD,045.,T,023.,M,DEST,START*01') 29 | // console.log(JSON.stringify(delta, null, 2)) 30 | 31 | delta.should.be.an('object') 32 | delta.updates[0].values.should.contain.an.item.with.property( 33 | 'path', 34 | 'navigation.courseRhumbline.bearingTrackTrue' 35 | ) 36 | delta.updates[0].values.should.contain.an.item.with.property( 37 | 'value', 38 | 0.7853981635767779 39 | ) 40 | delta.updates[0].values.should.contain.an.item.with.property( 41 | 'path', 42 | 'navigation.courseRhumbline.bearingTrackMagnetic' 43 | ) 44 | delta.updates[0].values.should.contain.an.item.with.property( 45 | 'value', 46 | 0.40142572805035315 47 | ) 48 | delta.updates[0].values.should.contain.an.item.with.property( 49 | 'path', 50 | 'navigation.courseRhumbline.nextPoint.ID' 51 | ) 52 | delta.updates[0].values.should.contain.an.item.with.property( 53 | 'value', 54 | 'DEST' 55 | ) 56 | delta.updates[0].values.should.contain.an.item.with.property( 57 | 'path', 58 | 'navigation.courseRhumbline.previousPoint.ID' 59 | ) 60 | delta.updates[0].values.should.contain.an.item.with.property( 61 | 'value', 62 | 'START' 63 | ) 64 | }) 65 | 66 | it("Doesn't choke on an empty sentence", () => { 67 | const delta = new Parser().parse('$GPBOD,,,,,,*5E') 68 | should.equal(delta, null) 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /hooks/seatalk/0x00.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 00 02 YZ XX XX Depth below transducer: XXXX/10 feet 23 | Flags in Y: Y&8 = 8: Anchor Alarm is active 24 | Y&4 = 4: Metric display units or 25 | Fathom display units if followed by command 65 26 | Y&2 = 2: Used, unknown meaning 27 | Flags in Z: Z&4 = 4: Transducer defective 28 | Z&2 = 2: Deep Alarm is active 29 | Z&1 = 1: Shallow Depth Alarm is active 30 | */ 31 | 32 | module.exports = function (input) { 33 | const { id, sentence, parts, tags } = input 34 | 35 | var Y = parseInt(parts[2].charAt(0), 16) 36 | var Z = parseInt(parts[2].charAt(1), 16) 37 | var s = 1 38 | var XXXX = 39 | parseInt(parts[3], 16) + ((parseInt(parts[4], 16) & 0x7f & 0xffff) << 8) 40 | var depthbelowtransducer = (0.3048 * XXXX) / 10.0 41 | if ((parseInt(parts[3], 16) & 0x80) != 0) { 42 | s = -1 43 | } 44 | 45 | var modeY 46 | if ((Y & 8) == 8) { 47 | var mode = 'AnchorAlarmActive' 48 | } 49 | if ((Y & 4) == 4) { 50 | var mode = 'MetricDisplayOrFathom' 51 | } 52 | if ((Y & 2) == 2) { 53 | var mode = 'UnknownMeaning' 54 | } 55 | 56 | var modeZ 57 | if ((Z & 4) == 4) { 58 | var mode = 'TransducerDefective' 59 | } 60 | if ((Z & 2) == 2) { 61 | var mode = 'DeepAlarm' 62 | } 63 | if ((Z & 1) == 1) { 64 | var mode = 'ShallowDepthAlarm' 65 | } 66 | 67 | var pathValues = [] 68 | 69 | pathValues.push({ 70 | path: 'environment.depth.belowTransducer', 71 | value: depthbelowtransducer, 72 | }) 73 | 74 | return { 75 | updates: [ 76 | { 77 | source: tags.source, 78 | timestamp: tags.timestamp, 79 | values: pathValues, 80 | }, 81 | ], 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /test/VTG.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const expect = chai.expect 22 | 23 | chai.Should() 24 | chai.use(require('chai-things')) 25 | 26 | describe('VTG', () => { 27 | it('Converts OK using individual parser', () => { 28 | const delta = new Parser().parse('$GPVTG,0.0,T,359.3,M,0.0,N,0.0,K,A*2F') 29 | 30 | delta.updates[0].values.should.contain.an.item.with.property( 31 | 'path', 32 | 'navigation.courseOverGroundMagnetic' 33 | ) 34 | delta.updates[0].values[0].value.should.be.closeTo(6.271, 0.005) 35 | 36 | delta.updates[0].values.should.contain.an.item.with.property( 37 | 'path', 38 | 'navigation.courseOverGroundTrue' 39 | ) 40 | delta.updates[0].values[1].value.should.equal(0) 41 | 42 | delta.updates[0].values.should.contain.an.item.with.property( 43 | 'path', 44 | 'navigation.speedOverGround' 45 | ) 46 | delta.updates[0].values[2].value.should.equal(0) 47 | }) 48 | 49 | it('Outputs nulls for missing values', () => { 50 | const delta = new Parser().parse('$GPVTG,,T,,M,0.102,N,0.190,K,A*28') 51 | 52 | delta.updates[0].values.should.contain.an.item.with.property( 53 | 'path', 54 | 'navigation.courseOverGroundMagnetic' 55 | ) 56 | expect(delta.updates[0].values[0].value).to.be.null 57 | 58 | delta.updates[0].values.should.contain.an.item.with.property( 59 | 'path', 60 | 'navigation.courseOverGroundTrue' 61 | ) 62 | expect(delta.updates[0].values[1].value).to.be.null 63 | 64 | delta.updates[0].values.should.contain.an.item.with.property( 65 | 'path', 66 | 'navigation.speedOverGround' 67 | ) 68 | delta.updates[0].values[2].value.should.be.closeTo(0.0528, 0.00005) 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /test/BWR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.Should() 24 | chai.use(require('chai-things')) 25 | 26 | describe('BWR', () => { 27 | it('Converts OK using individual parser', () => { 28 | const delta = new Parser().parse( 29 | '$GPBWR,225444,4917.24,N,12309.57,W,051.9,T,031.6,M,001.3,N,004*38' 30 | ) 31 | // console.log(JSON.stringify(delta, null, 2)) 32 | 33 | delta.should.be.an('object') 34 | delta.updates[0].values.should.deep.equal([ 35 | { 36 | path: 'navigation.courseRhumbline.bearingTrackTrue', 37 | value: 0.9058258819918839, 38 | }, 39 | { 40 | path: 'navigation.courseRhumbline.bearingTrackMagnetic', 41 | value: 0.5515240437561374, 42 | }, 43 | { 44 | path: 'navigation.courseRhumbline.nextPoint.distance', 45 | value: 2407.6000020320143, 46 | }, 47 | { 48 | path: 'navigation.courseRhumbline.nextPoint.position', 49 | value: { 50 | latitude: 49.287333333333336, 51 | longitude: -123.1595, 52 | }, 53 | }, 54 | ]) 55 | }) 56 | 57 | it("Doesn't choke on an empty sentence", () => { 58 | const delta = new Parser().parse('$GPBWR,,,,,,,,,,,,*50') 59 | delta.updates[0].values.should.deep.equal([ 60 | { path: 'navigation.courseRhumbline.bearingTrackTrue', value: null }, 61 | { 62 | path: 'navigation.courseRhumbline.bearingTrackMagnetic', 63 | value: null, 64 | }, 65 | { 66 | path: 'navigation.courseRhumbline.nextPoint.distance', 67 | value: null, 68 | }, 69 | { 70 | path: 'navigation.courseRhumbline.nextPoint.position', 71 | value: null, 72 | }, 73 | ]) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /hooks/VWR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | VWR - Relative (Apparent) Wind Speed and Angle 23 | 0 1 2 3 4 5 6 7 8 24 | $--VWR,x.x,a,x.x,N,x.x,M,x.x,K*hh 25 | 0 - Measured wind angle relative to the vessel, 0 to 180 deg 26 | 1 - a = L/R left or right of vessel heading 27 | 2 - Measured wind Speed, knots 28 | 3 - N = knots 29 | 4 - Wind speed, meters/second 30 | 5 - M = m/s 31 | 6 - Wind speed, Km/Hr 32 | 7 - K = km/h 33 | 8 - Checksum 34 | */ 35 | 36 | function isEmpty(mixed) { 37 | return ( 38 | (typeof mixed !== 'string' && typeof mixed !== 'number') || 39 | (typeof mixed === 'string' && mixed.trim() === '') 40 | ) 41 | } 42 | 43 | module.exports = function (input) { 44 | const { id, sentence, parts, tags } = input 45 | 46 | const empty = parts.reduce((count, part) => { 47 | count += isEmpty(part) ? 1 : 0 48 | return count 49 | }, 0) 50 | if (empty > 4) { 51 | return null 52 | } 53 | 54 | var rightPositive = 0 55 | if (String(parts[1]).toUpperCase() === 'R') { 56 | rightPositive = 1 57 | } else if (String(parts[1]).toUpperCase() === 'L') { 58 | rightPositive = -1 59 | } 60 | 61 | return { 62 | updates: [ 63 | { 64 | source: tags.source, 65 | timestamp: tags.timestamp, 66 | values: [ 67 | { 68 | path: 'environment.wind.angleApparent', 69 | value: utils.transform( 70 | utils.float(parts[0]) * rightPositive, 71 | 'deg', 72 | 'rad' 73 | ), 74 | }, 75 | { 76 | path: 'environment.wind.speedApparent', 77 | value: utils.transform(utils.float(parts[2]), 'knots', 'ms'), 78 | }, 79 | ], 80 | }, 81 | ], 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /hooks/DPT.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/DBT') 20 | const utils = require('@signalk/nmea0183-utilities') 21 | 22 | /* 23 | === DPT - Depth of water === 24 | ------------------------------------------------------------------------------ 25 | *******0 1 2 26 | *******| | | 27 | $--DPT,x.x,x.x*hh 28 | ------------------------------------------------------------------------------ 29 | Field Number: 30 | 0. Depth, meters 31 | 1. transducer offset, positive means distance from tansducer to water line negative means distance from transducer to keel 32 | 2. Checksum 33 | */ 34 | 35 | module.exports = function (input) { 36 | const { id, sentence, parts, tags } = input 37 | 38 | var depth = parts[0].trim() == '' ? null : utils.float(parts[0]) 39 | 40 | const delta = { 41 | updates: [ 42 | { 43 | source: tags.source, 44 | timestamp: tags.timestamp, 45 | values: [ 46 | { 47 | path: 'environment.depth.belowTransducer', 48 | value: depth, 49 | }, 50 | ], 51 | }, 52 | ], 53 | } 54 | 55 | var offset = utils.float(parts[1]) 56 | 57 | if (offset > 0) { 58 | delta.updates[0].values.push({ 59 | path: 'environment.depth.surfaceToTransducer', 60 | value: offset, 61 | }) 62 | if (depth !== null) { 63 | delta.updates[0].values.push({ 64 | path: 'environment.depth.belowSurface', 65 | value: depth + offset, 66 | }) 67 | } 68 | } else if (offset < 0) { 69 | delta.updates[0].values.push({ 70 | path: 'environment.depth.transducerToKeel', 71 | value: offset * -1, 72 | }) 73 | if (depth !== null) { 74 | delta.updates[0].values.push({ 75 | path: 'environment.depth.belowKeel', 76 | value: depth + offset, 77 | }) 78 | } 79 | } 80 | 81 | return delta 82 | } 83 | -------------------------------------------------------------------------------- /hooks/XTE.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Signal K and Fabian Tollenaar . 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')('signalk-parser-nmea0183/XTE') 18 | const utils = require('@signalk/nmea0183-utilities') 19 | 20 | /** 21 | * 0 1 2 3 4 22 | * | | | | | 23 | * $--XTE,A,A,x.x,a,N,*hh 24 | * 25 | * 0) Status V = LORAN-C blink or SNR warning 26 | * A = general warning flag or other navigation systems when a reliable fix is not available 27 | * 28 | * 1) Status V = Loran-C cycle lock warning flag 29 | * A = OK or not used 30 | * 31 | * 2) Cross track error magnitude 32 | * 3) Direction to steer, L or R 33 | * 4) Cross track units. N = Nautical Miles 34 | **/ 35 | 36 | module.exports = function XTEHook(input) { 37 | const { id, sentence, parts, tags } = input 38 | 39 | debug(`[XTEHook] decoding sentence ${id} => ${sentence}`) 40 | 41 | if (parts[0].trim().toUpperCase() === 'V') { 42 | // Don't parse this sentence as it's void. 43 | throw new Error( 44 | "Not parsing sentence for it's void (LORAN-C blink/SNR warning)" 45 | ) 46 | } 47 | 48 | if (parts[1].trim().toUpperCase() === 'V') { 49 | throw new Error( 50 | "Not parsing sentence for it's void (LORAN-C cycle warning)" 51 | ) 52 | } 53 | 54 | if (parts[2].trim() === '' && parts[3].trim() === '') { 55 | return null 56 | } 57 | 58 | const direction = parts[3].trim().toUpperCase() === 'L' ? 1 : -1 59 | const value = 60 | direction * 61 | utils.transform( 62 | parts[2], 63 | parts[4].trim().toUpperCase() === 'N' ? 'nm' : 'km', 64 | 'm' 65 | ) 66 | const path = 'navigation.courseRhumbline.crossTrackError' 67 | 68 | return { 69 | updates: [ 70 | { 71 | source: tags.source, 72 | timestamp: tags.timestamp, 73 | values: [ 74 | { 75 | path, 76 | value, 77 | }, 78 | ], 79 | }, 80 | ], 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /test/APB.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | const expect = chai.expect 23 | 24 | chai.Should() 25 | chai.use(require('chai-things')) 26 | 27 | describe('APB', (done) => { 28 | it('Converts OK using individual parser', () => { 29 | const delta = new Parser().parse( 30 | '$GPAPB,A,A,0.10,R,N,V,V,011,M,DEST,011,M,011,M*3C' 31 | ) 32 | // console.log(JSON.stringify(delta, null, 2)) 33 | 34 | delta.should.be.an('object') 35 | delta.updates[0].values 36 | .find((x) => x.path === 'navigation.courseRhumbline.crossTrackError') 37 | .value.should.be.closeTo(-185.2, 0.001) 38 | delta.updates[0].values 39 | .find((x) => x.path === 'navigation.courseRhumbline.bearingTrackMagnetic') 40 | .value.should.be.closeTo(0.19198621776321237, 0.000001) 41 | delta.updates[0].values 42 | .find( 43 | (x) => 44 | x.path === 'navigation.courseRhumbline.bearingToDestinationMagnetic' 45 | ) 46 | .value.should.be.closeTo(0.19198621776321237, 0.000001) 47 | delta.updates[0].values 48 | .find((x) => x.path === 'navigation.courseRhumbline.nextPoint.ID') 49 | .value.should.equal('DEST') 50 | delta.updates[0].values 51 | .find((x) => x.path === 'steering.autopilot.target.headingMagnetic') 52 | .value.should.closeTo(0.19198621776321237, 0.0001) 53 | expect( 54 | delta.updates[0].values.find( 55 | (x) => x.path === 'notifications.arrivalCircleEntered' 56 | ).value 57 | ).to.be.null 58 | expect( 59 | delta.updates[0].values.find( 60 | (x) => x.path === 'notifications.perpendicularPassed' 61 | ).value 62 | ).to.be.null 63 | }) 64 | 65 | it("Doesn't choke on an empty sentence", () => { 66 | const delta = new Parser().parse('$GPAPB,,,,,,,,,,,,,,*44') 67 | should.equal(delta, null) 68 | }) 69 | }) 70 | -------------------------------------------------------------------------------- /hooks/seatalk/0x26.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 26 04 XX XX YY YY DE Speed through water: 23 | XXXX/100 Knots, sensor 1, current speed, valid if D&4=4 24 | YYYY/100 Knots, average speed (trip/time) if D&8=0 25 | or data from sensor 2 if D&8=8 26 | E&1=1: Average speed calulation stopped 27 | E&2=2: Display value in MPH 28 | Corresponding NMEA sentence: VHW 29 | */ 30 | 31 | module.exports = function (input) { 32 | const { id, sentence, parts, tags } = input 33 | 34 | var D = (parseInt(parts[6], 16) & 0xf0) >> 4 35 | var E = parseInt(parts[6], 16) & 0x0f 36 | var XXXX = parseInt(parts[2], 16) + 256 * parseInt(parts[3], 16) 37 | var YYYY = parseInt(parts[4], 16) + 256 * parseInt(parts[5], 16) 38 | 39 | var value1 = XXXX / 100.0 40 | var value2 = YYYY / 100.0 41 | 42 | var pathValues = [] 43 | 44 | // Check if value1 is a valid speedThroughWater 45 | if ((D & 4) == 4) { 46 | var speedThroughWater = value1 47 | // Check if value2 is a valid speedThroughWater from sensor 2 48 | /* 49 | if ((D & 8)==8) { 50 | // compute the speedThroughWater as the average between the two values 51 | speedThroughWater=(value1+value2)/2 52 | } 53 | */ 54 | pathValues.push({ 55 | path: 'navigation.speedThroughWater', 56 | value: utils.transform(utils.float(speedThroughWater), 'knots', 'ms'), 57 | }) 58 | } 59 | 60 | // Check if value2 is a valid averageSpeedThroughWater 61 | if ((D & 8) == 0) { 62 | pathValues.push({ 63 | path: 'navigation.averageSpeedThroughWater', 64 | value: utils.transform(utils.float(value2), 'knots', 'ms'), 65 | }) 66 | } 67 | 68 | return { 69 | updates: [ 70 | { 71 | source: tags.source, 72 | timestamp: tags.timestamp, 73 | values: pathValues, 74 | }, 75 | ], 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /hooks/seatalk/0x9C.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | 9C U1 VW RR Compass heading and Rudder position (see also command 84) 23 | Compass heading in degrees: 24 | The two lower bits of U * 90 + 25 | the six lower bits of VW * 2 + 26 | number of bits set in the two higher bits of U = 27 | (U & 0x3)* 90 + (VW & 0x3F)* 2 + (U & 0xC ? (U & 0xC == 0xC ? 2 : 1): 0) 28 | Turning direction: 29 | Most significant bit of U = 1: Increasing heading, Ship turns right 30 | Most significant bit of U = 0: Decreasing heading, Ship turns left 31 | Rudder position: RR degrees (positive values steer right, 32 | negative values steer left. Example: 0xFE = 2° left) 33 | The rudder angle bar on the ST600R uses this record 34 | */ 35 | 36 | module.exports = function (input) { 37 | const { id, sentence, parts, tags } = input 38 | var U = parseInt(parts[1].charAt(0), 16) 39 | var VW = parseInt(parts[2], 16) 40 | var RR = parseInt(parts[3], 16) 41 | 42 | if (Number.isNaN(U) || Number.isNaN(VW) || Number.isNaN(RR)) { 43 | return null 44 | } 45 | var compassHeading = 46 | (U & 0x3) * 90 + 47 | (VW & 0x3f) * 2 + 48 | (U & 0xc ? (U & (0xc == 0xc) ? 2 : 1) : 0) 49 | var rudderPos = RR 50 | if (rudderPos > 127) { 51 | rudderPos = rudderPos - 256 52 | } 53 | 54 | var pathValues = [] 55 | if (compassHeading) { 56 | pathValues.push({ 57 | path: 'navigation.headingMagnetic', 58 | value: utils.transform(utils.float(compassHeading), 'deg', 'rad'), 59 | }) 60 | } 61 | if (rudderPos) { 62 | pathValues.push({ 63 | path: 'steering.rudderAngle', 64 | value: utils.transform(utils.float(rudderPos), 'deg', 'rad'), 65 | }) 66 | } 67 | 68 | return { 69 | updates: [ 70 | { 71 | source: tags.source, 72 | timestamp: tags.timestamp, 73 | values: pathValues, 74 | }, 75 | ], 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /hooks/BOD.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Signal K and Fabian Tollenaar . 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')('signalk-parser-nmea0183/BOD') 18 | const utils = require('@signalk/nmea0183-utilities') 19 | 20 | /** 21 | * $GPBOD,045.,T,023.,M,DEST,START*01 22 | * 23 | * Bearing - origin to destination waypoint (BOD) 24 | * 25 | * 0) 045. bearing 045 True from "START" to "DEST" 26 | * 1) T, True 27 | * 2) 023. bearing 023 Magnetic from "START" to "DEST" 28 | * 3) M Magnetic 29 | * 4) DEST destination waypoint ID 30 | * 5) START origin waypoint ID 31 | **/ 32 | 33 | module.exports = function BODHook(input) { 34 | const { id, sentence, parts, tags } = input 35 | const upper = (str) => str.trim().toUpperCase() 36 | 37 | debug(`[BODHook] decoding sentence ${id} => ${sentence}`) 38 | 39 | if ( 40 | upper(parts[0]) === '' || 41 | upper(parts[1]) === '' || 42 | upper(parts[2]) === '' || 43 | upper(parts[3]) === '' || 44 | upper(parts[4]) === '' 45 | ) { 46 | return null 47 | } 48 | 49 | const bearingOriginToDestination = {} 50 | 51 | bearingOriginToDestination[upper(parts[1]) === 'T' ? 'True' : 'Magnetic'] = 52 | utils.transform(parts[0], 'deg', 'rad') 53 | bearingOriginToDestination[upper(parts[3]) === 'T' ? 'True' : 'Magnetic'] = 54 | utils.transform(parts[2], 'deg', 'rad') 55 | const destinationWaypointID = parts[4].trim() 56 | const originWaypointID = parts[5].trim() 57 | 58 | return { 59 | updates: [ 60 | { 61 | source: tags.source, 62 | timestamp: tags.timestamp, 63 | values: [ 64 | { 65 | path: 'navigation.courseRhumbline.bearingTrackTrue', 66 | value: bearingOriginToDestination.True || null, 67 | }, 68 | { 69 | path: 'navigation.courseRhumbline.bearingTrackMagnetic', 70 | value: bearingOriginToDestination.Magnetic || null, 71 | }, 72 | { 73 | path: 'navigation.courseRhumbline.nextPoint.ID', 74 | value: destinationWaypointID, 75 | }, 76 | { 77 | path: 'navigation.courseRhumbline.previousPoint.ID', 78 | value: originWaypointID, 79 | }, 80 | ], 81 | }, 82 | ], 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /hooks/GLL.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the 'License'); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an 'AS IS' BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/GLL') 20 | const utils = require('@signalk/nmea0183-utilities') 21 | const moment = require('moment-timezone') 22 | 23 | /* 24 | === GLL - Geographic Position - Latitude/Longitude === 25 | ------------------------------------------------------------------------------ 26 | 0 1 2 3 4 5 6 27 | | | | | | | | 28 | $--GLL,llll.ll,a,yyyyy.yy,a,hhmmss.ss,a,m,*hh 29 | ------------------------------------------------------------------------------ 30 | Field Number: 31 | 0. Latitude 32 | 1. N or S (North or South) 33 | 2. Longitude 34 | 3. E or W (East or West) 35 | 4. Universal Time Coordinated (UTC) 36 | 5. Status A - Data Valid, V - Data Invalid 37 | 6. FAA mode indicator (NMEA 2.3 and later) 38 | */ 39 | 40 | function isEmpty(mixed) { 41 | return ( 42 | (typeof mixed !== 'string' && typeof mixed !== 'number') || 43 | (typeof mixed === 'string' && mixed.trim() === '') 44 | ) 45 | } 46 | 47 | module.exports = function (input) { 48 | const { id, sentence, parts, tags } = input 49 | 50 | let valid = parts.reduce((v, part) => { 51 | v = !isEmpty(part) 52 | return v 53 | }, true) 54 | 55 | if (typeof parts[5] === 'string' && parts[5].toLowerCase() === 'v') { 56 | valid = false 57 | } 58 | 59 | if (!valid) { 60 | return null 61 | } 62 | 63 | const time = parts[4].indexOf('.') === -1 ? parts[4] : parts[4].split('.')[0] 64 | const timestamp = utils.timestamp(time, moment.tz('UTC').format('DDMMYY')) 65 | 66 | const latitude = utils.coordinate(parts[0], parts[1]) 67 | const longitude = utils.coordinate(parts[2], parts[3]) 68 | let position = null 69 | 70 | if (utils.isValidPosition(latitude, longitude)) { 71 | position = { 72 | latitude: latitude, 73 | longitude: longitude, 74 | } 75 | } 76 | 77 | const delta = { 78 | updates: [ 79 | { 80 | source: tags.source, 81 | timestamp: timestamp, 82 | values: [ 83 | { 84 | path: 'navigation.position', 85 | value: position, 86 | }, 87 | ], 88 | }, 89 | ], 90 | } 91 | 92 | return delta 93 | } 94 | -------------------------------------------------------------------------------- /hooks/MWD.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/MWD') 20 | const utils = require('@signalk/nmea0183-utilities') 21 | 22 | /* 23 | * $WIMWD,<0>,<1>,<2>,<3>,<4>,<5>,<6>,<7>*hh 24 | * 25 | * NMEA 0183 standard Wind Direction and Speed, with respect to north. 26 | * 27 | * <0> Wind direction, 0.0 to 359.9 degrees True, to the nearest 0.1 degree 28 | * <1> T = True 29 | * <2> Wind direction, 0.0 to 359.9 degrees Magnetic, to the nearest 0.1 degree 30 | * <3> M = Magnetic 31 | * <4> Wind speed, knots, to the nearest 0.1 knot. 32 | * <5> N = Knots 33 | * <6> Wind speed, meters/second, to the nearest 0.1 m/s. 34 | * <7> M = Meters/second 35 | */ 36 | 37 | module.exports = function (input) { 38 | const { id, sentence, parts, tags } = input 39 | var pathValues = [] 40 | 41 | // get direction data: 42 | // return both, true and magnetic direction, if present in the NMEA sentence 43 | var haveDirection = false 44 | if (parts[0] != '' && parts[1] == 'T') { 45 | haveDirection = true 46 | pathValues.push({ 47 | path: 'environment.wind.directionTrue', 48 | value: utils.transform(utils.float(parts[0]), 'deg', 'rad'), 49 | }) 50 | } 51 | if (parts[2] != '' && parts[3] == 'M') { 52 | haveDirection = true 53 | pathValues.push({ 54 | path: 'environment.wind.directionMagnetic', 55 | value: utils.transform(utils.float(parts[2]), 'deg', 'rad'), 56 | }) 57 | } 58 | if (!haveDirection) { 59 | return null 60 | } 61 | 62 | // get speed data: 63 | // speed given in kn is used in case no speed in m/s is present in the NMEA sentence 64 | var haveSpeed = false 65 | var speed 66 | if (parts[4] != '' && parts[5] == 'N') { 67 | haveSpeed = true 68 | speed = utils.transform(utils.float(parts[4]), 'knots', 'ms') 69 | } 70 | if (parts[6] != '' && parts[7] == 'M') { 71 | haveSpeed = true 72 | speed = utils.float(parts[6]) 73 | } 74 | if (!haveSpeed) { 75 | return null 76 | } 77 | pathValues.push({ 78 | path: 'environment.wind.speedTrue', 79 | value: speed, 80 | }) 81 | 82 | const delta = { 83 | updates: [ 84 | { 85 | source: tags.source, 86 | timestamp: tags.timestamp, 87 | values: pathValues, 88 | }, 89 | ], 90 | } 91 | 92 | return delta 93 | } 94 | -------------------------------------------------------------------------------- /hooks/ZDA.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ZDA codec 3 | * 4 | * Copyright 2014, Mikko Vesikkala 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License") 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | * 18 | */ 19 | 20 | 'use strict' 21 | 22 | /* 23 | === ZDA - Time & Date === 24 | 25 | ------------------------------------------------------------------------------ 26 | *******1 2 3 4 5 6 7 27 | *******| | | | | | | 28 | $--ZDA,hhmmss.ss,xx,xx,xxxx,xx,xx*hh 29 | ------------------------------------------------------------------------------ 30 | 31 | Field Number: 32 | 1. UTC time (hours, minutes, seconds, may have fractional subsecond) 33 | 2. Day, 01 to 31 34 | 3. Month, 01 to 12 35 | 4. Year (4 digits) 36 | 5. Local zone description, 00 to +- 13 hours 37 | 6. Local zone minutes description, apply same sign as local hours 38 | 7. Checksum 39 | */ 40 | const debug = require('debug')('signalk-parser-nmea0183/ZDA') 41 | const utils = require('@signalk/nmea0183-utilities') 42 | const moment = require('moment-timezone') 43 | 44 | function isEmpty(mixed) { 45 | return ( 46 | (typeof mixed !== 'string' && typeof mixed !== 'number') || 47 | (typeof mixed === 'string' && mixed.trim() === '') 48 | ) 49 | } 50 | 51 | module.exports = function (input) { 52 | const { id, sentence, parts, tags } = input 53 | 54 | const empty = parts.reduce((e, val) => { 55 | if (isEmpty(val)) { 56 | ++e 57 | } 58 | return e 59 | }, 0) 60 | 61 | if (empty > 3) { 62 | return null 63 | } 64 | 65 | const time = parts[0] || '' 66 | const date = parts[1] + parts[2] + (parts[3] || '').slice(-2) 67 | 68 | var delta = {} 69 | if (time.length >= 6 && date.length === 6 && empty < 3) { 70 | const year = parts[3] 71 | const month = parts[2] - 1 72 | const day = parts[1] 73 | const hour = (parts[0] || '').substring(0, 2) 74 | const minute = (parts[0] || '').substring(2, 4) 75 | const second = (parts[0] || '').substring(4, 6) 76 | const milliSecond = (parts[0].substring(4) % 1) * 1000 77 | const d = new Date( 78 | Date.UTC(year, month, day, hour, minute, second, milliSecond) 79 | ) 80 | const ts = d.toISOString() 81 | delta = { 82 | updates: [ 83 | { 84 | source: tags.source, 85 | timestamp: tags.timestamp, 86 | values: [ 87 | { 88 | path: 'navigation.datetime', 89 | value: ts, 90 | }, 91 | ], 92 | }, 93 | ], 94 | } 95 | } 96 | 97 | return delta 98 | } 99 | -------------------------------------------------------------------------------- /test/HDG.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 Parser = require('../lib') 18 | const chai = require('chai') 19 | const should = chai.Should() 20 | 21 | chai.use(require('chai-things')) 22 | 23 | describe('HDG', () => { 24 | it('Converts OK using individual parser', () => { 25 | const delta = new Parser().parse('$SDHDG,181.9,,,0.6,E*32') 26 | delta.updates[0].values.should.contain.an.item.with.property( 27 | 'path', 28 | 'navigation.headingMagnetic' 29 | ) 30 | delta.updates[0].values[0].value.should.be.closeTo( 31 | (181.9 / 180) * Math.PI, 32 | 0.005 33 | ) 34 | delta.updates[0].values.should.contain.an.item.with.property( 35 | 'path', 36 | 'navigation.magneticVariation' 37 | ) 38 | delta.updates[0].values 39 | .find((pv) => pv.path === 'navigation.magneticVariation') 40 | .value.should.be.closeTo((0.6 / 180) * Math.PI, 0.005) 41 | }) 42 | 43 | it('Sentence with just heading works', () => { 44 | const delta = new Parser().parse('$HCHDG,51.5,,,,*73') 45 | 46 | delta.updates[0].values.should.contain.an.item.with.property( 47 | 'path', 48 | 'navigation.headingMagnetic' 49 | ) 50 | delta.updates[0].values[0].value.should.be.closeTo( 51 | (51.5 / 180) * Math.PI, 52 | 0.005 53 | ) 54 | }) 55 | 56 | it('Sentence with all fields converts and applies corrections', () => { 57 | const delta = new Parser().parse('$INHDG,180,5,W,10,W*6D') 58 | 59 | delta.updates[0].values 60 | .find((pv) => pv.path === 'navigation.headingMagnetic') 61 | .value.should.be.closeTo(((180 - 5) / 180) * Math.PI, 0.00001) 62 | delta.updates[0].values 63 | .find((pv) => pv.path === 'navigation.headingCompass') 64 | .value.should.be.closeTo((180 / 180) * Math.PI, 0.00001) 65 | delta.updates[0].values 66 | .find((pv) => pv.path === 'navigation.magneticVariation') 67 | .value.should.be.closeTo((-10 / 180) * Math.PI, 0.00001) 68 | delta.updates[0].values 69 | .find((pv) => pv.path === 'navigation.magneticDeviation') 70 | .value.should.be.closeTo((-5 / 180) * Math.PI, 0.00001) 71 | delta.updates[0].values 72 | .find((pv) => pv.path === 'navigation.headingTrue') 73 | .value.should.be.closeTo(((180 - 5 - 10) / 180) * Math.PI, 0.00001) 74 | }) 75 | 76 | it("Doesn't choke on empty sentences", () => { 77 | const delta = new Parser().parse('$SDHDG,,,,,*70') 78 | should.equal(delta, null) 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /test/BWC.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.Should() 24 | chai.use(require('chai-things')) 25 | 26 | describe('BWC', () => { 27 | it('Converts OK using individual parser', () => { 28 | const delta = new Parser().parse( 29 | '$GPBWC,225444,4917.24,N,12309.57,W,051.9,T,031.6,M,001.3,N,004*29' 30 | ) 31 | // console.log(JSON.stringify(delta, null, 2)) 32 | 33 | delta.should.be.an('object') 34 | delta.updates[0].values.should.deep.equal([ 35 | { 36 | path: 'navigation.courseGreatCircle.nextPoint.position', 37 | value: { 38 | latitude: 49.287333333333336, 39 | longitude: -123.1595, 40 | }, 41 | }, 42 | { 43 | path: 'navigation.courseGreatCircle.nextPoint.distance', 44 | value: 2407.6000020320143, 45 | }, 46 | { 47 | path: 'navigation.courseGreatCircle.bearingTrackTrue', 48 | value: 0.9058258819918839, 49 | }, 50 | { 51 | path: 'navigation.courseGreatCircle.bearingTrackMagnetic', 52 | value: 0.5515240437561374, 53 | }, 54 | ]) 55 | 56 | // delta.updates[0].values.find(x => x.path === 'navigation.courseRhumbline.bearingToDestinationMagnetic').value.should.be.closeTo(0.19198621776321237, 0.000001) 57 | }) 58 | 59 | it('Converts also without next position coordinates', () => { 60 | const delta = new Parser().parse( 61 | '$IIBWC,200321,,,,,119.5,T,129.5,M,22.10,N,1*1E' 62 | ) 63 | 64 | delta.should.be.an('object') 65 | delta.updates[0].values.should.deep.equal([ 66 | { 67 | path: 'navigation.courseGreatCircle.nextPoint.position', 68 | value: null, 69 | }, 70 | { 71 | path: 'navigation.courseGreatCircle.nextPoint.distance', 72 | value: 40929.20003454424, 73 | }, 74 | { 75 | path: 'navigation.courseGreatCircle.bearingTrackTrue', 76 | value: 2.0856684566094437, 77 | }, 78 | { 79 | path: 'navigation.courseGreatCircle.bearingTrackMagnetic', 80 | value: 2.2602013818487277, 81 | }, 82 | ]) 83 | }) 84 | 85 | it("Doesn't choke on an empty sentence", () => { 86 | const delta = new Parser().parse('$GPBWC,,,,,,,,,,,,*41') 87 | 88 | delta.updates[0].values.should.deep.equal([ 89 | { 90 | path: 'navigation.courseGreatCircle.nextPoint.position', 91 | value: null, 92 | }, 93 | ]) 94 | }) 95 | }) 96 | -------------------------------------------------------------------------------- /hooks/VTG.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and contributors. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/HDG') 20 | const utils = require('@signalk/nmea0183-utilities') 21 | 22 | /* 23 | === VTG - Track made good and Ground speed === 24 | ------------------------------------------------------------------------------ 25 | 0 1 2 3 4 5 6 7 8 9 26 | | | | | | | | | | | 27 | $--VTG,x.x,T,x.x,M,x.x,N,x.x,K,m,*hh 28 | ------------------------------------------------------------------------------ 29 | Field Number: 30 | 0. Track Degrees 31 | 1. T = True 32 | 2. Track Degrees 33 | 3. M = Magnetic 34 | 4. Speed Knots 35 | 5. N = Knots 36 | 6. Speed Kilometers Per Hour 37 | 7. K = Kilometers Per Hour 38 | 8. FAA mode indicator (NMEA 2.3 and later) 39 | 9. Checksum 40 | */ 41 | 42 | function isEmpty(mixed) { 43 | return ( 44 | (typeof mixed !== 'string' && typeof mixed !== 'number') || 45 | (typeof mixed === 'string' && mixed.trim() === '') 46 | ) 47 | } 48 | 49 | module.exports = function (input) { 50 | const { id, sentence, parts, tags } = input 51 | 52 | if ( 53 | parts[2] === '' && 54 | parts[0] === '' && 55 | parts[6] === '' && 56 | parts[4] === '' 57 | ) { 58 | return null 59 | } 60 | 61 | let speed = 0.0 62 | 63 | if (utils.float(parts[6]) > 0 && String(parts[7]).toUpperCase() === 'K') { 64 | speed = utils.transform(utils.float(parts[6]), 'kph', 'ms') 65 | } else if ( 66 | utils.float(parts[4]) > 0 && 67 | String(parts[5]).toUpperCase() === 'N' 68 | ) { 69 | speed = utils.transform(utils.float(parts[4]), 'knots', 'ms') 70 | } 71 | 72 | return { 73 | updates: [ 74 | { 75 | source: tags.source, 76 | timestamp: tags.timestamp, 77 | values: [ 78 | { 79 | path: 'navigation.courseOverGroundMagnetic', 80 | value: 81 | parts[2].length === 0 82 | ? null 83 | : utils.transform(utils.float(parts[2]), 'deg', 'rad'), 84 | }, 85 | { 86 | path: 'navigation.courseOverGroundTrue', 87 | value: 88 | parts[0].length === 0 89 | ? null 90 | : utils.transform(utils.float(parts[0]), 'deg', 'rad'), 91 | }, 92 | { 93 | path: 'navigation.speedOverGround', 94 | value: speed, 95 | }, 96 | ], 97 | }, 98 | ], 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /hooks/BWR.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Signal K and Fabian Tollenaar . 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')('signalk-parser-nmea0183/BWR') 18 | const utils = require('@signalk/nmea0183-utilities') 19 | 20 | /** 21 | * $GPBWR,225444,4917.24,N,12309.57,W,051.9,T,031.6,M,001.3,N,004*38 22 | * 23 | * Bearing and distance to waypoint - rhumb line 24 | * 25 | * 0) 225444 UTC time of fix 22:54:44 26 | * 1,2) 4917.24,N Latitude of waypoint 27 | * 3,4) 12309.57,W Longitude of waypoint 28 | * 5,6) 051.9,T Bearing to waypoint, degrees true 29 | * 7,8) 031.6,M Bearing to waypoint, degrees magnetic 30 | * 9,10) 001.3,N Distance to waypoint, Nautical miles 31 | * 11) 004 Waypoint ID 32 | **/ 33 | 34 | module.exports = function BWRHook(input) { 35 | const { id, sentence, parts, tags } = input 36 | const upper = (str) => str.trim().toUpperCase() 37 | 38 | debug(`[BWRHook] decoding sentence ${id} => ${sentence}`) 39 | 40 | let timestamp 41 | let position 42 | let distance 43 | const bearingToWaypoint = {} 44 | 45 | if ( 46 | upper(parts[0]) === '' || 47 | upper(parts[1]) === '' || 48 | upper(parts[2]) === '' || 49 | upper(parts[3]) === '' || 50 | upper(parts[4]) === '' 51 | ) { 52 | timestamp = tags.timestamp 53 | position = null 54 | distance = null 55 | } else { 56 | timestamp = utils.timestamp(parts[0]) 57 | position = { 58 | latitude: utils.coordinate(parts[1], parts[2]), 59 | longitude: utils.coordinate(parts[3], parts[4]), 60 | } 61 | distance = utils.transform( 62 | parts[9], 63 | upper(parts[10]) === 'N' ? 'nm' : 'km', 64 | 'm' 65 | ) 66 | bearingToWaypoint[upper(parts[6]) === 'T' ? 'True' : 'Magnetic'] = 67 | utils.transform(parts[5], 'deg', 'rad') 68 | bearingToWaypoint[upper(parts[8]) === 'T' ? 'True' : 'Magnetic'] = 69 | utils.transform(parts[7], 'deg', 'rad') 70 | } 71 | 72 | return { 73 | updates: [ 74 | { 75 | timestamp, 76 | source: tags.source, 77 | values: [ 78 | { 79 | path: 'navigation.courseRhumbline.bearingTrackTrue', 80 | value: bearingToWaypoint.True || null, 81 | }, 82 | { 83 | path: 'navigation.courseRhumbline.bearingTrackMagnetic', 84 | value: bearingToWaypoint.Magnetic || null, 85 | }, 86 | { 87 | path: 'navigation.courseRhumbline.nextPoint.distance', 88 | value: distance, 89 | }, 90 | { 91 | path: 'navigation.courseRhumbline.nextPoint.position', 92 | value: position, 93 | }, 94 | ], 95 | }, 96 | ], 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /hooks/BWC.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Signal K and Fabian Tollenaar . 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')('signalk-parser-nmea0183/BWC') 18 | const utils = require('@signalk/nmea0183-utilities') 19 | 20 | /** 21 | * $GPBWC,225444,4917.24,N,12309.57,W,051.9,T,031.6,M,001.3,N,004*29 22 | * 23 | * Bearing and distance to waypoint - great circle 24 | * 25 | * 0) 225444 UTC time of fix 22:54:44 26 | * 1,2) 4917.24,N Latitude of waypoint 27 | * 3,4) 12309.57,W Longitude of waypoint 28 | * 5,6) 051.9,T Bearing to waypoint, degrees true 29 | * 7,8) 031.6,M Bearing to waypoint, degrees magnetic 30 | * 9,10) 001.3,N Distance to waypoint, Nautical miles 31 | * 11) 004 Waypoint ID 32 | **/ 33 | 34 | module.exports = function BWCHook(input) { 35 | const { id, sentence, parts, tags } = input 36 | const upper = (str) => str.trim().toUpperCase() 37 | 38 | debug(`[BWCHook] decoding sentence ${id} => ${sentence}`) 39 | 40 | const values = [] 41 | const result = { 42 | updates: [ 43 | { 44 | source: tags.source, 45 | values, 46 | }, 47 | ], 48 | } 49 | 50 | if (parts[0] !== '') { 51 | result.updates[0].timestamp = utils.timestamp(parts[0]) 52 | } 53 | 54 | if ( 55 | parts[1] !== '' && 56 | parts[2] !== '' && 57 | parts[3] !== '' && 58 | parts[4] !== '' 59 | ) { 60 | const latitude = utils.coordinate(parts[1], parts[2]) 61 | const longitude = utils.coordinate(parts[3], parts[4]) 62 | values.push({ 63 | path: 'navigation.courseGreatCircle.nextPoint.position', 64 | value: { 65 | longitude, 66 | latitude, 67 | }, 68 | }) 69 | } else { 70 | values.push({ 71 | path: 'navigation.courseGreatCircle.nextPoint.position', 72 | value: null, 73 | }) 74 | } 75 | 76 | if (parts[9] !== '' && parts[10] !== '') { 77 | const distance = utils.transform( 78 | parts[9], 79 | upper(parts[10]) === 'N' ? 'nm' : 'km', 80 | 'm' 81 | ) 82 | values.push({ 83 | path: 'navigation.courseGreatCircle.nextPoint.distance', 84 | value: distance, 85 | }) 86 | } 87 | 88 | if (parts[6] === 'T' && parts[5] !== '') { 89 | values.push({ 90 | path: 'navigation.courseGreatCircle.bearingTrackTrue', 91 | value: utils.transform(parts[5], 'deg', 'rad'), 92 | }) 93 | } 94 | if (parts[8] === 'M' && parts[7] !== '') { 95 | values.push({ 96 | path: 'navigation.courseGreatCircle.bearingTrackMagnetic', 97 | value: utils.transform(parts[7], 'deg', 'rad'), 98 | }) 99 | } 100 | 101 | return values.length > 0 ? result : undefined 102 | } 103 | -------------------------------------------------------------------------------- /test/RMB.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 Parser = require('../lib') 18 | const chai = require('chai') 19 | const should = chai.Should() 20 | 21 | chai.use(require('chai-things')) 22 | chai.use(require('@signalk/signalk-schema').chaiModule) 23 | 24 | describe('RMB', () => { 25 | it('Converts OK using individual parser', () => { 26 | const delta = new Parser().parse( 27 | '$ECRMB,A,0.000,L,001,002,4653.550,N,07115.984,W,2.505,334.205,0.000,V*04' 28 | ) 29 | should.equal(delta.updates[0].timestamp, undefined) 30 | 31 | delta.updates[0].values.should.contain.an.item.with.property( 32 | 'path', 33 | 'navigation.courseRhumbline.nextPoint.position' 34 | ) 35 | delta.updates[0].values.should.contain.an.item.with.property( 36 | 'path', 37 | 'navigation.courseRhumbline.nextPoint.distance' 38 | ) 39 | delta.updates[0].values.should.contain.an.item.with.property( 40 | 'path', 41 | 'navigation.courseRhumbline.nextPoint.bearingTrue' 42 | ) 43 | delta.updates[0].values.should.contain.an.item.with.property( 44 | 'path', 45 | 'navigation.courseRhumbline.nextPoint.velocityMadeGood' 46 | ) 47 | delta.updates[0].values.should.contain.an.item.with.property( 48 | 'path', 49 | 'navigation.courseRhumbline.crossTrackError' 50 | ) 51 | delta.updates[0].values[0].value.latitude.should.be.closeTo(46.8925, 0.005) 52 | delta.updates[0].values[0].value.longitude.should.be.closeTo( 53 | -71.2664, 54 | 0.005 55 | ) 56 | delta.updates[0].values[1].value.should.be.closeTo(5.832, 0.005) 57 | delta.updates[0].values[2].value.should.be.closeTo(0, 0.005) 58 | delta.updates[0].values[3].value.should.be.closeTo(4639.26, 0.005) 59 | delta.updates[0].values[4].value.should.equal(0) 60 | }) 61 | 62 | it('crossTrackError should be negative to steer right', () => { 63 | const delta = new Parser().parse( 64 | '$ECRMB,A,0.432,R,001,002,4653.550,N,07115.984,W,2.505,334.205,0.000,V*1F' 65 | ) 66 | delta.updates[0].values[4].value.should.be.closeTo(-800.064, 0.005) 67 | }) 68 | 69 | it('crossTrackError should be positive to steer left', () => { 70 | const delta = new Parser().parse( 71 | '$ECRMB,A,0.432,L,001,002,4653.550,N,07115.984,W,2.505,334.205,0.000,V*01' 72 | ) 73 | delta.updates[0].values[4].value.should.be.closeTo(800.064, 0.005) 74 | }) 75 | 76 | it("Doesn't choke on empty sentences", () => { 77 | const delta = new Parser().parse('$ECRMB,,,,,,,,,,,,,*77') 78 | delta.updates[0].values.should.contain.an.item.with.property( 79 | 'path', 80 | 'navigation.courseRhumbline.nextPoint.position' 81 | ) 82 | delta.updates[0].values.should.contain.an.item({ 83 | path: 'navigation.courseRhumbline.nextPoint.position', 84 | value: null, 85 | }) 86 | }) 87 | }) 88 | -------------------------------------------------------------------------------- /hooks/HDG.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and contributors. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/HDG') 20 | const utils = require('@signalk/nmea0183-utilities') 21 | 22 | /* 23 | ******* 0 1 2 3 4 24 | ******* | | | | | 25 | $--HDG,x.x,x.x,a,x.x,a*hh 26 | Field Number: 27 | 0 Magnetic Sensor heading in degrees 28 | 1 Magnetic Deviation, degrees 29 | 2 Magnetic Deviation direction, E = Easterly, W = Westerly 30 | 3 Magnetic Variation degrees 31 | 4 Magnetic Variation direction, E = Easterly, W = Westerly 32 | */ 33 | 34 | function isEmpty(mixed) { 35 | return ( 36 | (typeof mixed !== 'string' && typeof mixed !== 'number') || 37 | (typeof mixed === 'string' && mixed.trim() === '') 38 | ) 39 | } 40 | 41 | module.exports = function (input) { 42 | const { parts, tags } = input 43 | const values = [] 44 | 45 | const headingCompass = parts[0] 46 | const deviation = parts[1] 47 | const deviationDir = parts[2] === 'E' ? 1 : -1 48 | const variation = parts[3] 49 | const variationDir = parts[4] === 'E' ? 1 : -1 50 | if (!isEmpty(headingCompass)) { 51 | const effectiveDeviation = !isEmpty(deviation) 52 | ? Number(deviation) * deviationDir 53 | : 0 54 | values.push({ 55 | path: 'navigation.headingMagnetic', 56 | value: utils.transform( 57 | utils.float(headingCompass) + effectiveDeviation, 58 | 'deg', 59 | 'rad' 60 | ), 61 | }) 62 | if (!isEmpty(deviation)) { 63 | values.push({ 64 | path: 'navigation.headingCompass', 65 | value: utils.transform(utils.float(headingCompass), 'deg', 'rad'), 66 | }) 67 | } 68 | if (!isEmpty(variation)) { 69 | const effectiveVariation = variation * variationDir 70 | values.push({ 71 | path: 'navigation.headingTrue', 72 | value: utils.transform( 73 | utils.float(headingCompass) + effectiveDeviation + effectiveVariation, 74 | 'deg', 75 | 'rad' 76 | ), 77 | }) 78 | } 79 | } 80 | if (!(isEmpty(variation) || isEmpty(variationDir))) { 81 | values.push({ 82 | path: 'navigation.magneticVariation', 83 | value: 84 | utils.transform(utils.float(variation), 'deg', 'rad') * variationDir, 85 | }) 86 | } 87 | if (!(isEmpty(deviation) || isEmpty(deviationDir))) { 88 | values.push({ 89 | path: 'navigation.magneticDeviation', 90 | value: 91 | utils.transform(utils.float(deviation), 'deg', 'rad') * deviationDir, 92 | }) 93 | } 94 | if (!values.length) { 95 | return null 96 | } 97 | 98 | const delta = { 99 | updates: [ 100 | { 101 | source: tags.source, 102 | timestamp: tags.timestamp, 103 | values, 104 | }, 105 | ], 106 | } 107 | 108 | return delta 109 | } 110 | -------------------------------------------------------------------------------- /hooks/VWT.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/VWT') 20 | const utils = require('@signalk/nmea0183-utilities') 21 | 22 | /* 23 | * $WIVWT,<0>,<1>,<2>,<3>,<4>,<5>,<6>,<7>*hh 24 | * 25 | * NMEA 0183 True wind angle in relation to the vessel's heading, and true wind 26 | * speed referenced to the water. True wind is the vector sum of the Relative 27 | * (apparent) wind vector and the vessel's velocity vector relative to the water along 28 | * the heading line of the vessel. It represents the wind at the vessel if it were 29 | * stationary relative to the water and heading in the same direction. 30 | * 31 | * <0> Calculated wind angle relative to the vessel, 0 to 180°, left/right of 32 | * vessel heading, to the nearest 0.1 degree 33 | * <1> L = left, or R = right 34 | * <2> Calculated wind speed, knots, to the nearest 0.1 knot 35 | * <3> N = knots 36 | * <4> Wind speed, meters per second, to the nearest 0.1 m/s 37 | * <5> M = meters per second 38 | * <6> Wind speed, km/h, to the nearest km/h 39 | * <7> K = km/h 40 | */ 41 | 42 | // $IIVWT,030.,R,10.1,N,05.2,M,018.7,K*75 43 | 44 | function convertToWindAngle(angle) { 45 | const numAngle = utils.float(angle) % 360 46 | if (numAngle > 180 && numAngle <= 360) { 47 | return numAngle - 360 48 | } 49 | return numAngle 50 | } 51 | 52 | module.exports = function (input) { 53 | const { id, sentence, parts, tags } = input 54 | 55 | // get direction 56 | if (!parts[0]) { 57 | return null 58 | } 59 | var angle = convertToWindAngle(parts[0]) 60 | 61 | if (!parts[1]) { 62 | return null 63 | } 64 | switch (parts[1]) { 65 | case 'L': 66 | angle = -1 * utils.transform(angle, 'deg', 'rad') 67 | break 68 | case 'R': 69 | angle = utils.transform(angle, 'deg', 'rad') 70 | break 71 | default: 72 | return null 73 | } 74 | 75 | // get speed data: 76 | // speed value given in m/s is given precedence if present in the NMEA sentence 77 | var haveSpeed = false 78 | var speed 79 | if (parts[2] != '' && parts[3] == 'N') { 80 | haveSpeed = true 81 | speed = utils.transform(utils.float(parts[2]), 'knots', 'ms') 82 | } 83 | if (parts[6] != '' && parts[7] == 'K') { 84 | haveSpeed = true 85 | speed = utils.transform(utils.float(parts[6]), 'kph', 'ms') 86 | } 87 | if (parts[4] != '' && parts[5] == 'M') { 88 | // overwrite speed from knots or km/h if present 89 | haveSpeed = true 90 | speed = utils.float(parts[4]) 91 | } 92 | if (!haveSpeed) { 93 | return null 94 | } 95 | 96 | const delta = { 97 | updates: [ 98 | { 99 | source: tags.source, 100 | timestamp: tags.timestamp, 101 | values: [ 102 | { 103 | path: 'environment.wind.speedTrue', 104 | value: speed, 105 | }, 106 | { 107 | path: 'environment.wind.angleTrueWater', 108 | value: angle, 109 | }, 110 | ], 111 | }, 112 | ], 113 | } 114 | 115 | return delta 116 | } 117 | -------------------------------------------------------------------------------- /test/DPT.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.use(require('chai-things')) 24 | 25 | describe('DPT', () => { 26 | it('Converts OK using individual parser', () => { 27 | const delta = new Parser().parse('$IIDPT,4.1,0.0*45') 28 | delta.updates[0].values.should.contain.an.item.with.property( 29 | 'path', 30 | 'environment.depth.belowTransducer' 31 | ) 32 | delta.updates[0].values.should.contain.an.item.with.property('value', 4.1) 33 | }) 34 | 35 | it('Converts OK with missing offset', () => { 36 | const delta = new Parser().parse('$IIDPT,4.1,*6B') 37 | delta.updates[0].values[0].path.should.equal( 38 | 'environment.depth.belowTransducer' 39 | ) 40 | delta.updates[0].values[0].value.should.equal(4.1) 41 | }) 42 | 43 | it('Converts OK with positive offset', () => { 44 | const delta = new Parser().parse('$IIDPT,4.1,1.0*44') 45 | 46 | delta.updates[0].values.should.contain.an.item.with.property( 47 | 'path', 48 | 'environment.depth.belowTransducer' 49 | ) 50 | delta.updates[0].values.should.contain.an.item.with.property('value', 4.1) 51 | 52 | delta.updates[0].values[1].path.should.equal( 53 | 'environment.depth.surfaceToTransducer' 54 | ) 55 | delta.updates[0].values[1].value.should.equal(1) 56 | 57 | delta.updates[0].values[2].path.should.equal( 58 | 'environment.depth.belowSurface' 59 | ) 60 | delta.updates[0].values[2].value.should.equal(5.1) 61 | }) 62 | 63 | it('Converts OK with negative offset', () => { 64 | const delta = new Parser().parse('$IIDPT,4.1,-1.0*69') 65 | 66 | delta.updates[0].values[0].path.should.equal( 67 | 'environment.depth.belowTransducer' 68 | ) 69 | delta.updates[0].values[0].value.should.be.closeTo(4.1, 0.1) 70 | 71 | delta.updates[0].values[1].path.should.equal( 72 | 'environment.depth.transducerToKeel' 73 | ) 74 | delta.updates[0].values[1].value.should.equal(1) 75 | 76 | delta.updates[0].values[2].path.should.equal('environment.depth.belowKeel') 77 | delta.updates[0].values[2].value.should.be.closeTo(3.1, 0.1) 78 | }) 79 | 80 | it('Converts empty depth to null', () => { 81 | const delta = new Parser().parse('$IIDPT,,,*6C') 82 | delta.updates[0].values[0].path.should.equal( 83 | 'environment.depth.belowTransducer' 84 | ) 85 | delta.updates[0].values.length.should.equal(1) 86 | should.equal(delta.updates[0].values[0].value, null) 87 | }) 88 | 89 | it('Converts empty depth to null and still outputs offset', () => { 90 | const delta = new Parser().parse('$IIDPT,,0.1*6F') 91 | delta.updates[0].values.length.should.equal(2) 92 | delta.updates[0].values[0].path.should.equal( 93 | 'environment.depth.belowTransducer' 94 | ) 95 | should.equal(delta.updates[0].values[0].value, null) 96 | delta.updates[0].values[1].path.should.equal( 97 | 'environment.depth.surfaceToTransducer' 98 | ) 99 | should.equal(delta.updates[0].values[1].value, 0.1) 100 | }) 101 | }) 102 | -------------------------------------------------------------------------------- /test/PBVE.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2019 Signal K and contributors. 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 Parser = require('../lib') 18 | const should = require('chai').Should() 19 | const toFull = require('./toFull') 20 | 21 | describe('PBVE', () => { 22 | it('Converts engine oil pressure using individual parser', () => { 23 | const delta = new Parser().parse( 24 | '$PBVE,DGOIADNNACAEACAAABBLAAEBAACMCFAAEPAIKI*37' 25 | ) 26 | delta.updates[0].values.should.contain.an.item.with.property( 27 | 'path', 28 | 'propulsion.0.oilPressure' 29 | ) 30 | delta.updates[0].values[0].value.should.equal(255106.009) 31 | delta.updates[0].values[0].meta.should.deep.equal({ 32 | description: 'CruzPro OP30/OP60 Engine Oil Pressure Gauge', 33 | units: 'pa', 34 | displayName: 'Engine Oil Pressure', 35 | shortName: 'EOP', 36 | warnMethod: ['visual'], 37 | alarmMethod: ['sound'], 38 | gaugeAlarmOn: true, 39 | backlight: 2, 40 | originalValue: 37, 41 | gaugeUnits: 'psi', 42 | zones: [ 43 | { 44 | lower: 186158.43899999998, 45 | state: 'alarm', 46 | message: 'Engine oil pressure at lowest threshold', 47 | }, 48 | { 49 | upper: 448159.20499999996, 50 | state: 'alarm', 51 | message: 'Engine oil pressure at highest threshold', 52 | }, 53 | ], 54 | }) 55 | 56 | toFull(delta).should.be.validSignalK 57 | }) 58 | it('Converts engine coolant temperature using individual parser', () => { 59 | const delta = new Parser().parse( 60 | '$PBVE,EDOIADOKACABABAAAACAPPCMABCGADABDOAEGL*20' 61 | ) 62 | delta.updates[0].values.should.contain.an.item.with.property( 63 | 'path', 64 | 'propulsion.0.coolantTemperature' 65 | ) 66 | delta.updates[0].values[0].value.should.equal(399.26111111111106) 67 | delta.updates[0].values[0].meta.should.deep.equal({ 68 | description: 'CruzPro T30/T60 Engine Coolant Temperature Gauge', 69 | units: 'k', 70 | displayName: 'Engine Coolant Temperature', 71 | shortName: 'ECT', 72 | warnMethod: ['visual'], 73 | alarmMethod: ['sound'], 74 | gaugeAlarmOn: false, 75 | backlight: 2, 76 | originalValue: 259, 77 | gaugeUnits: 'f', 78 | zones: [ 79 | { 80 | lower: 2406.4833333333336, 81 | state: 'alarm', 82 | message: 'Engine coolant temperature at lowest threshold', 83 | }, 84 | { 85 | upper: 279.81666666666666, 86 | state: 'alarm', 87 | message: 'Engine coolant temperature at highest threshold', 88 | }, 89 | ], 90 | }) 91 | 92 | toFull(delta).should.be.validSignalK 93 | }) 94 | 95 | it('Should not parse if product code is F or A', () => { 96 | should.not.exist(new Parser().parse('$PBVE,FGLABCGJAAAHADEE*21')) 97 | // should.not.exist( 98 | // new Parser().parse( 99 | // '$PBVE,BEAAADAAABFKBCIIBDAGOOABAAAAADAAAAOIACAAONFMAKCAADAAAAHG*2A' 100 | // ) 101 | // ) 102 | should.not.exist( 103 | new Parser().parse( 104 | '$PBVE,AQHKAAAACIAAAAABAAGEDOBCAAAAFLABCKAADIAADIAAAAAANDEN*3F' 105 | ) 106 | ) 107 | }) 108 | }) 109 | -------------------------------------------------------------------------------- /hooks/RMB.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | const moment = require('moment-timezone') 21 | 22 | /* 23 | RMB Sentence 24 | $GPRMB,A,0.66,L,003,004,4917.24,N,12309.57,W,001.3,052.5,000.5,V*20 25 | values: 26 | 27 | - RMB Recommended minimum navigation information 28 | [0] A Data status A = OK, V = Void (warning) 29 | [1][2] 0.66,L Cross-track error (nautical miles, 9.99 max), 30 | steer Left to correct (or R = right) 31 | [3] 003 Origin waypoint ID 32 | [4] 004 Destination waypoint ID 33 | [5][6] 4917.24,N Destination waypoint latitude 49 deg. 17.24 min. N 34 | [7][8] 12309.57,W Destination waypoint longitude 123 deg. 09.57 min. W 35 | [9] 001.3 Range to destination, nautical miles (999.9 max) 36 | [10] 052.5 True bearing to destination 37 | [11] 000.5 Velocity towards destination, knots 38 | [12] V Arrival alarm A = arrived, V = not arrived 39 | - *20 checksum 40 | 41 | */ 42 | 43 | module.exports = function (input) { 44 | const { id, sentence, parts, tags } = input 45 | 46 | let latitude = -1 47 | let longitude = -1 48 | let bearing = 0.0 49 | let vmg = 0.0 50 | let distance = 0.0 51 | let crossTrackError = 0.0 52 | let position = null 53 | 54 | latitude = utils.coordinate(parts[5], parts[6]) 55 | longitude = utils.coordinate(parts[7], parts[8]) 56 | if (isNaN(latitude) || isNaN(longitude)) { 57 | position = null 58 | } else { 59 | position = { 60 | longitude, 61 | latitude, 62 | } 63 | } 64 | 65 | bearing = utils.float(parts[10]) 66 | bearing = !isNaN(bearing) ? bearing : 0.0 67 | 68 | vmg = utils.float(parts[11]) 69 | vmg = !isNaN(vmg) && vmg > 0 ? vmg : 0.0 70 | 71 | distance = utils.float(parts[9]) 72 | distance = !isNaN(distance) ? distance : 0.0 73 | 74 | crossTrackError = utils.float(parts[1]) 75 | crossTrackError = !isNaN(crossTrackError) ? crossTrackError : 0.0 76 | 77 | crossTrackError = parts[2] == 'L' ? crossTrackError : -crossTrackError 78 | 79 | const delta = { 80 | updates: [ 81 | { 82 | source: tags.source, 83 | timestamp: tags.timestamp, 84 | values: [ 85 | { 86 | path: 'navigation.courseRhumbline.nextPoint.position', 87 | value: position, 88 | }, 89 | 90 | { 91 | path: 'navigation.courseRhumbline.nextPoint.bearingTrue', 92 | value: utils.transform(bearing, 'deg', 'rad'), 93 | }, 94 | 95 | { 96 | path: 'navigation.courseRhumbline.nextPoint.velocityMadeGood', 97 | value: utils.transform(vmg, 'knots', 'ms'), 98 | }, 99 | 100 | { 101 | path: 'navigation.courseRhumbline.nextPoint.distance', 102 | value: utils.transform(distance, 'nm', 'km') * 1000, 103 | }, 104 | 105 | { 106 | path: 'navigation.courseRhumbline.crossTrackError', 107 | value: utils.transform(crossTrackError, 'nm', 'km') * 1000, 108 | }, 109 | ], 110 | }, 111 | ], 112 | } 113 | 114 | return delta 115 | } 116 | -------------------------------------------------------------------------------- /hooks/RMC.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | const moment = require('moment-timezone') 21 | 22 | /* 23 | RMC Sentence 24 | http://www.gpsinformation.org/dale/nmea.htm#RMC 25 | $GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A 26 | values: 27 | - RMC Recommended Minimum sentence C 28 | [0] 123519 Fix taken at 12:35:19 UTC 29 | [1] A Status A=active or V=Void. 30 | [2][3] 4807.038,N Latitude 48 deg 07.038' N 31 | [4][5] 01131.000,E Longitude 11 deg 31.000' E 32 | [6] 022.4 Speed over the ground in knots 33 | [7] 084.4 Track angle in degrees True 34 | [8] 230394 Date - 23rd of March 1994 35 | [9][10] 003.1,W Magnetic Variation 36 | - *6A The checksum data, always begins with * 37 | */ 38 | 39 | module.exports = function (input) { 40 | const { id, sentence, parts, tags } = input 41 | 42 | let latitude = null 43 | let longitude = null 44 | let speed = null 45 | let track = null 46 | let variation = null 47 | 48 | const timestamp = utils.timestamp(parts[0], parts[8]) 49 | const age = moment.tz(timestamp, 'UTC').unix() 50 | 51 | latitude = 52 | parts[2].trim().length > 0 && !isNaN(parts[2]) && 'NS'.includes(parts[3]) 53 | ? utils.coordinate(parts[2], parts[3]) 54 | : null 55 | longitude = 56 | parts[4].trim().length > 0 && !isNaN(parts[4]) && 'EW'.includes(parts[5]) 57 | ? utils.coordinate(parts[4], parts[5]) 58 | : null 59 | 60 | speed = 61 | parts[6].trim().length > 0 && !isNaN(parts[6]) && parts[6] >= 0 62 | ? utils.transform(parts[6], 'knots', 'ms') 63 | : null 64 | 65 | track = 66 | parts[7].trim().length > 0 && !isNaN(parts[7]) 67 | ? utils.transform(parts[7], 'deg', 'rad') 68 | : null 69 | 70 | variation = 71 | parts[9].trim().length > 0 && !isNaN(parts[9]) && 'EW'.includes(parts[10]) 72 | ? utils.transform( 73 | utils.magneticVariaton(parts[9], parts[10]), 74 | 'deg', 75 | 'rad' 76 | ) 77 | : null 78 | 79 | let position = null 80 | 81 | if (utils.isValidPosition(latitude, longitude)) { 82 | position = { 83 | latitude: latitude, 84 | longitude: longitude, 85 | } 86 | } 87 | 88 | const delta = { 89 | updates: [ 90 | { 91 | source: tags.source, 92 | timestamp: timestamp, 93 | values: [ 94 | { 95 | path: 'navigation.position', 96 | value: position, 97 | }, 98 | { 99 | path: 'navigation.courseOverGroundTrue', 100 | value: track, 101 | }, 102 | { 103 | path: 'navigation.speedOverGround', 104 | value: speed, 105 | }, 106 | { 107 | path: 'navigation.magneticVariation', 108 | value: variation, 109 | }, 110 | { 111 | path: 'navigation.magneticVariationAgeOfService', 112 | value: age, 113 | }, 114 | { 115 | path: 'navigation.datetime', 116 | value: timestamp, 117 | }, 118 | ], 119 | }, 120 | ], 121 | } 122 | 123 | return delta 124 | } 125 | -------------------------------------------------------------------------------- /hooks/proprietary/PNKEP.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2018 Signal K and contributors. 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const debug = require('debug')('signalk-parser-nmea0183/PNKEP') 20 | const utils = require('@signalk/nmea0183-utilities') 21 | 22 | /* 23 | === PNKEP,01 - NKE Target speed === 24 | ------------------------------------------------------------------------------ 25 | 0 1 2 3 4 5 26 | | | | | | | 27 | $PNKEP,01,x.x,N,x.x,K*hh 28 | ------------------------------------------------------------------------------ 29 | $PNKEP,01,8.3,N,15.5,K*52 30 | Field Number: 31 | 0: NKE Target speed sentence 32 | 1: Target speed in knots 33 | 2: N = knots 34 | 3: Target speed in km/h 35 | 4: K 36 | 5: Checksum 37 | 38 | === PNKEP,02 - Course (COG) on next track === 39 | ------------------------------------------------------------------------------ 40 | 0 1 2 41 | | | | 42 | $PNKEP,02,x.x*hh 43 | ------------------------------------------------------------------------------ 44 | $PNKEP,02,344.4*6B 45 | Field Number: 46 | 0: NKE Course on next tack sentence 47 | 1: Course on next tack from 0 to 359 48 | 2: Checksum 49 | 50 | === PNKEP,03 - NKE Opt. VMG angle and performance up and downwind === 51 | ------------------------------------------------------------------------------ 52 | 0 1 2 3 4 53 | | | | | | 54 | $PNKEP,03,x.x,x.x,x.x*hh 55 | ------------------------------------------------------------------------------ 56 | $PNKEP,03,152.0,55.2,67.1*69 57 | Field Number: 58 | 0: NKE Opt. VMG angle 59 | 1: Opt. VMG angle 60 | 2: performance upwind from 0 to 99% - ignored 61 | 3: performance downwind from 0 to 99% - ignored 62 | 4: Checksum 63 | */ 64 | 65 | function isEmpty(mixed) { 66 | return ( 67 | (typeof mixed !== 'string' && typeof mixed !== 'number') || 68 | (typeof mixed === 'string' && mixed.trim() === '') 69 | ) 70 | } 71 | 72 | module.exports = function (input) { 73 | const { id, sentence, parts, tags } = input 74 | let values = [] 75 | let delta = {} 76 | 77 | //PNKEP,01 78 | if (parts[0] === '01') { 79 | if (parts[1] === '' && parts[3] === '') { 80 | return null 81 | } 82 | 83 | let targetspeed = 0.0 84 | 85 | if (utils.float(parts[3]) > 0 && String(parts[4]).toUpperCase() === 'K') { 86 | targetspeed = utils.transform(utils.float(parts[3]), 'kph', 'ms') 87 | } 88 | 89 | if (utils.float(parts[1]) > 0 && String(parts[2]).toUpperCase() === 'N') { 90 | targetspeed = utils.transform(utils.float(parts[1]), 'knots', 'ms') 91 | } 92 | values.push({ 93 | path: 'performance.targetSpeed', 94 | value: targetspeed, 95 | }) 96 | } 97 | 98 | // PNKEP,02 99 | if (parts[0] === '02') { 100 | if (parts[1] === '') { 101 | return null 102 | } 103 | 104 | let nxtcourse = 0.0 105 | 106 | if (utils.float(parts[1]) > 0) { 107 | nxtcourse = utils.transform(utils.float(parts[1]), 'deg', 'rad') 108 | } 109 | 110 | values.push({ 111 | path: 'performance.tackMagnetic', 112 | value: nxtcourse, 113 | }) 114 | } 115 | 116 | // PNKEP,03 117 | if (parts[0] === '03') { 118 | if (parts[1] === '') { 119 | return null 120 | } 121 | 122 | let optcourse = 0.0 123 | 124 | if (utils.float(parts[1]) > 0) { 125 | optcourse = utils.transform(utils.float(parts[1]), 'deg', 'rad') 126 | } 127 | 128 | values.push({ 129 | path: 'performance.targetAngle', 130 | value: optcourse, 131 | }) 132 | } 133 | 134 | delta = { 135 | updates: [ 136 | { 137 | source: tags.source, //this.source(input.instrument), 138 | timestamp: tags.timestamp, 139 | values: values, 140 | }, 141 | ], 142 | } 143 | 144 | return delta 145 | } 146 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | /** 4 | * Copyright 2016 Signal K and Fabian Tollenaar . 5 | * 6 | * Licensed under the Apache License, Version 2.0 (the "License"); 7 | * you may not use this file except in compliance with the License. 8 | * You may obtain a copy of the License at 9 | * 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * 12 | * Unless required by applicable law or agreed to in writing, software 13 | * distributed under the License is distributed on an "AS IS" BASIS, 14 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | * See the License for the specific language governing permissions and 16 | * limitations under the License. 17 | */ 18 | 19 | const getTagBlock = require('./getTagBlock') 20 | const transformSource = require('./transformSource') 21 | const utils = require('@signalk/nmea0183-utilities') 22 | const defaultHooks = require('../hooks') 23 | const pkg = require('../package.json') 24 | const debug = require('debug')('signalk-parser-nmea0183') 25 | 26 | class Parser { 27 | constructor( 28 | opts = { 29 | emitPropertyValue: () => undefined, 30 | onPropertyValues: () => undefined, 31 | } 32 | ) { 33 | this.options = typeof opts === 'object' && opts !== null ? opts : {} 34 | if (!Object.keys(this.options).includes('validateChecksum')) { 35 | this.options.validateChecksum = true 36 | } 37 | this.session = {} 38 | 39 | this.name = pkg.name 40 | this.version = pkg.version 41 | this.author = pkg.author 42 | this.license = pkg.license 43 | this.hooks = { ...defaultHooks } 44 | 45 | opts.onPropertyValues && 46 | opts.onPropertyValues('nmea0183sentenceParser', (propertyValues_) => { 47 | if (propertyValues_ === undefined) { 48 | return 49 | } 50 | const propValues = propertyValues_ 51 | .filter((v) => v) 52 | .map((propValue) => propValue.value) 53 | .filter(isValidSentenceParserEntry) 54 | .map(({ sentence, parser }) => { 55 | debug(`setting custom parser ${sentence}`) 56 | this.hooks[sentence] = parser 57 | }) 58 | }) 59 | } 60 | 61 | parse(sentence) { 62 | let tags = getTagBlock(sentence) 63 | 64 | if (tags !== false) { 65 | sentence = tags.sentence 66 | tags = tags.tags 67 | } else { 68 | tags = {} 69 | } 70 | 71 | let valid = utils.valid(sentence, this.options.validateChecksum) 72 | 73 | if (valid === false) { 74 | throw new Error(`Sentence "${sentence.trim()}" is invalid`) 75 | } 76 | 77 | if (sentence.charCodeAt(sentence.length - 1) === 10) { 78 | // in case there's a newline 79 | sentence = sentence.substr(0, sentence.length - 1) 80 | } 81 | 82 | const data = sentence.split('*')[0] 83 | const dataParts = data.split(',') 84 | let id = '' 85 | let talker = '' 86 | let internalId = '' 87 | 88 | if (dataParts[0].charAt(1).toUpperCase() === 'P') { 89 | // proprietary sentence 90 | id = dataParts[0].substr(-3, dataParts[0].length).toUpperCase() 91 | talker = dataParts[0].substr(1, 2).toUpperCase() 92 | internalId = dataParts[0].substr(1, dataParts[0].length) 93 | } else { 94 | id = dataParts[0].substr(3, dataParts[0].length).toUpperCase() 95 | talker = dataParts[0].substr(1, 2) 96 | internalId = dataParts[0].substr(3, 3).toUpperCase() 97 | } 98 | const split = dataParts.slice(1, dataParts.length) 99 | 100 | if (typeof tags.source === 'undefined') { 101 | tags.source = ':' 102 | } else { 103 | tags.source = `${tags.source}:${id}` 104 | } 105 | 106 | if (typeof this.hooks[internalId] === 'function') { 107 | const result = this.hooks[internalId]( 108 | { 109 | id, 110 | sentence, 111 | parts: split, 112 | tags, 113 | talker 114 | }, 115 | this.session 116 | ) 117 | return transformSource(result, id, talker) 118 | } else { 119 | return null 120 | } 121 | } 122 | } 123 | 124 | function isValidSentenceParserEntry(entry) { 125 | const isValid = 126 | typeof entry.sentence === 'string' && typeof entry.parser === 'function' 127 | if (!isValid) { 128 | console.error(`Invalid sentence parser entry:${JSON.stringify(entry)}`) 129 | } 130 | return isValid 131 | } 132 | 133 | module.exports = Parser 134 | -------------------------------------------------------------------------------- /hooks/MDA.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const utils = require('@signalk/nmea0183-utilities') 20 | 21 | /* 22 | * MDA - Meteorological Composite 23 | * 24 | * 1 2 3 4 5 6 7 8 9 10 11 12 25 | * | | | | | | | | | | | | 26 | * $--MDA,x.x,I,x.x,B,x.x,C,x.x,C,x.x,x.x,x.x,C,x.x,T,x.x,M,x.x,N,x.x,M*hh 27 | * Field Number: 28 | * 1. Barometric pressure, inches of mercury 29 | * 2. Barometric pressure, bars 30 | * 3. Air temperature, deg Celsius 31 | * 4. Water temperature, deg Celsius 32 | * 5. Relative humidity, percent 33 | * 6. Absolute humidity, percent <-- absolute is usually density, but NMEA probably using less common mass water per mass atmosphere formulation 34 | * 7. Dew point, deg Celsius 35 | * 8. Wind direction, degress True 36 | * 9. Wind direction, degress Magnetic 37 | * 10. Wind speed, knots 38 | * 11. Wind speed, m/s 39 | * 12. Checksum 40 | */ 41 | 42 | module.exports = function (input) { 43 | const { id, sentence, parts, tags } = input 44 | 45 | const values = [] 46 | 47 | // make SI units override any non-SI units 48 | if (parts[0] !== '') { 49 | values.push({ 50 | path: 'environment.outside.pressure', 51 | value: 3386.3886666667 * utils.float(parts[0]), // converting inHg -> Pa (SI units) 52 | }) 53 | } 54 | if (parts[2] !== '') { 55 | values.push({ 56 | path: 'environment.outside.pressure', 57 | value: utils.float(parts[2]) * 100000.0, // converting from bars to Pa (SI units) 58 | }) 59 | } 60 | if (parts[4] !== '') { 61 | values.push({ 62 | path: 'environment.outside.temperature', 63 | value: utils.transform(utils.float(parts[4]), 'c', 'k'), // transform units Celsius to Kelvin (stick to SI units) 64 | }) 65 | } 66 | if (parts[6] !== '') { 67 | values.push({ 68 | path: 'environment.water.temperature', 69 | value: utils.transform(utils.float(parts[6]), 'c', 'k'), // transform units Celsius to Kelvin (stick to SI units) 70 | }) 71 | } 72 | if (parts[8] !== '') { 73 | values.push({ 74 | path: 'environment.outside.humidity', 75 | value: utils.float(parts[8]) / 100.0, // converting from precentage to fraction 76 | }) 77 | } 78 | if (parts[9] !== '') { 79 | values.push({ 80 | path: 'environment.outside.humidityAbsolute', 81 | value: utils.float(parts[9]) / 100.0, // NMEA docs suggest this is a fraction/percentage, so probably they mean mass water per mass atmosphere formulation 82 | }) 83 | } 84 | if (parts[10] !== '') { 85 | values.push({ 86 | path: 'environment.outside.dewPointTemperature', 87 | value: utils.transform(utils.float(parts[10]), 'c', 'k'), 88 | }) 89 | } 90 | if (parts[12] !== '') { 91 | values.push({ 92 | path: 'environment.wind.directionTrue', 93 | value: utils.transform(utils.float(parts[12]), 'deg', 'rad'), 94 | }) 95 | } 96 | if (parts[14] !== '') { 97 | values.push({ 98 | path: 'environment.wind.directionMagnetic', 99 | value: utils.transform(utils.float(parts[14]), 'deg', 'rad'), 100 | }) 101 | } 102 | if (parts[16] !== '') { 103 | values.push({ 104 | path: 'environment.wind.speedOverGround', 105 | value: utils.transform(utils.float(parts[16]), 'knots', 'ms'), 106 | }) 107 | } 108 | if (parts[18] !== '') { 109 | values.push({ 110 | path: 'environment.wind.speedOverGround', 111 | value: utils.float(parts[18]), 112 | }) 113 | } 114 | 115 | const delta = { 116 | updates: [ 117 | { 118 | source: tags.source, 119 | timestamp: tags.timestamp, 120 | values: values, 121 | }, 122 | ], 123 | } 124 | 125 | return delta 126 | } 127 | -------------------------------------------------------------------------------- /test/MWD.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Signal K and contributors. 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 | 'use strict' 18 | 19 | const Parser = require('../lib') 20 | const chai = require('chai') 21 | const should = chai.Should() 22 | 23 | chai.use(require('chai-things')) 24 | 25 | describe('MWD', () => { 26 | it('speed & direction data (#1)', () => { 27 | const delta = new Parser().parse('$IIMWD,,,046.,M,10.1,N,05.2,M*0B') 28 | 29 | delta.updates[0].values[0].path.should.equal( 30 | 'environment.wind.directionMagnetic' 31 | ) 32 | delta.updates[0].values[0].value.should.be.closeTo(0.802851, 0.00005) 33 | delta.updates[0].values[1].path.should.equal('environment.wind.speedTrue') 34 | delta.updates[0].values[1].value.should.equal(5.2) 35 | }) 36 | 37 | it('speed & direction data (#2)', () => { 38 | const delta = new Parser().parse('$IIMWD,046.,T,046.,M,10.1,N,,*17') 39 | 40 | delta.updates[0].values[0].path.should.equal( 41 | 'environment.wind.directionTrue' 42 | ) 43 | delta.updates[0].values[0].value.should.be.closeTo(0.802851, 0.00005) 44 | delta.updates[0].values[1].path.should.equal( 45 | 'environment.wind.directionMagnetic' 46 | ) 47 | delta.updates[0].values[1].value.should.be.closeTo(0.802851, 0.00005) 48 | delta.updates[0].values[2].path.should.equal('environment.wind.speedTrue') 49 | delta.updates[0].values[2].value.should.be.closeTo(5.2, 0.005) 50 | }) 51 | 52 | it('speed & direction data (#3)', () => { 53 | const delta = new Parser().parse('$IIMWD,046.,T,,,,,5.2,M*72') 54 | 55 | delta.updates[0].values[0].path.should.equal( 56 | 'environment.wind.directionTrue' 57 | ) 58 | delta.updates[0].values[0].value.should.be.closeTo(0.802851, 0.00005) 59 | delta.updates[0].values[1].path.should.equal('environment.wind.speedTrue') 60 | delta.updates[0].values[1].value.should.be.equal(5.2) 61 | }) 62 | 63 | it('missing direction data', () => { 64 | const delta = new Parser().parse('$IIMWD,,,,,,,5.2,M*3A') 65 | 66 | should.equal(delta, null) 67 | }) 68 | 69 | it('missing speed data', () => { 70 | const delta = new Parser().parse('$IIMWD,,,046.,M,,,,*0F') 71 | 72 | should.equal(delta, null) 73 | }) 74 | 75 | it('improper direction designator (#1)', () => { 76 | const delta = new Parser().parse('$IIMWD,,,046.,T,,,,*16') 77 | 78 | should.equal(delta, null) 79 | }) 80 | 81 | it('improper direction designator (#2)', () => { 82 | const delta = new Parser().parse('$IIMWD,046.,M,,,,,,*0F') 83 | 84 | should.equal(delta, null) 85 | }) 86 | 87 | it('improper speed designator (#1)', () => { 88 | const delta = new Parser().parse('$IIMWD,,,046.,M,10.1,n,,*7F') 89 | 90 | should.equal(delta, null) 91 | }) 92 | 93 | it('improper speed designator (#2)', () => { 94 | const delta = new Parser().parse('$IIMWD,,,046.,M,,,0.0,m*4C') 95 | 96 | should.equal(delta, null) 97 | }) 98 | 99 | it('improper direction designator for degrees magnetic, using degrees true', () => { 100 | const delta = new Parser().parse('$IIMWD,046.,T,0.,m,10.1,N,5.2,M*51') 101 | 102 | delta.updates[0].values[0].path.should.equal( 103 | 'environment.wind.directionTrue' 104 | ) 105 | delta.updates[0].values[0].value.should.be.closeTo(0.802851, 0.00005) 106 | delta.updates[0].values[1].path.should.equal('environment.wind.speedTrue') 107 | delta.updates[0].values[1].value.should.equal(5.2) 108 | }) 109 | 110 | it('improper speed designator for m/s, using speed in kn', () => { 111 | const delta = new Parser().parse('$IIMWD,,,046.,M,10.1,N,0.0,m*1C') 112 | 113 | delta.updates[0].values[0].path.should.equal( 114 | 'environment.wind.directionMagnetic' 115 | ) 116 | delta.updates[0].values[0].value.should.be.closeTo(0.802851, 0.00005) 117 | delta.updates[0].values[1].path.should.equal('environment.wind.speedTrue') 118 | delta.updates[0].values[1].value.should.be.closeTo(5.2, 0.005) 119 | }) 120 | }) 121 | --------------------------------------------------------------------------------