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.
npm install @signalk/nmea0183-utilitiesRequires Node >=22.
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' }All functions are named exports; a default export aggregates them so
import utils from '@signalk/nmea0183-utilities' works as well.
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.
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.
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.
The lookup table used internally — exported for downstream code that
needs the raw constants (e.g. RATIOS.NM_IN_KM === 1.852).
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>.
true iff both inputs are finite numbers within [-90, 90] /
[-180, 180]. Rejects NaN and ±Infinity.
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.
Assembles an ISO-8601 UTC string from the two fields NMEA sentences carry.
timeisHHMMSSwith 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.dateisDDMMYY. 2-digit years follow IEC 61162-1:YY < 80→20YY,YY >= 80→19YY. When omitted, today's UTC date is used.
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.
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.
Same idea for parseFloat. Accepts numbers or numeric strings. Returns
0.0 on parse failure.
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).
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.
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.
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.
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.
Apache-2.0. See LICENSE.