init commit
commit
94f58cd552
|
@ -0,0 +1,33 @@
|
||||||
|
# This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node
|
||||||
|
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions
|
||||||
|
|
||||||
|
name: Node.js CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ master ]
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [10.x, 12.x, 14.x, 15.x]
|
||||||
|
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
- run: npm install
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm run build --if-present
|
||||||
|
- run: npm test
|
|
@ -0,0 +1,51 @@
|
||||||
|
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
|
||||||
|
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages
|
||||||
|
|
||||||
|
name: Node.js Package
|
||||||
|
|
||||||
|
on:
|
||||||
|
release:
|
||||||
|
types: [created]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 12
|
||||||
|
- run: npm install
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm test
|
||||||
|
|
||||||
|
publish-npm:
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment: release
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v1
|
||||||
|
with:
|
||||||
|
node-version: 12
|
||||||
|
registry-url: https://registry.npmjs.org/
|
||||||
|
- run: npm install
|
||||||
|
- run: npm ci
|
||||||
|
- run: npm publish
|
||||||
|
env:
|
||||||
|
NODE_AUTH_TOKEN: ${{ secrets.npm_token }}
|
||||||
|
|
||||||
|
# publish-gpr:
|
||||||
|
# needs: build
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# steps:
|
||||||
|
# - uses: actions/checkout@v2
|
||||||
|
# - uses: actions/setup-node@v1
|
||||||
|
# with:
|
||||||
|
# node-version: 12
|
||||||
|
# registry-url: https://npm.pkg.github.com/
|
||||||
|
# - run: npm ci
|
||||||
|
# - run: npm publish
|
||||||
|
# env:
|
||||||
|
# NODE_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}}
|
|
@ -0,0 +1,2 @@
|
||||||
|
.DS_Store
|
||||||
|
node_modules
|
|
@ -0,0 +1,20 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2013
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
the Software without restriction, including without limitation the rights to
|
||||||
|
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
||||||
|
the Software, and to permit persons to whom the Software is furnished to do so,
|
||||||
|
subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||||
|
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@ -0,0 +1,45 @@
|
||||||
|
A NMEA-0183 GPS Protocol parser
|
||||||
|
===============================
|
||||||
|
|
||||||
|
An example using the node-serialport library to read a stream of messages
|
||||||
|
from a GlobalSat BU-353 USB GPS receiver:
|
||||||
|
|
||||||
|
````
|
||||||
|
var serialport = require('serialport');
|
||||||
|
var nmea = require('nmea');
|
||||||
|
|
||||||
|
var port = new serialport.SerialPort('/dev/cu.usbserial', {
|
||||||
|
baudrate: 4800,
|
||||||
|
parser: serialport.parsers.readline('\r\n')});
|
||||||
|
|
||||||
|
port.on('data', function(line) {
|
||||||
|
console.log(nmea.parse(line));
|
||||||
|
});
|
||||||
|
|
||||||
|
// { type: 'active-satellites',
|
||||||
|
// selectionMode: 'A',
|
||||||
|
// mode: 1,
|
||||||
|
// satellites: [ 29, 18, 21 ],
|
||||||
|
// PDOP: '',
|
||||||
|
// HDOP: '',
|
||||||
|
// VDOP: '',
|
||||||
|
// talker_id: 'GP' }
|
||||||
|
// { type: 'satellite-list-partial',
|
||||||
|
// numMsgs: 3,
|
||||||
|
// msgNum: 1,
|
||||||
|
// satsInView: 11,
|
||||||
|
// satellites:
|
||||||
|
// [ { id: '18', elevationDeg: 7, azimuthTrue: 214, SNRdB: 43 },
|
||||||
|
// { id: '21', elevationDeg: 5, azimuthTrue: 114, SNRdB: 34 },
|
||||||
|
// { id: '26', elevationDeg: 71, azimuthTrue: 234, SNRdB: 0 } ],
|
||||||
|
// talker_id: 'GP' }
|
||||||
|
|
||||||
|
````
|
||||||
|
|
||||||
|
To add custom codecs
|
||||||
|
====================
|
||||||
|
````
|
||||||
|
var MyCustom = require('./MyCustom.js');
|
||||||
|
nmea.traditionalDecoders['MyCustomr'] = MyCustom.decode;
|
||||||
|
````
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var Liner = require('../liner.js');
|
||||||
|
|
||||||
|
process.stdin.resume();
|
||||||
|
process.stdin.setEncoding('utf8');
|
||||||
|
|
||||||
|
process.stdin.pipe(new Liner()).pipe(require('../nmea.js').createDefaultTransformer()).pipe(require('JSONStream').stringify(false)).pipe(process.stdout);;
|
|
@ -0,0 +1,58 @@
|
||||||
|
exports.ID = 'APB';
|
||||||
|
exports.TYPE = 'autopilot-b';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
/*
|
||||||
|
=== APB - Autopilot Sentence "B" ===
|
||||||
|
|
||||||
|
13 15
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3 4 5 6 7 8 9 10 11 12| 14|
|
||||||
|
| | | | | | | | | | | | | | |
|
||||||
|
$--APB,A,A,x.x,a,N,A,A,x.x,a,c--c,x.x,a,x.x,a*hh<CR><LF>
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1. Status
|
||||||
|
V = LORAN-C Blink or SNR warning
|
||||||
|
V = general warning flag or other navigation systems when a reliable
|
||||||
|
fix is not available
|
||||||
|
2. Status
|
||||||
|
V = Loran-C Cycle Lock warning flag
|
||||||
|
A = OK or not used
|
||||||
|
3. Cross Track Error Magnitude
|
||||||
|
4. Direction to steer, L or R
|
||||||
|
5. Cross Track Units, N = Nautical Miles
|
||||||
|
6. Status
|
||||||
|
A = Arrival Circle Entered
|
||||||
|
7. Status
|
||||||
|
A = Perpendicular passed at waypoint
|
||||||
|
8. Bearing origin to destination
|
||||||
|
9. M = Magnetic, T = True
|
||||||
|
10. Destination Waypoint ID
|
||||||
|
11. Bearing, present position to Destination
|
||||||
|
12. M = Magnetic, T = True
|
||||||
|
13. Heading to steer to destination waypoint
|
||||||
|
14. M = Magnetic, T = True
|
||||||
|
15. Checksum
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
status1 : fields[1],
|
||||||
|
status2 : fields[2],
|
||||||
|
xteMagn : +fields[3],
|
||||||
|
steerDir : fields[4],
|
||||||
|
xteUnit : fields[5],
|
||||||
|
arrivalCircleStatus : fields[6],
|
||||||
|
arrivalPerpendicularStatus : fields[7],
|
||||||
|
bearingOrig2Dest : +fields[8],
|
||||||
|
bearingOrig2DestType : fields[9],
|
||||||
|
waypoint : fields[10],
|
||||||
|
bearing2Dest : +fields[11],
|
||||||
|
bearingDestType : fields[12],
|
||||||
|
heading2steer : +fields[13],
|
||||||
|
headingDestType : fields[14]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
=== BWC - Bearing & Distance to Waypoint - Great Circle ===
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
12
|
||||||
|
1 2 3 4 5 6 7 8 9 10 11| 13 14
|
||||||
|
| | | | | | | | | | | | | |
|
||||||
|
$--BEC,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x.x,T,x.x,M,x.x,N,c--c,m,*hh<CR><LF>
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
Field Number:
|
||||||
|
1. UTCTime
|
||||||
|
2. Waypoint Latitude
|
||||||
|
3. N = North, S = South
|
||||||
|
4. Waypoint Longitude
|
||||||
|
5. E = East, W = West
|
||||||
|
6. Bearing, True
|
||||||
|
7. T = True
|
||||||
|
8. Bearing, Magnetic
|
||||||
|
9. M = Magnetic
|
||||||
|
10. Nautical Miles
|
||||||
|
11. N = Nautical Miles
|
||||||
|
12. Waypoint ID
|
||||||
|
13. FAA mode indicator (NMEA 2.3 and later, optional)
|
||||||
|
14. Checksum
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.ID = 'BWC';
|
||||||
|
exports.TYPE = '2waypoint';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
lat: +fields[2],
|
||||||
|
latPole: fields[3],
|
||||||
|
lon: +fields[4],
|
||||||
|
lonPole: fields[5],
|
||||||
|
bearingtrue: +fields[6],
|
||||||
|
bearingmag: +fields[8],
|
||||||
|
distance: +fields[10],
|
||||||
|
id: fields[12]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
var helpers = require("../helpers.js")
|
||||||
|
|
||||||
|
/*
|
||||||
|
=== DBT - Depth below transducer ===
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
*******1 2 3 4 5 6 7
|
||||||
|
*******| | | | | | |
|
||||||
|
$--DBT,x.x,f,x.x,M,x.x,F*hh<CR><LF>
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1. Depth, feet
|
||||||
|
2. f = feet
|
||||||
|
3. Depth, meters
|
||||||
|
4. M = meters
|
||||||
|
5. Depth, Fathoms
|
||||||
|
6. F = Fathoms
|
||||||
|
7. Checksum
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.TYPE = 'depth-transducer';
|
||||||
|
exports.ID = 'DBT';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
depthMeters: +fields[3],
|
||||||
|
depthFeet: +fields[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.encode = function (talker, msg) {
|
||||||
|
var result = ['$' + talker + exports.ID];
|
||||||
|
result.push(helpers.encodeFixed(msg.depthFeet,2));
|
||||||
|
result.push('f');
|
||||||
|
result.push(helpers.encodeFixed(msg.depthMeters, 2));
|
||||||
|
result.push('M');
|
||||||
|
result.push(helpers.encodeFixed(msg.depthFathoms, 2));
|
||||||
|
result.push('F');
|
||||||
|
var resultMsg = result.join(',');
|
||||||
|
return resultMsg + helpers.computeChecksum(resultMsg);
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
var helpers = require("../helpers.js")
|
||||||
|
|
||||||
|
exports.TYPE = 'fix';
|
||||||
|
exports.ID = 'GGA';
|
||||||
|
|
||||||
|
/*
|
||||||
|
11
|
||||||
|
1 2 3 4 5 6 7 8 9 10 | 12 13 14 15
|
||||||
|
| | | | | | | | | | | | | | |
|
||||||
|
$--GGA,hhmmss.ss,llll.ll,a,yyyyy.yy,a,x,xx,x.x,x.x,M,x.x,M,x.x,xxxx*hh
|
||||||
|
1) Time (UTC)
|
||||||
|
2) Latitude
|
||||||
|
3) N or S (North or South)
|
||||||
|
4) Longitude
|
||||||
|
5) E or W (East or West)
|
||||||
|
6) GPS Quality Indicator,
|
||||||
|
0 - fix not available,
|
||||||
|
1 - GPS fix,
|
||||||
|
2 - Differential GPS fix
|
||||||
|
7) Number of satellites in view, 00 - 12
|
||||||
|
8) Horizontal Dilution of precision
|
||||||
|
9) Antenna Altitude above/below mean-sea-level (geoid)
|
||||||
|
10) Units of antenna altitude, meters
|
||||||
|
11) Geoidal separation, the difference between the WGS-84 earth
|
||||||
|
ellipsoid and mean-sea-level (geoid), "-" means mean-sea-level below ellipsoid
|
||||||
|
12) Units of geoidal separation, meters
|
||||||
|
13) Age of differential GPS data, time in seconds since last SC104
|
||||||
|
type 1 or 9 update, null field when DGPS is not used
|
||||||
|
14) Differential reference station ID, 0000-1023
|
||||||
|
15) Checksum
|
||||||
|
*/
|
||||||
|
var FIX_TYPE = ['none', 'fix', 'delta','pps','rtk','frtk','estimated','manual','simulation'];
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
timestamp: fields[1],
|
||||||
|
lat: fields[2],
|
||||||
|
latPole: fields[3],
|
||||||
|
lon: fields[4],
|
||||||
|
lonPole: fields[5],
|
||||||
|
fixType: FIX_TYPE[+fields[6]],
|
||||||
|
numSat: +fields[7],
|
||||||
|
horDilution: +fields[8],
|
||||||
|
alt: +fields[9],
|
||||||
|
altUnit: fields[10],
|
||||||
|
geoidalSep: +fields[11],
|
||||||
|
geoidalSepUnit: fields[12],
|
||||||
|
differentialAge: +fields[13],
|
||||||
|
differentialRefStn: fields[14]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.encode = function (talker, msg) {
|
||||||
|
var result = ['$' + talker + exports.ID];
|
||||||
|
result.push(helpers.encodeTime(msg.timestamp));
|
||||||
|
result.push(helpers.encodeDegrees(msg.lat));
|
||||||
|
result.push(msg.latPole);
|
||||||
|
result.push(helpers.encodeDegrees(msg.lon));
|
||||||
|
result.push(msg.lonPole);
|
||||||
|
result.push(FIX_TYPE.indexOf(msg.fixType));
|
||||||
|
result.push(helpers.encodeValue(msg.numSat));
|
||||||
|
result.push(helpers.encodeFixed(msg.horDilution, 1));
|
||||||
|
result.push(helpers.encodeAltitude(msg.alt));
|
||||||
|
result.push(helpers.encodeGeoidalSeperation(msg.geoidalSep));
|
||||||
|
result.push(helpers.encodeFixed(msg.differentialAge, 2));
|
||||||
|
result.push(msg.differentialRefStn);
|
||||||
|
|
||||||
|
var resultMsg = result.join(',');
|
||||||
|
return resultMsg + helpers.computeChecksum(resultMsg);
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
var helpers = require("../helpers.js")
|
||||||
|
|
||||||
|
/*
|
||||||
|
=== GLL - Geographic Position - Latitude/Longitude ===
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3 4 5 6 7 8
|
||||||
|
| | | | | | | |
|
||||||
|
$--GLL,llll.ll,a,yyyyy.yy,a,hhmmss.ss,a,m,*hh<CR><LF>
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1. Latitude
|
||||||
|
2. N or S (North or South)
|
||||||
|
3. Longitude
|
||||||
|
4. E or W (East or West)
|
||||||
|
5. Universal Time Coordinated (UTC)
|
||||||
|
6. Status A - Data Valid, V - Data Invalid
|
||||||
|
7. FAA mode indicator (NMEA 2.3 and later)
|
||||||
|
8. Checksum
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.TYPE = 'geo-position';
|
||||||
|
exports.ID = 'GLL';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: 'geo-position',
|
||||||
|
timestamp: fields[5],
|
||||||
|
lat: fields[1],
|
||||||
|
latPole: fields[2],
|
||||||
|
lon: fields[3],
|
||||||
|
lonPole: fields[4],
|
||||||
|
timestamp: fields[5],
|
||||||
|
status: fields[6] == 'A' ? 'valid' : 'invalid'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.encode = function (talker, msg) {
|
||||||
|
var result = ['$' + talker + exports.ID];
|
||||||
|
result.push(helpers.encodeDegrees(msg.lat));
|
||||||
|
result.push(msg.latPole);
|
||||||
|
result.push(helpers.encodeDegrees(msg.lon));
|
||||||
|
result.push(msg.lonPole);
|
||||||
|
result.push(helpers.encodeTime(msg.timestamp));
|
||||||
|
result.push('A');
|
||||||
|
result.push('D');
|
||||||
|
var resultMsg = result.join(',');
|
||||||
|
return resultMsg + helpers.computeChecksum(resultMsg);
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
exports.ID = 'GRMT';
|
||||||
|
exports.TYPE = 'sensor-information';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
/*
|
||||||
|
=== PGRMT - Garmin Proprietary Sensor Status Information ===
|
||||||
|
|
||||||
|
The Garmin Proprietary sentence $PGRMT gives information concerning the status of a GPS sensor. This
|
||||||
|
sentence is transmitted once per minute regardless of the selected baud rate.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3 4 5 6 7 8 9
|
||||||
|
| | | | | | | | |
|
||||||
|
$PGRMT,GPS19x-HVS Software Version 2.20,P,P,R,R,P,C,15,R*hh<CR><LF>
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1. Product, model and software version (variable length field, e.g., “GPS 10 SW VER 2.01 BT VER 1.27 764”)
|
||||||
|
2. ROM checksum test, P = pass, F = fail
|
||||||
|
3. Receiver failure discrete, P = pass, F = fail
|
||||||
|
4. Stored data lost, R = retained, L = lost
|
||||||
|
5. Real time clock lost, R = retained, L = lost
|
||||||
|
6. Oscillator drift discrete, P = pass, F = excessive drift detected
|
||||||
|
7. Data collection discrete, C = collecting, null if not collecting
|
||||||
|
8. GPS sensor temperature in degrees C
|
||||||
|
9. GPS sensor configuration data, R = retained, L = lost
|
||||||
|
|
||||||
|
Note: Some sensors have been seen to not provide all information above, in some cases just the product
|
||||||
|
model during boot. Example:
|
||||||
|
|
||||||
|
$PGRMT,GPS19x-HVS Software Version 2.20,,,,,,,,*6F
|
||||||
|
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
product : fields[1],
|
||||||
|
rom_checksum : fields[2],
|
||||||
|
receiver_failure : fields[3],
|
||||||
|
stored_data_lost : fields[4],
|
||||||
|
rtc_lost: fields[5],
|
||||||
|
oscillator_drift: fields[6],
|
||||||
|
data_collection: fields[7],
|
||||||
|
sensor_temperature: fields[8],
|
||||||
|
sensor_configuration: fields[9]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
/*
|
||||||
|
=== GSA - GPS DOP and active satellites ===
|
||||||
|
|
||||||
|
This is one of the sentences commonly emitted by GPS units.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3 4 14 15 16 17 18
|
||||||
|
| | | | | | | | |
|
||||||
|
$--GSA,a,a,x,x,x,x,x,x,x,x,x,x,x,x,x,x,x.x,x.x,x.x*hh
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1) Selection mode
|
||||||
|
2) Mode
|
||||||
|
3) ID of 1st satellite used for fix
|
||||||
|
4) ID of 2nd satellite used for fix
|
||||||
|
...
|
||||||
|
14) ID of 12th satellite used for fix
|
||||||
|
15) PDOP in meters
|
||||||
|
16) HDOP in meters
|
||||||
|
17) VDOP in meters
|
||||||
|
18) Checksum
|
||||||
|
*/
|
||||||
|
exports.TYPE = 'active-satellites';
|
||||||
|
exports.ID = 'GSA';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
// $GPGSA,A,3,12,05,25,29,,,,,,,,,9.4,7.6,5.6
|
||||||
|
var sats = [];
|
||||||
|
for (var i=1; i < 13; i++) {
|
||||||
|
if (fields[i+2]) sats.push(+fields[i+2]);
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
selectionMode: fields[1],
|
||||||
|
mode: +fields[2],
|
||||||
|
satellites: sats,
|
||||||
|
PDOP: +fields[15],
|
||||||
|
HDOP: +fields[16],
|
||||||
|
VDOP: +fields[17],
|
||||||
|
systemId: +fields[18]
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
exports.ID = 'GSV';
|
||||||
|
exports.TYPE = 'satellite-list-partial';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
// $GPGSV,3,1,12, 05,58,322,36, 02,55,032,, 26,50,173,, 04,31,085,
|
||||||
|
var numRecords = (fields.length - 4) / 4,
|
||||||
|
sats = [];
|
||||||
|
for (var i=0; i < numRecords; i++) {
|
||||||
|
var offset = i * 4 + 4;
|
||||||
|
sats.push({id: fields[offset],
|
||||||
|
elevationDeg: +fields[offset+1],
|
||||||
|
azimuthTrue: +fields[offset+2],
|
||||||
|
SNRdB: +fields[offset+3]});
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
numMsgs: +fields[1],
|
||||||
|
msgNum: +fields[2],
|
||||||
|
satsInView: +fields[3],
|
||||||
|
satellites: sats
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
var helpers = require("../helpers.js")
|
||||||
|
|
||||||
|
/*
|
||||||
|
=== HDG - Magnetic heading, deviation, variation ===
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3 4 5 6
|
||||||
|
| | | | | |
|
||||||
|
$--HDG,x.x,x.x,a,x.x,a*hh
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1) Magnetic Sensor heading in degrees
|
||||||
|
2) Magnetic Deviation, degrees
|
||||||
|
3) Magnetic Deviation direction, E = Easterly, W = Westerly
|
||||||
|
4) Magnetic Variation degrees
|
||||||
|
5) Magnetic Variation direction, E = Easterly, W = Westerly
|
||||||
|
6) Checksum
|
||||||
|
|
||||||
|
*/
|
||||||
|
exports.TYPE = 'heading-deviation-variation';
|
||||||
|
exports.ID = 'HDG';
|
||||||
|
|
||||||
|
exports.decode = function (fields) {
|
||||||
|
console.log(fields);
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: 'heading-deviation-variation',
|
||||||
|
heading: +fields[1],
|
||||||
|
deviation: +fields[2],
|
||||||
|
deviationDirection: fields[3],
|
||||||
|
variation: +fields[4],
|
||||||
|
variationDirection: fields[5]
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,34 @@
|
||||||
|
var helpers = require("../helpers.js")
|
||||||
|
/*
|
||||||
|
=== HDM - Heading - Magnetic ===
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3
|
||||||
|
| | |
|
||||||
|
$--HDM,x.x,M*hh
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1) Heading Degrees, magnetic
|
||||||
|
2) M = Magnetic
|
||||||
|
3) Checksum
|
||||||
|
*/
|
||||||
|
exports.TYPE = 'heading-info-magnetic';
|
||||||
|
exports.ID = 'HDM';
|
||||||
|
|
||||||
|
exports.decode = function (fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: 'heading-info-magnetic',
|
||||||
|
heading: +fields[1]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.encode = function (talker, msg) {
|
||||||
|
var result = ['$' + talker + exports.ID];
|
||||||
|
result.push(helpers.encodeFixed(msg.heading, 1));
|
||||||
|
result.push('M');
|
||||||
|
var resultMsg = result.join(',');
|
||||||
|
return resultMsg + helpers.computeChecksum(resultMsg);
|
||||||
|
};
|
|
@ -0,0 +1,34 @@
|
||||||
|
var helpers = require("../helpers.js")
|
||||||
|
/*
|
||||||
|
=== HDT - Heading - True ===
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3
|
||||||
|
| | |
|
||||||
|
$--HDT,x.x,T*hh
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1) Heading Degrees, true
|
||||||
|
2) T = True
|
||||||
|
3) Checksum
|
||||||
|
*/
|
||||||
|
exports.TYPE = 'heading-info';
|
||||||
|
exports.ID = 'HDT';
|
||||||
|
|
||||||
|
exports.decode = function (fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: 'heading-info',
|
||||||
|
heading: +fields[1]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.encode = function (talker, msg) {
|
||||||
|
var result = ['$' + talker + exports.ID];
|
||||||
|
result.push(helpers.encodeFixed(msg.heading, 1));
|
||||||
|
result.push('T');
|
||||||
|
var resultMsg = result.join(',');
|
||||||
|
return resultMsg + helpers.computeChecksum(resultMsg);
|
||||||
|
};
|
|
@ -0,0 +1,44 @@
|
||||||
|
var helpers = require("../helpers.js")
|
||||||
|
/*
|
||||||
|
=== MWV - Wind Speed and Angle ===
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
*******1 2 3 4 5
|
||||||
|
*******| | | | |
|
||||||
|
$--MWV,x.x,a,x.x,a*hh<CR><LF>
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1. Wind Angle, 0 to 360 degrees
|
||||||
|
2. Reference, R = Relative, T = True
|
||||||
|
3. Wind Speed
|
||||||
|
4. Wind Speed Units, K/M/N
|
||||||
|
5. Status, A = Data Valid
|
||||||
|
6. Checksum
|
||||||
|
*/
|
||||||
|
exports.TYPE = 'wind';
|
||||||
|
exports.ID = 'MWV';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
angle: +fields[1],
|
||||||
|
reference: fields[2],
|
||||||
|
speed: +fields[3],
|
||||||
|
units: fields[4],
|
||||||
|
status: fields[5]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.encode = function(talker, msg) {
|
||||||
|
var result = ['$' + talker + exports.ID];
|
||||||
|
result.push(helpers.encodeDegrees(msg.angle));
|
||||||
|
result.push(msg.reference);
|
||||||
|
result.push(helpers.encodeFixed(msg.speed, 2));
|
||||||
|
result.push(msg.units);
|
||||||
|
result.push(typeof msg.status === undefined ? 'A' : msg.status);
|
||||||
|
var resultMsg = result.join(',');
|
||||||
|
return resultMsg + helpers.computeChecksum(resultMsg);
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
exports.ID = 'RDID';
|
||||||
|
exports.TYPE = 'gyro';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
/*
|
||||||
|
=== PRDID - RDI Proprietary Heading, Pitch, Roll ===
|
||||||
|
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3 4
|
||||||
|
| | | |
|
||||||
|
$PRDID,-2.06,4.81,37.62*6D<CR><LF>
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1. Roll
|
||||||
|
2. Pitch
|
||||||
|
3. Heading
|
||||||
|
4. Checksum
|
||||||
|
*/
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
roll : +fields[1],
|
||||||
|
pitch : +fields[2],
|
||||||
|
heading : +fields[3],
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
=== RMB Recommended Minimum Navigation Information ===
|
||||||
|
|
||||||
|
To be sent by a navigation receiver when a destination waypoint is active.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
||||||
|
| | | | | | | | | | | | | |
|
||||||
|
$--RMB,A,x.x,a,c--c,c--c,llll.ll,a,yyyyy.yy,a,x.x,x.x,x.x,A*hh
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1) Status, V = Navigation receiver warning
|
||||||
|
2) Cross Track error - nautical miles
|
||||||
|
3) Direction to Steer, Left or Right
|
||||||
|
4) FROM Waypoint ID
|
||||||
|
5) TO Waypoint ID
|
||||||
|
6) Destination Waypoint Latitude
|
||||||
|
7) N or S
|
||||||
|
8) Destination Waypoint Longitude
|
||||||
|
9) E or W
|
||||||
|
10) Range to destination in nautical miles
|
||||||
|
11) Bearing to destination in degrees True
|
||||||
|
12) Destination closing velocity in knots
|
||||||
|
13) Arrival Status, A = Arrival Circle Entered
|
||||||
|
14) Checksum
|
||||||
|
|
||||||
|
*/
|
||||||
|
exports.TYPE = 'nav-info-waypoint';
|
||||||
|
exports.ID = 'RMB';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
status: fields[1] == 'V' ? 'warning' : 'valid',
|
||||||
|
crossTrackError: +fields[2],
|
||||||
|
steer: fields[3],
|
||||||
|
fromWaypoint: fields[4],
|
||||||
|
toWaypoint: fields[5],
|
||||||
|
lat: fields[6],
|
||||||
|
latPole: fields[7],
|
||||||
|
lon: fields[8],
|
||||||
|
lonPole: fields[9],
|
||||||
|
range: +fields[10],
|
||||||
|
bearing: +fields[11],
|
||||||
|
vmg: +fields[12],
|
||||||
|
arrived: fields[13] == 'A' ? true : false
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
/*
|
||||||
|
=== RMC - Recommended Minimum Navigation Information ===
|
||||||
|
|
||||||
|
This is one of the sentences commonly emitted by GPS units.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3 4 5 6 7 8 9 10 11 12
|
||||||
|
| | | | | | | | | | | |
|
||||||
|
$--RMC,hhmmss.ss,A,llll.ll,a,yyyyy.yy,a,x.x,x.x,xxxx,x.x,a*hh
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1) Time (UTC)
|
||||||
|
2) Status, V = Navigation receiver warning
|
||||||
|
3) Latitude
|
||||||
|
4) N or S
|
||||||
|
5) Longitude
|
||||||
|
6) E or W
|
||||||
|
7) Speed over ground, knots
|
||||||
|
8) Track made good, degrees true
|
||||||
|
9) Date, ddmmyy
|
||||||
|
10) Magnetic Variation, degrees
|
||||||
|
11) E or W
|
||||||
|
12) Checksum
|
||||||
|
*/
|
||||||
|
exports.TYPE = 'nav-info';
|
||||||
|
exports.ID = 'RMC';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
timestamp: fields[1],
|
||||||
|
status: fields[2] == 'V' ? 'warning' : 'valid',
|
||||||
|
lat: fields[3],
|
||||||
|
latPole: fields[4],
|
||||||
|
lon: fields[5],
|
||||||
|
lonPole: fields[6],
|
||||||
|
speedKnots: +fields[7],
|
||||||
|
trackTrue: +fields[8],
|
||||||
|
date: fields[9],
|
||||||
|
variation: +fields[10],
|
||||||
|
variationPole: fields[11]
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
var helpers = require("../helpers.js")
|
||||||
|
/*
|
||||||
|
=== ROT - Rate Of Turn ===
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3
|
||||||
|
| | |
|
||||||
|
$--ROT,x.x,A*hh<CR><LF>
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1. Rate Of Turn, degrees per minute, "-" means bow turns to port
|
||||||
|
2. Status, "A" means data is valid
|
||||||
|
3. Checksum
|
||||||
|
*/
|
||||||
|
exports.TYPE = 'rate-of-turn';
|
||||||
|
exports.ID = 'ROT';
|
||||||
|
|
||||||
|
exports.decode = function (fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
rateOfTurn: +fields[1],
|
||||||
|
valid: fields[2] === "A",
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.encode = function (talker, msg) {
|
||||||
|
var result = ['$' + talker + exports.ID];
|
||||||
|
result.push(helpers.encodeFixed(msg.rateOfTurn, 2));
|
||||||
|
result.push('A');
|
||||||
|
var resultMsg = result.join(',');
|
||||||
|
return resultMsg + helpers.computeChecksum(resultMsg);
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
=== RSA - Rudder Angle ===
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3
|
||||||
|
| | |
|
||||||
|
$--RSA,x.x,A,,*0B
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1. Rudder Angle
|
||||||
|
2. Always A
|
||||||
|
3. Checksum
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.TYPE = 'rudder';
|
||||||
|
exports.ID = 'RSA';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
angle: +fields[1]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
ZDA product info
|
||||||
|
1 2 3 4 5
|
||||||
|
| | | | |
|
||||||
|
$GPTXT,xx,yy,zz,info*hh
|
||||||
|
1) The total number of statements in the current message, 01 to 99
|
||||||
|
2) Statement number, 01 to 99
|
||||||
|
3) Text identifier
|
||||||
|
4) Text information
|
||||||
|
5) Checksum
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.ID = 'TXT';
|
||||||
|
exports.TYPE = 'product-info';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
xx: fields[1],
|
||||||
|
yy: fields[2],
|
||||||
|
zz: fields[3],
|
||||||
|
info: fields[4]
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
var helpers = require("../helpers.js")
|
||||||
|
/*
|
||||||
|
=== VTG - Track made good and Ground speed ===
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3 4 5 6 7 8 9 10
|
||||||
|
| | | | | | | | | |
|
||||||
|
$--VTG,x.x,T,x.x,M,x.x,N,x.x,K,m,*hh<CR><LF>
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1. Track Degrees
|
||||||
|
2. T = True
|
||||||
|
3. Track Degrees
|
||||||
|
4. M = Magnetic
|
||||||
|
5. Speed Knots
|
||||||
|
6. N = Knots
|
||||||
|
7. Speed Kilometers Per Hour
|
||||||
|
8. K = Kilometers Per Hour
|
||||||
|
9. FAA mode indicator (NMEA 2.3 and later)
|
||||||
|
10. Checksum=== VTG - Track made good and Ground speed ===
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3 4 5 6 7 8 9 10
|
||||||
|
| | | | | | | | | |
|
||||||
|
$--VTG,x.x,T,x.x,M,x.x,N,x.x,K,m,*hh<CR><LF>
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1. Track Degrees
|
||||||
|
2. T = True
|
||||||
|
3. Track Degrees
|
||||||
|
4. M = Magnetic
|
||||||
|
5. Speed Knots
|
||||||
|
6. N = Knots
|
||||||
|
7. Speed Kilometers Per Hour
|
||||||
|
8. K = Kilometers Per Hour
|
||||||
|
9. FAA mode indicator (NMEA 2.3 and later)
|
||||||
|
10. Checksum
|
||||||
|
*/
|
||||||
|
exports.TYPE = 'track-info';
|
||||||
|
exports.ID = 'VTG';
|
||||||
|
|
||||||
|
exports.decode = function (fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: 'track-info',
|
||||||
|
trackTrue: +fields[1],
|
||||||
|
trackMagnetic: +fields[3],
|
||||||
|
speedKnots: +fields[5],
|
||||||
|
speedKmph: +fields[7]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.encode = function (talker, msg) {
|
||||||
|
var result = ['$' + talker + exports.ID];
|
||||||
|
result.push(helpers.encodeDegrees(msg.trackTrue));
|
||||||
|
result.push('T');
|
||||||
|
result.push(helpers.encodeDegrees(msg.trackMagnetic));
|
||||||
|
result.push('M');
|
||||||
|
result.push(helpers.encodeFixed(msg.speedKnots, 2));
|
||||||
|
result.push('N');
|
||||||
|
result.push('');
|
||||||
|
result.push('');
|
||||||
|
result.push('A');
|
||||||
|
var resultMsg = result.join(',');
|
||||||
|
return resultMsg + helpers.computeChecksum(resultMsg);
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
var helpers = require("../helpers.js")
|
||||||
|
/*
|
||||||
|
=== VWR Relative Wind Speed and Angle ===
|
||||||
|
|
||||||
|
Note: This is no longer a sentence that is recommended by the NMEA 0183 Standard Committee
|
||||||
|
for new designs, however it does exist in a lot of equipment.
|
||||||
|
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
1 2 3 4 5 6 7 8 9
|
||||||
|
| | | | | | | | |
|
||||||
|
$--VWR,x.x,a,x.x,N,x.x,M,x.x,K*hh
|
||||||
|
------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Field Number:
|
||||||
|
|
||||||
|
1) Wind direction magnitude in degrees
|
||||||
|
2) Wind direction Left/Right of bow
|
||||||
|
3) Speed
|
||||||
|
4) N
|
||||||
|
5) Speed
|
||||||
|
6) M = Meters Per Second
|
||||||
|
7) Speed
|
||||||
|
8) K = Kilometers Per Hour
|
||||||
|
9) Checksum
|
||||||
|
*/
|
||||||
|
exports.TYPE = 'wind-relative';
|
||||||
|
exports.ID = 'VWR';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
angle: +fields[1],
|
||||||
|
direction: fields[2],
|
||||||
|
speedKnots: +fields[3],
|
||||||
|
speedMs: +fields[5],
|
||||||
|
speedKmph: +fields[7]
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
var helpers = require("../helpers.js")
|
||||||
|
|
||||||
|
/*
|
||||||
|
ZDA Time & Date – UTC, Day, Month, Year and Local Time Zone
|
||||||
|
1 2 3 4 5 6 7
|
||||||
|
| | | | | | |
|
||||||
|
$--ZDA,hhmmss.ss,xx,xx,xxxx,xx,xx*hh
|
||||||
|
1) Local zone minutes description, same sign as local hours
|
||||||
|
2) Local zone description, 00 to +/- 13 hours
|
||||||
|
3) Year
|
||||||
|
4) Month, 01 to 12
|
||||||
|
5) Day, 01 to 31
|
||||||
|
6) Time (UTC)
|
||||||
|
7) Checksum
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.ID = 'ZDA';
|
||||||
|
exports.TYPE = 'time-zone';
|
||||||
|
|
||||||
|
exports.decode = function(fields) {
|
||||||
|
return {
|
||||||
|
sentence: exports.ID,
|
||||||
|
type: exports.TYPE,
|
||||||
|
timestamp: fields[1],
|
||||||
|
day: fields[2],
|
||||||
|
month: fields[3],
|
||||||
|
year: fields[4],
|
||||||
|
ltzh: fields[5],
|
||||||
|
ltzn: fields[6]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.encode = function (talker, msg) {
|
||||||
|
var result = ['$' + talker + exports.ID];
|
||||||
|
var { date } = msg
|
||||||
|
result.push(helpers.padLeft(date.getHours().toString(), 2, '0') + helpers.padLeft(date.getMinutes().toString(), 2, '0') + helpers.padLeft(date.getSeconds().toString(), 2, '0') + '.000');
|
||||||
|
result.push(helpers.padLeft(date.getDate().toString(), 2, '0'));
|
||||||
|
result.push(helpers.padLeft((date.getMonth() + 1).toString(), 2, '0'));
|
||||||
|
result.push(date.getFullYear().toString());
|
||||||
|
result.push('00');
|
||||||
|
result.push('00');
|
||||||
|
|
||||||
|
var resultMsg = result.join(',');
|
||||||
|
return resultMsg + helpers.computeChecksum(resultMsg);
|
||||||
|
}
|
|
@ -0,0 +1,356 @@
|
||||||
|
//Copied from from https://github.com/nherment/node-nmea/blob/master/lib/Helper.js
|
||||||
|
|
||||||
|
var m_hex = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||||
|
|
||||||
|
exports.toHexString = function(v) {
|
||||||
|
var lsn;
|
||||||
|
var msn;
|
||||||
|
|
||||||
|
msn = (v >> 4) & 0x0f;
|
||||||
|
lsn = (v >> 0) & 0x0f;
|
||||||
|
return m_hex[msn] + m_hex[lsn];
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.padLeft = function(s, len, ch) {
|
||||||
|
while(s.length < len) {
|
||||||
|
s = ch + s;
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
// verify the checksum
|
||||||
|
exports.verifyChecksum = function(sentence, checksum) {
|
||||||
|
var q;
|
||||||
|
var c1;
|
||||||
|
var c2;
|
||||||
|
var i;
|
||||||
|
|
||||||
|
// skip the $
|
||||||
|
i = 1;
|
||||||
|
|
||||||
|
// init to first character
|
||||||
|
c1 = sentence.charCodeAt(i);
|
||||||
|
|
||||||
|
// process rest of characters, zero delimited
|
||||||
|
for( i = 2; i < sentence.length; ++i) {
|
||||||
|
c1 = c1 ^ sentence.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
// checksum is a 2 digit hex value
|
||||||
|
c2 = parseInt(checksum, 16);
|
||||||
|
|
||||||
|
// should be equal
|
||||||
|
return ((c1 & 0xff) === c2);
|
||||||
|
};
|
||||||
|
|
||||||
|
// generate a checksum for a sentence (no trailing *xx)
|
||||||
|
exports.computeChecksum = function(sentence) {
|
||||||
|
var c1;
|
||||||
|
var i;
|
||||||
|
|
||||||
|
// skip the $
|
||||||
|
i = 1;
|
||||||
|
|
||||||
|
// init to first character var count;
|
||||||
|
|
||||||
|
c1 = sentence.charCodeAt(i);
|
||||||
|
|
||||||
|
// process rest of characters, zero delimited
|
||||||
|
for( i = 2; i < sentence.length; ++i) {
|
||||||
|
c1 = c1 ^ sentence.charCodeAt(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '*' + exports.toHexString(c1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// =========================================
|
||||||
|
// field encoders
|
||||||
|
// =========================================
|
||||||
|
|
||||||
|
// encode latitude
|
||||||
|
// input: latitude in decimal degrees
|
||||||
|
// output: latitude in nmea format
|
||||||
|
// ddmm.mmm
|
||||||
|
exports.encodeLatitude = function(lat) {
|
||||||
|
var d;
|
||||||
|
var m;
|
||||||
|
var f;
|
||||||
|
var h;
|
||||||
|
var s;
|
||||||
|
var t;
|
||||||
|
if(lat === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lat < 0) {
|
||||||
|
h = 'S';
|
||||||
|
lat = -lat;
|
||||||
|
} else {
|
||||||
|
h = 'N';
|
||||||
|
}
|
||||||
|
// get integer degrees
|
||||||
|
d = Math.floor(lat);
|
||||||
|
// degrees are always 2 digits
|
||||||
|
s = d.toString();
|
||||||
|
if(s.length < 2) {
|
||||||
|
s = '0' + s;
|
||||||
|
}
|
||||||
|
// get fractional degrees
|
||||||
|
f = lat - d;
|
||||||
|
// convert to fractional minutes
|
||||||
|
m = (f * 60.0);
|
||||||
|
// format the fixed point fractional minutes
|
||||||
|
t = m.toFixed(3);
|
||||||
|
if(m < 10) {
|
||||||
|
// add leading 0
|
||||||
|
t = '0' + t;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = s + t + ',' + h;
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
// encode longitude
|
||||||
|
// input: longitude in decimal degrees
|
||||||
|
// output: longitude in nmea format
|
||||||
|
// dddmm.mmm
|
||||||
|
exports.encodeLongitude = function(lon) {
|
||||||
|
var d;
|
||||||
|
var m;
|
||||||
|
var f;
|
||||||
|
var h;
|
||||||
|
var s;
|
||||||
|
var t;
|
||||||
|
|
||||||
|
if(lon === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lon < 0) {
|
||||||
|
h = 'W';
|
||||||
|
lon = -lon;
|
||||||
|
} else {
|
||||||
|
h = 'E';
|
||||||
|
}
|
||||||
|
|
||||||
|
// get integer degrees
|
||||||
|
d = Math.floor(lon);
|
||||||
|
// degrees are always 3 digits
|
||||||
|
s = d.toString();
|
||||||
|
while(s.length < 3) {
|
||||||
|
s = '0' + s;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get fractional degrees
|
||||||
|
f = lon - d;
|
||||||
|
// convert to fractional minutes and round up to the specified precision
|
||||||
|
m = (f * 60.0);
|
||||||
|
// minutes are always 6 characters = mm.mmm
|
||||||
|
t = m.toFixed(3);
|
||||||
|
if(m < 10) {
|
||||||
|
// add leading 0
|
||||||
|
t = '0' + t;
|
||||||
|
}
|
||||||
|
s = s + t + ',' + h;
|
||||||
|
return s;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1 decimal, always meters
|
||||||
|
exports.encodeAltitude = function(alt) {
|
||||||
|
if(alt === undefined) {
|
||||||
|
return ',';
|
||||||
|
}
|
||||||
|
return alt.toFixed(1) + ',M';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 1 decimal, always meters
|
||||||
|
exports.encodeGeoidalSeperation = function(geoidalSep) {
|
||||||
|
if(geoidalSep === undefined) {
|
||||||
|
return ',';
|
||||||
|
}
|
||||||
|
return geoidalSep.toFixed(1) + ',M';
|
||||||
|
};
|
||||||
|
|
||||||
|
// magnetic variation
|
||||||
|
exports.encodeMagVar = function(v) {
|
||||||
|
var a;
|
||||||
|
var s;
|
||||||
|
if(v === undefined) {
|
||||||
|
return ',';
|
||||||
|
}
|
||||||
|
a = Math.abs(v);
|
||||||
|
s = (v < 0) ? (a.toFixed(1) + ',E') : (a.toFixed(1) + ',W');
|
||||||
|
return exports.padLeft(s, 7, '0');
|
||||||
|
};
|
||||||
|
|
||||||
|
// degrees
|
||||||
|
exports.encodeDegrees = function(d) {
|
||||||
|
if(d === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return exports.padLeft(d.toFixed(2), 6, '0');
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.encodeDate = function(d) {
|
||||||
|
var yr;
|
||||||
|
var mn;
|
||||||
|
var dy;
|
||||||
|
|
||||||
|
if(d === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
yr = d.getUTCFullYear();
|
||||||
|
mn = d.getUTCMonth() + 1;
|
||||||
|
dy = d.getUTCDate();
|
||||||
|
return exports.padLeft(dy.toString(), 2, '0') + exports.padLeft(mn.toString(), 2, '0') + yr.toString().substr(2);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.encodeTime = function(d) {
|
||||||
|
var h;
|
||||||
|
var m;
|
||||||
|
var s;
|
||||||
|
|
||||||
|
if(d === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
h = d.getUTCHours();
|
||||||
|
m = d.getUTCMinutes();
|
||||||
|
s = d.getUTCSeconds();
|
||||||
|
return exports.padLeft(h.toString(), 2, '0') + exports.padLeft(m.toString(), 2, '0') + exports.padLeft(s.toString(), 2, '0');
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.encodeKnots = function(k) {
|
||||||
|
if(k === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return exports.padLeft(k.toFixed(1), 5, '0');
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.encodeValue = function(v) {
|
||||||
|
if(v === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return v.toString();
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.encodeFixed = function(v, f) {
|
||||||
|
if(v === undefined) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return v.toFixed(f);
|
||||||
|
};
|
||||||
|
|
||||||
|
// =========================================
|
||||||
|
// field traditionalDecoders
|
||||||
|
// =========================================
|
||||||
|
|
||||||
|
// separate number and units
|
||||||
|
exports.parseAltitude = function(alt, units) {
|
||||||
|
var scale = 1.0;
|
||||||
|
if(units === 'F') {
|
||||||
|
scale = 0.3048;
|
||||||
|
}
|
||||||
|
return parseFloat(alt) * scale;
|
||||||
|
};
|
||||||
|
|
||||||
|
// separate degrees value and quadrant (E/W)
|
||||||
|
exports.parseDegrees = function(deg, quadrant) {
|
||||||
|
var q = (quadrant === 'E') ? -1.0 : 1.0;
|
||||||
|
|
||||||
|
return parseFloat(deg) * q;
|
||||||
|
};
|
||||||
|
|
||||||
|
// fields can be empty so have to wrap the global parseFloat
|
||||||
|
exports.parseFloatX = function(f) {
|
||||||
|
if(f === '') {
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
return parseFloat(f);
|
||||||
|
};
|
||||||
|
|
||||||
|
// decode latitude
|
||||||
|
// input : latitude in nmea format
|
||||||
|
// first two digits are degress
|
||||||
|
// rest of digits are decimal minutes
|
||||||
|
// output : latitude in decimal degrees
|
||||||
|
exports.parseLatitude = function(lat, hemi) {
|
||||||
|
var h = (hemi === 'N') ? 1.0 : -1.0;
|
||||||
|
var a;
|
||||||
|
var dg;
|
||||||
|
var mn;
|
||||||
|
var l;
|
||||||
|
a = lat.split('.');
|
||||||
|
if(a[0].length === 4) {
|
||||||
|
// two digits of degrees
|
||||||
|
dg = lat.substring(0, 2);
|
||||||
|
mn = lat.substring(2);
|
||||||
|
} else if(a[0].length === 3) {
|
||||||
|
// 1 digit of degrees (in case no leading zero)
|
||||||
|
dg = lat.substring(0, 1);
|
||||||
|
mn = lat.substring(1);
|
||||||
|
} else {
|
||||||
|
// no degrees, just minutes (nonstandard but a buggy unit might do this)
|
||||||
|
dg = '0';
|
||||||
|
mn = lat;
|
||||||
|
}
|
||||||
|
// latitude is usually precise to 5-8 digits
|
||||||
|
return ((parseFloat(dg) + (parseFloat(mn) / 60.0)) * h).toFixed(8);
|
||||||
|
};
|
||||||
|
|
||||||
|
// decode longitude
|
||||||
|
// first three digits are degress
|
||||||
|
// rest of digits are decimal minutes
|
||||||
|
exports.parseLongitude = function(lon, hemi) {
|
||||||
|
var h;
|
||||||
|
var a;
|
||||||
|
var dg;
|
||||||
|
var mn;
|
||||||
|
h = (hemi === 'E') ? 1.0 : -1.0;
|
||||||
|
a = lon.split('.');
|
||||||
|
if(a[0].length === 5) {
|
||||||
|
// three digits of degrees
|
||||||
|
dg = lon.substring(0, 3);
|
||||||
|
mn = lon.substring(3);
|
||||||
|
} else if(a[0].length === 4) {
|
||||||
|
// 2 digits of degrees (in case no leading zero)
|
||||||
|
dg = lon.substring(0, 2);
|
||||||
|
mn = lon.substring(2);
|
||||||
|
} else if(a[0].length === 3) {
|
||||||
|
// 1 digit of degrees (in case no leading zero)
|
||||||
|
dg = lon.substring(0, 1);
|
||||||
|
mn = lon.substring(1);
|
||||||
|
} else {
|
||||||
|
// no degrees, just minutes (nonstandard but a buggy unit might do this)
|
||||||
|
dg = '0';
|
||||||
|
mn = lon;
|
||||||
|
}
|
||||||
|
// longitude is usually precise to 5-8 digits
|
||||||
|
return ((parseFloat(dg) + (parseFloat(mn) / 60.0)) * h).toFixed(8);
|
||||||
|
};
|
||||||
|
|
||||||
|
// fields can be empty so have to wrap the global parseInt
|
||||||
|
exports.parseIntX = function(i) {
|
||||||
|
if(i === '') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return parseInt(i, 10);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.parseDateTime = function(date, time) {
|
||||||
|
var h = parseInt(time.slice(0, 2), 10);
|
||||||
|
var m = parseInt(time.slice(2, 4), 10);
|
||||||
|
var s = parseInt(time.slice(4, 6), 10);
|
||||||
|
var D = parseInt(date.slice(0, 2), 10);
|
||||||
|
var M = parseInt(date.slice(2, 4), 10) - 1; // UTC = 0..11
|
||||||
|
var Y = parseInt(date.slice(4, 6), 10);
|
||||||
|
// hack : GPRMC date doesn't specify century. GPS came out in 1973
|
||||||
|
// so if year is less than 73 its 2000, otherwise 1900
|
||||||
|
if (Y < 73) {
|
||||||
|
Y = Y + 2000;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Y = Y + 1900;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Date(Date.UTC(Y, M, D, h, m, s));
|
||||||
|
};
|
|
@ -0,0 +1,49 @@
|
||||||
|
var Transform = require('stream').Transform;
|
||||||
|
|
||||||
|
function Liner() {
|
||||||
|
Transform.call(this, { objectMode: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
Liner.prototype = {
|
||||||
|
_transform: function (chunk, encoding, done) {
|
||||||
|
var data = chunk.toString()
|
||||||
|
if (this._lastLineData) data = this._lastLineData + data
|
||||||
|
|
||||||
|
var lines = data.split('\n')
|
||||||
|
this._lastLineData = lines.splice(lines.length - 1, 1)[0]
|
||||||
|
|
||||||
|
lines.forEach(this.push.bind(this))
|
||||||
|
done()
|
||||||
|
},
|
||||||
|
_flush: function (done) {
|
||||||
|
if (this._lastLineData) this.push(this._lastLineData)
|
||||||
|
this._lastLineData = null
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extend(Transform, Liner);
|
||||||
|
|
||||||
|
function extend(base, sub) {
|
||||||
|
// Avoid instantiating the base class just to setup inheritance
|
||||||
|
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
|
||||||
|
// for a polyfill
|
||||||
|
// Also, do a recursive merge of two prototypes, so we don't overwrite
|
||||||
|
// the existing prototype, but still maintain the inheritance chain
|
||||||
|
// Thanks to @ccnokes
|
||||||
|
var origProto = sub.prototype;
|
||||||
|
sub.prototype = Object.create(base.prototype);
|
||||||
|
for (var key in origProto) {
|
||||||
|
sub.prototype[key] = origProto[key];
|
||||||
|
}
|
||||||
|
// Remember the constructor property was set wrong, let's fix it
|
||||||
|
sub.prototype.constructor = sub;
|
||||||
|
// In ECMAScript5+ (all modern browsers), you can make the constructor property
|
||||||
|
// non-enumerable if you define it like this instead
|
||||||
|
Object.defineProperty(sub.prototype, 'constructor', {
|
||||||
|
enumerable: false,
|
||||||
|
value: sub
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Liner;
|
|
@ -0,0 +1,124 @@
|
||||||
|
// A NMEA-0183 parser based on the format given here: http://www.tronico.fi/OH6NT/docs/NMEA0183.pdf
|
||||||
|
|
||||||
|
var MWV = require('./codecs/MWV.js');
|
||||||
|
var VTG = require('./codecs/VTG.js');
|
||||||
|
var DBT = require('./codecs/DBT.js');
|
||||||
|
var GLL = require('./codecs/GLL.js');
|
||||||
|
var BWC = require('./codecs/BWC.js');
|
||||||
|
var GSV = require('./codecs/GSV.js');
|
||||||
|
var GSA = require('./codecs/GSA.js');
|
||||||
|
var GGA = require('./codecs/GGA.js');
|
||||||
|
var RMB = require('./codecs/RMB.js');
|
||||||
|
var RMC = require('./codecs/RMC.js');
|
||||||
|
var RSA = require('./codecs/RSA.js');
|
||||||
|
var APB = require('./codecs/APB.js');
|
||||||
|
var HDG = require('./codecs/HDG.js');
|
||||||
|
var HDT = require('./codecs/HDT.js');
|
||||||
|
var HDM = require('./codecs/HDM.js');
|
||||||
|
var RDID = require('./codecs/RDID.js');
|
||||||
|
var GRMT = require('./codecs/GRMT.js');
|
||||||
|
var VWR = require('./codecs/VWR.js');
|
||||||
|
var ROT = require('./codecs/ROT.js');
|
||||||
|
var ZDA = require('./codecs/ZDA.js');
|
||||||
|
var TXT = require('./codecs/TXT.js');
|
||||||
|
|
||||||
|
// export helpers
|
||||||
|
module.exports.Helpers= require('./helpers.js');
|
||||||
|
|
||||||
|
var validLine = function (line) {
|
||||||
|
// check that the line passes checksum validation
|
||||||
|
// checksum is the XOR of all characters between $ and * in the message.
|
||||||
|
// checksum reference is provided as a hex value after the * in the message.
|
||||||
|
var checkVal = 0;
|
||||||
|
var parts = line.split('*');
|
||||||
|
for (var i = 1; i < parts[0].length; i++) {
|
||||||
|
checkVal = checkVal ^ parts[0].charCodeAt(i);
|
||||||
|
}
|
||||||
|
;
|
||||||
|
return checkVal == parseInt(parts[1], 16);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.traditionalDecoders = {
|
||||||
|
GGA: GGA.decode,
|
||||||
|
RMB: RMB.decode,
|
||||||
|
RMC: RMC.decode,
|
||||||
|
RSA: RSA.decode,
|
||||||
|
APB: APB.decode,
|
||||||
|
GSA: GSA.decode,
|
||||||
|
GSV: GSV.decode,
|
||||||
|
BWC: BWC.decode,
|
||||||
|
DBT: DBT.decode,
|
||||||
|
MWV: MWV.decode,
|
||||||
|
VTG: VTG.decode,
|
||||||
|
GLL: GLL.decode,
|
||||||
|
HDG: HDG.decode,
|
||||||
|
HDT: HDT.decode,
|
||||||
|
HDM: HDM.decode,
|
||||||
|
RDID: RDID.decode,
|
||||||
|
GRMT: GRMT.decode,
|
||||||
|
VWR: VWR.decode,
|
||||||
|
ROT: ROT.decode,
|
||||||
|
ZDA: ZDA.decode,
|
||||||
|
TXT: TXT.decode,
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.encoders = new Object();
|
||||||
|
|
||||||
|
exports.encoders[MWV.TYPE] = MWV;
|
||||||
|
exports.encoders[VTG.TYPE] = VTG;
|
||||||
|
exports.encoders[DBT.TYPE] = DBT;
|
||||||
|
exports.encoders[GLL.TYPE] = GLL;
|
||||||
|
exports.encoders[HDT.TYPE] = HDT;
|
||||||
|
exports.encoders[GGA.TYPE] = GGA;
|
||||||
|
exports.encoders[HDM.TYPE] = HDM;
|
||||||
|
exports.encoders[ROT.TYPE] = ROT;
|
||||||
|
exports.encoders[ZDA.TYPE] = ZDA;
|
||||||
|
exports.encoders[TXT.TYPE] = TXT;
|
||||||
|
|
||||||
|
exports.parse = function (line) {
|
||||||
|
if (validLine(line)) {
|
||||||
|
var fields = line.split('*')[0].split(','),
|
||||||
|
talker_id,
|
||||||
|
msg_fmt;
|
||||||
|
if (fields[0].charAt(1) == 'P') {
|
||||||
|
talker_id = 'P'; // Proprietary
|
||||||
|
msg_fmt = fields[0].substr(2);
|
||||||
|
} else {
|
||||||
|
talker_id = fields[0].substr(1, 2);
|
||||||
|
msg_fmt = fields[0].substr(3);
|
||||||
|
}
|
||||||
|
var parser = exports.traditionalDecoders[msg_fmt];
|
||||||
|
if (parser) {
|
||||||
|
var val = parser(fields);
|
||||||
|
val.talker_id = talker_id;
|
||||||
|
return val;
|
||||||
|
} else {
|
||||||
|
throw Error("Error in parsing:" + line);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw Error("Invalid line:" + line);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.encode = function (talker, msg) {
|
||||||
|
if (typeof msg === 'undefined') {
|
||||||
|
throw new Error("Can not encode undefined, did you forget msg parameter?");
|
||||||
|
}
|
||||||
|
var encoder = exports.encoders[msg.type];
|
||||||
|
if (encoder) {
|
||||||
|
return encoder.encode(talker, msg);
|
||||||
|
} else {
|
||||||
|
throw Error("No encoder for type:" + msg.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.createDefaultTransformer = function (options) {
|
||||||
|
var stream = require('through')(function (data) {
|
||||||
|
try {
|
||||||
|
stream.queue(exports.parse(data));
|
||||||
|
} catch (e) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return stream;
|
||||||
|
};
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"name": "nmea",
|
||||||
|
"description": "A parser for the NMEA 0183 GPS Receiver protocol",
|
||||||
|
"version": "0.1.2",
|
||||||
|
"author": "James Penn <james@jamespenn.co.uk>",
|
||||||
|
"license": "MIT",
|
||||||
|
"main": "nmea",
|
||||||
|
"keywords": [
|
||||||
|
"gps",
|
||||||
|
"nmea"
|
||||||
|
],
|
||||||
|
"repository": "git://github.com/jamesp/node-nmea",
|
||||||
|
"devDependencies": {
|
||||||
|
"line-reader": "0.2",
|
||||||
|
"mocha": "^8.1.1",
|
||||||
|
"should": "~2.0.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"JSONStream": "0.7",
|
||||||
|
"through": ">=2.2.7 <3"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "mocha"
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
var nmea = require('./nmea')
|
||||||
|
|
||||||
|
var s = [
|
||||||
|
"$GPGSA,A,3,27,08,11,10,26,21,18,16,07,20,,,1.60,0.97,1.27*0F",
|
||||||
|
"$GPGSV,3,1,12,29,75,266,39,05,48,047,,26,43,108,,15,35,157,*78",
|
||||||
|
"$GPGSV,3,2,12,21,30,292,,18,21,234,,02,18,093,,25,13,215,*7F",
|
||||||
|
"$GPGSV,3,3,12,30,11,308,,16,,333,,12,,191,,07,-4,033,*62",
|
||||||
|
"$GPRMC,085542.023,V,,,,,,,041211,,,N*45",
|
||||||
|
"$IIRMC,101639,A,4924.407,N,00108.467,W,06.2,177,230720,01,W,A*04",
|
||||||
|
"$GPGGA,085543.023,,,,,0,00,,,M,0.0,M,,0000*58",
|
||||||
|
"$IIBWC,160947,6008.160,N,02454.290,E,162.4,T,154.3,M,001.050,N,DEST*1C",
|
||||||
|
"$IIAPB,A,A,0.001,L,N,V,V,154.3,M,DEST,154.3,M,154.2,M*19",
|
||||||
|
"$APHDG,175.6,,,,*5D",
|
||||||
|
"$APHDG,132.2,2.0,W,3.9,E*40",
|
||||||
|
"$GPHDT,274.07,T*03",
|
||||||
|
"$IIHDM,201.5,M*24",
|
||||||
|
"$PRDID,-4.44,2.12,154.25*56",
|
||||||
|
"$PGRMT,GPS19x-HVS Software Version 2.20,,,,,,,,*6F",
|
||||||
|
"$IIVWR,045.0,L,12.6,N,6.5,M,23.3,K*52",
|
||||||
|
"$ECRMB,A,0.060,L,,Waypoint 110,4921.975,N,00109.838,W,2.63,199.8,5.8,V,D*65",
|
||||||
|
"$ECRMB,A,0.001,R,001,002,5431.307,N,00941.537,E,1.488,31.551,0.104,V*32",
|
||||||
|
"$APRSA,1.15,A,,*0B",
|
||||||
|
"$APRSA,1.10,A,,*0E",
|
||||||
|
"$--ROT,-2.53,A*3F",
|
||||||
|
"$GNZDA,080246.000,07,05,2021,00,00*43",
|
||||||
|
"$GPTXT,01,01,01,ANTENNA OPEN*25",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (var i=0; i < s.length; i++) {
|
||||||
|
console.log(nmea.parse(s[i]));
|
||||||
|
};
|
|
@ -0,0 +1,10 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('GGA ', function () {
|
||||||
|
it('parses', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$GPAPB,A,A,0.10,R,N,V,V,011,M,DEST,011,M,011,M*3C");
|
||||||
|
msg.should.have.property('type', 'autopilot-b');
|
||||||
|
msg.should.have.property('sentence', 'APB');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('BWC ', function () {
|
||||||
|
it('parses', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$GPBWC,220516,5130.02,N,00046.34,W,213.8,T,218.0,M,0004.6,N,EGLM*21");
|
||||||
|
msg.should.have.property('type', '2waypoint');
|
||||||
|
msg.should.have.property('sentence', 'BWC');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('DBT parsing', function () {
|
||||||
|
it('parses feet and meters', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$IIDBT,036.41,f,011.10,M,005.99,F*25");
|
||||||
|
msg.should.have.property('sentence', 'DBT');
|
||||||
|
msg.should.have.property('type', 'depth-transducer');
|
||||||
|
msg.should.have.property('depthFeet', 36.41);
|
||||||
|
msg.should.have.property('depthMeters', 11.10);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('DBT encoding', function () {
|
||||||
|
it('encodes ok', function () {
|
||||||
|
var nmeaMsg = require("../nmea.js").encode('II', {
|
||||||
|
type: 'depth-transducer',
|
||||||
|
depthFeet: 36.41,
|
||||||
|
depthFathoms: 5.99,
|
||||||
|
depthMeters: 11.10
|
||||||
|
});
|
||||||
|
nmeaMsg.should.equal("$IIDBT,36.41,f,11.10,M,5.99,F*25");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,44 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('GGA ', function () {
|
||||||
|
it('parses', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$IIGGA,123519,4807.04,N,1131.00,E,1,8,0.9,545.9,M,46.9,M,,*52");
|
||||||
|
msg.should.have.property('type', 'fix');
|
||||||
|
msg.should.have.property('sentence', 'GGA');
|
||||||
|
msg.should.have.property('talker_id', 'II');
|
||||||
|
msg.should.have.property('timestamp', '123519');
|
||||||
|
msg.should.have.property('lat', '4807.04');
|
||||||
|
msg.should.have.property('latPole', 'N');
|
||||||
|
msg.should.have.property('lon', '1131.00');
|
||||||
|
msg.should.have.property('lonPole', 'E');
|
||||||
|
msg.should.have.property('fixType', 'fix');
|
||||||
|
msg.should.have.property('numSat', 8);
|
||||||
|
msg.should.have.property('horDilution', 0.9);
|
||||||
|
msg.should.have.property('alt', 545.9);
|
||||||
|
msg.should.have.property('altUnit', 'M');
|
||||||
|
msg.should.have.property('geoidalSep', 46.9);
|
||||||
|
msg.should.have.property('geoidalSepUnit', 'M');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('GGA', function () {
|
||||||
|
it('encodes ok', function () {
|
||||||
|
var nmeaMsg = require("../nmea.js").encode('II', {
|
||||||
|
type: 'fix',
|
||||||
|
timestamp: new Date(Date.UTC(2013, 1, 1, 12, 35, 19)),
|
||||||
|
lat: 4807.04,
|
||||||
|
latPole: 'N',
|
||||||
|
lon: 1131.00,
|
||||||
|
lonPole: 'E',
|
||||||
|
fixType: 'fix',
|
||||||
|
numSat: 8,
|
||||||
|
horDilution: 0.9,
|
||||||
|
alt: 545.9,
|
||||||
|
altUnit: 'M',
|
||||||
|
geoidalSep: 46.9,
|
||||||
|
geoidalSepUnit: 'M'
|
||||||
|
});
|
||||||
|
nmeaMsg.should.equal("$IIGGA,123519,4807.04,N,1131.00,E,1,8,0.9,545.9,M,46.9,M,,*52");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,29 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('GLL ', function () {
|
||||||
|
it('parses', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$GPGLL,6005.068,N,02332.341,E,095601,A,D*42");
|
||||||
|
msg.should.have.property('type', 'geo-position');
|
||||||
|
msg.should.have.property('sentence', 'GLL');
|
||||||
|
msg.should.have.property('lat', '6005.068');
|
||||||
|
msg.should.have.property('latPole', 'N');
|
||||||
|
msg.should.have.property('lon', '02332.341');
|
||||||
|
msg.should.have.property('lonPole', 'E');
|
||||||
|
msg.should.have.property('status', 'valid');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('GLL', function () {
|
||||||
|
it('encodes ok', function () {
|
||||||
|
var nmeaMsg = require("../nmea.js").encode('II', {
|
||||||
|
type: 'geo-position',
|
||||||
|
lat: 6005.06,
|
||||||
|
latPole: 'N',
|
||||||
|
lon: 2332.34,
|
||||||
|
lonPole: 'E',
|
||||||
|
timestamp: new Date(2013, 4, 1, 21,17 - new Date().getTimezoneOffset(),22),
|
||||||
|
status: 'valid'
|
||||||
|
});
|
||||||
|
nmeaMsg.should.equal("$IIGLL,6005.06,N,2332.34,E,211722,A,D*62");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('GRMT ', function () {
|
||||||
|
it('parses', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$PGRMT,GPS19x-HVS Software Version 2.20,,,,,,,,*6F");
|
||||||
|
msg.should.have.property('type', 'sensor-information');
|
||||||
|
msg.should.have.property('sentence', 'GRMT');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('GSV ', function () {
|
||||||
|
it('parses', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$GPGSA,A,3,12,05,25,29,,,,,,,,,9.4,7.6,5.6*37");
|
||||||
|
msg.should.have.property('type', 'active-satellites');
|
||||||
|
msg.should.have.property('sentence', 'GSA');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,9 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('GSV ', function () {
|
||||||
|
it('parses', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$GPGSV,3,1,11,03,03,111,00,04,15,270,00,06,01,010,00,13,06,292,00*74 $GPGSV,3,2,11,14,25,170,00,16,57,208,39,18,67,296,40,19,40,246,00*2D");
|
||||||
|
msg.should.have.property('type', 'satellite-list-partial');
|
||||||
|
msg.should.have.property('sentence', 'GSV');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,19 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('HDM parsing', function () {
|
||||||
|
it('parse heading', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$IIHDM,201.5,M*24");
|
||||||
|
msg.should.have.property('sentence', 'HDM');
|
||||||
|
msg.should.have.property('heading', 201.5);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('HDM encoding', function () {
|
||||||
|
it('encodes ok', function () {
|
||||||
|
var nmeaMsg = require("../nmea.js").encode('II', {
|
||||||
|
type: 'heading-info-magnetic',
|
||||||
|
heading: 201.5
|
||||||
|
});
|
||||||
|
nmeaMsg.should.equal("$IIHDM,201.5,M*24");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,19 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('HDT parsing', function () {
|
||||||
|
it('parse heading', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$IIHDT,234.2,T*25");
|
||||||
|
msg.should.have.property('sentence', 'HDT');
|
||||||
|
msg.should.have.property('heading', 234.2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('HDT encoding', function () {
|
||||||
|
it('encodes ok', function () {
|
||||||
|
var nmeaMsg = require("../nmea.js").encode('II', {
|
||||||
|
type: 'heading-info',
|
||||||
|
heading: 234.2
|
||||||
|
});
|
||||||
|
nmeaMsg.should.equal("$IIHDT,234.2,T*25");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,27 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('MWV parsing', function () {
|
||||||
|
it('parses ok', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$IIMWV,017,R,02.91,N,A*2F");
|
||||||
|
msg.should.have.property('sentence', 'MWV');
|
||||||
|
msg.should.have.property('type', 'wind');
|
||||||
|
msg.should.have.property('angle', 17);
|
||||||
|
msg.should.have.property('reference', 'R');
|
||||||
|
msg.should.have.property('speed', 2.91);
|
||||||
|
msg.should.have.property('units', 'N');
|
||||||
|
msg.should.have.property('status', 'A');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('MWV encoding', function () {
|
||||||
|
it('parses ok', function () {
|
||||||
|
var nmeaMsg = require("../nmea.js").encode('XX', {
|
||||||
|
type: 'wind',
|
||||||
|
angle: 17,
|
||||||
|
reference: 'R',
|
||||||
|
speed: 2.91,
|
||||||
|
units: 'N',
|
||||||
|
status: 'A'});
|
||||||
|
nmeaMsg.should.equal("$XXMWV,017.00,R,2.91,N,A*31");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,10 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('RDID ', function () {
|
||||||
|
it('parses', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$PRDID,-1.31,7.81,47.31*68");
|
||||||
|
msg.should.have.property('type', 'gyro');
|
||||||
|
msg.should.have.property('sentence', 'RDID');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('RMC ', function () {
|
||||||
|
it('parses', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A");
|
||||||
|
msg.should.have.property('type', 'nav-info');
|
||||||
|
msg.should.have.property('sentence', 'RMC');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,18 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('ROT ', function () {
|
||||||
|
it('parses', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$--ROT,-2.53,A*3F");
|
||||||
|
msg.should.have.property('type', 'rate-of-turn');
|
||||||
|
msg.should.have.property('sentence', 'ROT');
|
||||||
|
msg.should.have.property('rateOfTurn', -2.53);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('encodes', function () {
|
||||||
|
var msg = require("../nmea.js").encode('--', {
|
||||||
|
type: 'rate-of-turn',
|
||||||
|
rateOfTurn: -2.53452
|
||||||
|
});
|
||||||
|
msg.should.equal('$--ROT,-2.53,A*3F');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,24 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('VTG parsing', function () {
|
||||||
|
it('parses ok', function () {
|
||||||
|
var msg = require("../nmea.js").parse("$IIVTG,210.43,T,210.43,M,5.65,N,,,A*67");
|
||||||
|
msg.should.have.property('sentence', 'VTG');
|
||||||
|
msg.should.have.property('type', 'track-info');
|
||||||
|
msg.should.have.property('trackTrue', 210.43);
|
||||||
|
msg.should.have.property('trackMagnetic', 210.43);
|
||||||
|
msg.should.have.property('speedKnots', 5.65);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('VTG encoding', function () {
|
||||||
|
it('encodes ok', function () {
|
||||||
|
var nmeaMsg = require("../nmea.js").encode('XX', {
|
||||||
|
type: 'track-info',
|
||||||
|
trackTrue: 210.43,
|
||||||
|
trackMagnetic: 209.43,
|
||||||
|
speedKnots: 2.91
|
||||||
|
});
|
||||||
|
nmeaMsg.should.equal("$XXVTG,210.43,T,209.43,M,2.91,N,,,A*63");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,22 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
describe('helpers ', function () {
|
||||||
|
|
||||||
|
it('padLeft', function () {
|
||||||
|
var msg = require("../helpers.js").padLeft("abc", 5, " ");
|
||||||
|
msg.should.equal(" abc");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parseDateTime', function () {
|
||||||
|
// Input = 3rd of April of 2005
|
||||||
|
var dt = require("../helpers.js").parseDateTime("030405", "112233");
|
||||||
|
(+dt.getUTCDate()).should.equal(3);
|
||||||
|
(+dt.getUTCMonth() + 1).should.equal(4);
|
||||||
|
(+dt.getUTCFullYear()).should.equal(2005);
|
||||||
|
(+dt.getUTCHours()).should.equal(11);
|
||||||
|
(+dt.getUTCMinutes()).should.equal(22);
|
||||||
|
(+dt.getUTCSeconds()).should.equal(33);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
var should = require('should');
|
||||||
|
|
||||||
|
|
||||||
|
describe('Encoding unknown', function () {
|
||||||
|
it('undefined throws error', function () {
|
||||||
|
var nmea = require("../nmea.js");
|
||||||
|
(function(){
|
||||||
|
nmea.encode(undefined);
|
||||||
|
}).should.throw("Can not encode undefined, did you forget msg parameter?");
|
||||||
|
});
|
||||||
|
|
||||||
|
it('no type throws error', function () {
|
||||||
|
var nmea = require("../nmea.js");
|
||||||
|
(function(){
|
||||||
|
nmea.encode('II', {type:"foo"});
|
||||||
|
}).should.throw("No encoder for type:foo");
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
var lineReader = require('line-reader');
|
||||||
|
var nmea = require('../nmea.js');
|
||||||
|
|
||||||
|
lineReader.eachLine(process.argv[2], function(line, last) {
|
||||||
|
var sentence = nmea.parse(line);
|
||||||
|
if (sentence !== undefined) {
|
||||||
|
console.log(sentence);
|
||||||
|
} else {
|
||||||
|
console.error("Parse error:" + line);
|
||||||
|
}
|
||||||
|
return !last;
|
||||||
|
});
|
Loading…
Reference in New Issue