init commit

master
叶志超 2023-10-16 12:09:14 +08:00
commit 94f58cd552
50 changed files with 1968 additions and 0 deletions

33
.github/workflows/node.js.yml vendored Normal file
View File

@ -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

51
.github/workflows/npm-publish.yml vendored Normal file
View File

@ -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}}

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
.DS_Store
node_modules

20
LICENSE Normal file
View File

@ -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.

45
README.md Normal file
View File

@ -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;
````

8
bin/parse-nmea Normal file
View File

@ -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);;

58
codecs/APB.js Normal file
View File

@ -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]
}
}

42
codecs/BWC.js Normal file
View File

@ -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]
}
}

45
codecs/DBT.js Normal file
View File

@ -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);
}

72
codecs/GGA.js Normal file
View File

@ -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);
}

52
codecs/GLL.js Normal file
View File

@ -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);
}

48
codecs/GRMT.js Normal file
View File

@ -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]
}
}

45
codecs/GSA.js Normal file
View File

@ -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]
};
}

23
codecs/GSV.js Normal file
View File

@ -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
};
}

36
codecs/HDG.js Normal file
View File

@ -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]
}
};

34
codecs/HDM.js Normal file
View File

@ -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);
};

34
codecs/HDT.js Normal file
View File

@ -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);
};

44
codecs/MWV.js Normal file
View File

@ -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);
}

29
codecs/RDID.js Normal file
View File

@ -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],
}
}

51
codecs/RMB.js Normal file
View File

@ -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
};
}

46
codecs/RMC.js Normal file
View File

@ -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]
};
}

35
codecs/ROT.js Normal file
View File

@ -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);
}

28
codecs/RSA.js Normal file
View File

@ -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]
}
}

25
codecs/TXT.js Normal file
View File

@ -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]
};
}

70
codecs/VTG.js Normal file
View File

@ -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);
}

39
codecs/VWR.js Normal file
View File

@ -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]
}
}

45
codecs/ZDA.js Normal file
View File

@ -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);
}

356
helpers.js Normal file
View File

@ -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));
};

49
liner.js Normal file
View File

@ -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;

124
nmea.js Normal file
View File

@ -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;
};

25
package.json Normal file
View File

@ -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"
}
}

31
test.js Normal file
View File

@ -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]));
};

10
test/APBtest.js Normal file
View File

@ -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');
});
});

10
test/BWCtest.js Normal file
View File

@ -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');
});
});

23
test/DBTtest.js Normal file
View File

@ -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");
});
});

44
test/GGAtest.js Normal file
View File

@ -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");
});
});

29
test/GLLtest.js Normal file
View File

@ -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");
});
});

9
test/GRMTtest.js Normal file
View File

@ -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');
});
});

9
test/GSAtest.js Normal file
View File

@ -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');
});
});

9
test/GSVtest.js Normal file
View File

@ -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');
});
});

19
test/HDMtest.js Normal file
View File

@ -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");
});
});

19
test/HDTtest.js Normal file
View File

@ -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");
});
});

27
test/MWVtest.js Normal file
View File

@ -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");
});
});

10
test/RDIDtest.js Normal file
View File

@ -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');
});
});

9
test/RMCtest.js Normal file
View File

@ -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');
});
});

18
test/ROTtest.js Normal file
View File

@ -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');
});
});

24
test/VTGtest.js Normal file
View File

@ -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");
});
});

22
test/helperstest.js Normal file
View File

@ -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);
});
});

18
test/nmeaTest.js Normal file
View File

@ -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");
});
});

14
util/parse.js Normal file
View File

@ -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;
});