Skip to content

Latest commit

 

History

History
214 lines (150 loc) · 7.53 KB

File metadata and controls

214 lines (150 loc) · 7.53 KB

@signalk/nmea0183-utilities

CI npm version License

Small, strictly-typed TypeScript helpers for parsing NMEA 0183 sentences into Signal K-flavoured SI units. Used under the hood by @signalk/nmea0183-signalk and available standalone for plugin authors.

Installation

npm install @signalk/nmea0183-utilities

Requires Node >=22.

Quick tour

import utils from '@signalk/nmea0183-utilities'
// Or: const utils = require('@signalk/nmea0183-utilities')
// Or: import { valid, transform, coordinate } from '@signalk/nmea0183-utilities'

// 1. Validate a sentence and its *XX checksum
utils.valid('$GPBOD,045.,T,023.,M,DEST,START*01') // -> true
utils.valid('$GPBOD,045.,T,023.,M,DEST,START*AA') // -> false (bad checksum)
utils.valid('$GPBOD,045.,T,023.,M,DEST,START', false) // -> true (skip checksum)

// 2. Compute and append a *XX checksum
utils.appendChecksum('$GPBOD,045.,T,023.,M,DEST,START')
// -> '$GPBOD,045.,T,023.,M,DEST,START*01'

// 3. Convert between units (SI-first)
utils.transform(10, 'knots', 'ms') // -> 5.144... m/s
utils.transform(100, 'c', 'k') // -> 373.15 K
utils.transform(33, 'ft', 'm') // -> 10.058... m

// 4. Parse a latitude/longitude pair
utils.coordinate('5222.3277', 'N') // -> 52.372128... (decimal degrees)
utils.coordinate('00454.5824', 'W') // -> -4.909706... (W/S negative)

// 5. Sanity-check a position
utils.isValidPosition(52.37, -4.91) // -> true
utils.isValidPosition(NaN, 0) // -> false

// 6. Build an ISO-8601 timestamp from NMEA time (HHMMSS[.FFF]) + date (DDMMYY)
utils.timestamp('173456.75', '050426') // -> '2026-04-05T17:34:56.750Z'
utils.timestamp() // -> current UTC as ISO string

// 7. Decode a magnetic-variation field
utils.magneticVariation(3.5, 'E') // -> 3.5    (east positive)
utils.magneticVariation(3.5, 'W') // -> -3.5   (west negative)

// 8. Build the `source` object Signal K deltas expect
utils.source('GPGGA')
// -> { type: 'NMEA0183', label: 'signalk-parser-nmea0183', sentence: 'GPGGA' }

API

All functions are named exports; a default export aggregates them so import utils from '@signalk/nmea0183-utilities' works as well.

Validation

valid(sentence, validateChecksum?) => boolean

Returns true if sentence looks like a well-formed NMEA 0183 frame. Requires a $ or ! prefix and, when validateChecksum is true (the default), a matching *XX checksum suffix. Pass false to accept prefix-only frames.

appendChecksum(sentence) => string

Returns sentence with a computed *XX suffix appended, uppercase hex, zero-padded. Returns the input unchanged if it already contains *, doesn't start with $/!, or is an unsupported shape.

Unit conversion

transform(value, from, to) => number

Converts value between the units below. value may be a number or a numeric string (it's coerced via float). from and to are typed as UnitFormat and are case-sensitive in TypeScript callers — pass an unknown pair and it throws unsupported conversion: <from> -> <to>.

Family Units
Distance km, nm, m, ft, fa
Speed knots, kph, ms, mph
Angle deg, rad
Temperature c, k, f

All combinations within each family are supported. Same-unit transform(x, 'm', 'm') short-circuits.

RATIOS

The lookup table used internally — exported for downstream code that needs the raw constants (e.g. RATIOS.NM_IN_KM === 1.852).

Coordinates & position

coordinate(value, pole) => number

Parses an NMEA coordinate (DDDMM.MMMM, DDMM.MMMM) to decimal degrees. pole is a Pole ('N' | 'S' | 'E' | 'W', uppercase only per IEC 61162-1). 'S' and 'W' flip the sign. Unknown pole letters throw unsupported pole: <value>.

isValidPosition(latitude, longitude) => boolean

true iff both inputs are finite numbers within [-90, 90] / [-180, 180]. Rejects NaN and ±Infinity.

magneticVariation(degrees, pole) => number

degrees is the magnitude; pole is a Pole. East is positive, West is negative; 'N' | 'E' pass through, 'S' | 'W' negate. Unknown pole letters throw.

Timestamp

timestamp(time?, date?) => string

Assembles an ISO-8601 UTC string from the two fields NMEA sentences carry.

  • time is HHMMSS with an optional fractional tail (HHMMSS.sss). The first three digits of the tail become milliseconds; extra digits are truncated, a missing or non-digit tail falls through to .000Z. When omitted, the current wall-clock time is used.
  • date is DDMMYY. 2-digit years follow IEC 61162-1: YY < 8020YY, YY >= 8019YY. When omitted, today's UTC date is used.

Sentence metadata

source(sentence?) => SignalKSource

Returns the source sub-object Signal K deltas expect:

{ type: 'NMEA0183', label: 'signalk-parser-nmea0183', sentence: <sentence> }

sentence defaults to '' when the argument is falsy.

Numeric parsing

int(n) => number

parseInt(n, 10) that returns 0 instead of NaN when the input doesn't parse. Accepts unknown so int(null), int(undefined), int('abc'), int(NaN) all return 0.

float(n) => number

Same idea for parseFloat. Accepts numbers or numeric strings. Returns 0.0 on parse failure.

intOrNull(n) => number | null / floatOrNull(n) => number | null

Null-preserving variants of int / float. An NMEA 0183 null field (IEC 61162-1 §7.2.3.4) signals "sensor working, value not available" and must not be confused with a legitimate zero. intOrNull(''), intOrNull(null), intOrNull('abc') all return null; intOrNull('0') returns 0. Prefer these over int/float when the caller wants to pass the not-available semantic through to Signal K (which treats null the same way).

transformOrNull(value, from, to) => number | null

Null-short-circuiting unit conversion. Identical to transform for real input; returns null when value is empty / null / undefined / non-numeric. Unsupported unit pairs still throw — the null short-circuit runs first, so a missing field with an unknown unit pair is still null, not an error.

magneticVariationOrNull(degrees, pole) => number | null

Null-preserving magnetic variation. Returns null when degrees or pole is missing or unparseable (including unknown / lowercase pole letters), rather than throwing or silently returning 0. 0 with a valid pole returns 0, not null.

zero(n) => string

Width-2 left-pad for integer date/time components. zero(2) === '02', zero(42) === '42', zero(-5) === '-05'. Throws TypeError on non-finite or non-integer input — feed it Math.trunc(n) if you have a float.

TypeScript

Full types ship in the package. The useful ones:

import type {
  UnitFormat, // 'km' | 'nm' | 'm' | 'ft' | 'fa' | 'knots' | ... | 'c' | 'k' | 'f'
  Pole, // 'N' | 'S' | 'E' | 'W'
  SignalKSource
} from '@signalk/nmea0183-utilities'

Both named and default imports are supported; the default is a plain object whose keys are the named exports.

License

Apache-2.0. See LICENSE.