@signalk/nmea0183-utilities
Version:
Various utilities for transforming NMEA0183 units into SI units for use in SK.
215 lines (150 loc) • 7.71 kB
Markdown
# @signalk/nmea0183-utilities
[](https://github.com/SignalK/nmea0183-utilities/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/@signalk/nmea0183-utilities)
[](https://github.com/SignalK/nmea0183-utilities/blob/master/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`](https://github.com/SignalK/nmea0183-signalk) and
available standalone for plugin authors.
## Installation
```sh
npm install @signalk/nmea0183-utilities
```
Requires Node `>=22`.
## Quick tour
```ts
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 < 80` →
`20YY`, `YY >= 80` → `19YY`. When omitted, today's UTC date is used.
### Sentence metadata
#### `source(sentence?) => SignalKSource`
Returns the `source` sub-object Signal K deltas expect:
```ts
{ 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:
```ts
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](LICENSE).