npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@coremarine/nmea-parser

v2.1.3

Published

Library to parse NMEA 0183 sentences

Downloads

72

Readme

NMEA parser

npm (scoped) publish npm

NMEA Parser is a library to parse NMEA 0183 sentences.

NMEA 0183, or just NMEA, is an standard ASCII text protocol typically used for GNSS (GPS) devices and naval tools.

This library parse ALL NMEA-like sentences (sentences that follow these rules):

  • ASCII string
  • Start with $
  • Fields separated by ,
  • An * character splitting info and checksum
  • Two hexadecimal digits of checksum
  • End with \r\n

If the parser knows the NMEA sentences it gives more metadata. Known frames are:

  • AAM
  • GGA
  • HDT
  • ZDA

NMEA is a backwards compatible protocol from 4.x versions until 2.00.

How to use it

import { NMEAParser } from '@coremarine/nmea-parser'
// NMEA parser
const parser = new NMEAParser()

Parse NMEA string data

The main behaviour it's just to create a parser object and with its method parseData() get the parsed sentences.

// NMEA ascii string with multiple sentences
const input = '$GPGGA,...\r\n$GPAAM,...\r\n'
const output: NMEASentence[] = parser.parseData(input)

The output it is an array of parsed sentences which have this type

type NMEASentence = {
  // Sentence ID
  id: string,
  // Array with ordered fields and their metadata
  payload: Array<{
    name: string,
    value: string | number | bigint | boolean | null,
    type: 'string' | 'boolean' | 'uint8' | 'uint16' | 'uint32' | 'uint64' | 'int8' | 'int16' | 'int32' | 'int64' | 'float32' | 'float64' | 'unknown',
    units?: string,
    description?: string,
    metadata?: any
  }>,
  // Metadata which can be computed fields from payload fields
  metadata?: any,
  // Protocol information
  protocol: {
    name: string,
    standard: boolean,
    version: string,
  },
  // UTC timestamp when the sentence was parsed
  received: number,
  // Whole ASCII string sentence
  sample: string,
  // Sentence checksum
  checksum: {
    sample: string,
    value: number
  }
  // Sentence talker
  talker?: {
    value: string,
    description: string
  }
}

If the sentence is unknown for the parser you have:

  • protocol is equal to { name: 'unknown' }
  • In payload each element is equal to { name: 'unknown', value: string, type: string }

Feed the parser

Another feature is to feed or train the parser to know more sentences. You can do it with a user friendly YAML file.

Look the section Feeding the parser

Extra features: memory and internal buffer

You can enable or disable memory in the parser. Why?

  • Imagine you are streaming NMEA data into the parser
  • You just enters string in slots so a frame could be splitted into string slots
  • With memory the parser remember the last half frame
  • So you can finally parse with the next string input

Generate fake sentences

It can be asked to the parser if a sentence is supported or known with the ID of the sentence and the method getSentence(). If the sentence is supported, parser can generate a fake sentence which is right in terms of NMEA-like requirements but just contains garbage. To get that is with method getFakeSentenceByID(). If the sentence it is not supported, it will returns null.

type Sentence = {
  // ID of the sentence
  id: string,
  // Protocol info
  protocol: {
    name: string,
    standard: boolean,
    version?: string
  },
  // Ordered fields with its info
  payload: Array<{
    name: string,
    type: 'string' | 'boolean' | 'uint8' | 'uint16' | 'uint32' | 'uint64' | 'int8' | 'int16' | 'int32' | 'int64' | 'float32' | 'float64',
    units?: string,
    description?: string
  }>,
  // Optional talker
  talker?: {
    value: string,
    description: string
  }
  // Optional description
  description?: string
}

// GET sentence info
const id = 'AAM'
const sentenceInfo: null | Sentence = parser.getSentence(id)
// GET fake sentence
const fakeSentence: string | null = parser.getFakeSentenceByID(id)

Feeding the parser (adding known sentences)

One of the greatest features of the parser is you can expand with more NMEA-like sentences. Standard or propietary sentences, it doesn't mind.

If you make smarter your parser, it will give you more metadata of each sentence and all the values will be in their right type.

Feeding the parser is with its method addProtocols(). You can use three ways (order by parser priority):

  1. YAML file path
    • Is the ProtocolsInput.file property
  2. YAML file string content
    • Is the ProtocolsInput.content property
  3. Protocol (JS) object
    • Is the ProtocolsInput.protocols property
type ProtocolsInput = {
  // YAML file path
  file?: string,
  // YAML file string content
  content?: string,
  // JS object similar
  protcols?: Array<Protocol>
}

let input: ProtocolsInput

...

parser.addProtocols(input)

YAML file / Protocols File

The YAML file is the most friendly way to feed the parser. It has this structure:

protocols:
  # Array of protocols
  - protocol: <name>
    version: <semantic version>
    standard: <true or false>
    # Array of sentences
    sentences:
      # Each sentence has this structure
      - id: <ID of sentence 1>
        description: <optional description>
        # Array of fields
        payload:
          # Each field has this structure
          - name: <field 1>
            type: <type>
            units: <optional units>
            description: <optional note>

It will be parsed into an Protocol object then (see next section).

Example of a yaml file

protocols:
  # NMEA 3.1
  - protocol: NMEA
    version: '3.1'
    standard: true
    sentences:
      - id: AAM
        description: Waypoint Arrival Alarm
        payload:
          # 1
          - name: status
            type: string
            description: "BOOLEAN\n
            
              A = arrival circle entered\n

              V = arrival circle not passed"
          # 2
          - name: status
            type: string
            description: "BOOLEAN\n
            
              A = perpendicular passed at waypoint\n
              
              V = perpendicular not passed"
          # 3
          - name: arrival_circle_radius
            type: float32
          # 4
          - name: radius_units
            type: string
            units: nautic miles
          # 5
          - name: waypoint_id
            type: string
      - id: GGA
        description: Global Positioning System Fix Data
        payload:
          # 1
          - name: utc_position
            type: string
            units: ms
          # 2
          - name: latitude
            type: string
            units: deg
          # 3
          - name: latitude_direction
            type: string
            description: "N: North\n
              S: South"
          # 4
          - name: longitude
            type: string
            units: deg
          # 5
          - name: longitude_direction
            type: string
            description: "E - East\n
              W - West"
          # 6
          - name: gps_quality
            type: int8
            description: "0: Fix not valid\n
              1: GPS fix\n
              2: Differential GPS fix (DGNSS), SBAS, OmniSTAR VBS, Beacon, RTX in GVBS mode\n
              3: Not applicable\n
              4: RTK Fixed, xFill\n
              5: RTK Float, OmniSTAR XP/HP, Location RTK, RTX\n
              6: INS Dead reckoning\n
              7: Manual Input Mode\n
              8: Simulator Mode"
          # 7
          - name: satellites
            type: uint8
          # 8
          - name: hdop
            type: float64
          # 9
          - name: altitude
            type: float64
            units: m
            description: "Orthometric height Mean-Sea-Level (MSL reference)"
          # 10
          - name: altitude_units
            type: string
            units: m
          # 11
          - name: geoid_separation
            type: float64
            units: m
            description: "Geoidal Separation: the difference between the WGS-84 earth ellipsoid surface and mean-sea-level (geoid) surface, \"-\" = mean-sea-level surface below WGS-84 ellipsoid surface."
          # 12
          - name: geoid_separation_units
            type: string
            units: m
          # 13
          - name: age_of_differential_gps_data
            type: float64
            units: sec
            description: "Time in seconds since last SC104 Type 1 or 9 update, null field when DGPS is not used300"
          # 14
          - name: reference_station_id
            type: uint16
            description: "Reference station ID, range 0000 to 4095. A null field when any reference station ID is selected and no corrections are received. See table below for a description of the field values.\n

              0002 CenterPoint or ViewPoint RTX\n

              0005 RangePoint RTX\n

              0006 FieldPoint RTX\n

              0100 VBS\n

              1000 HP\n

              1001 HP/XP (Orbits)\n

              1002 HP/G2 (Orbits)\n
              
              1008 XP (GPS)\n
              
              1012 G2 (GPS)\n
              
              1013 G2 (GPS/GLONASS)\n
              
              1014 G2 (GLONASS)\n
              
              1016 HP/XP (GPS)\n
              
              1020 HP/G2 (GPS)\n
              
              1021 HP/G2 (GPS/GLONASS)"
      - id: HDT
        description: Heading - True
        payload:
          # 1
          - name: heading
            type: float32
            description: "Heading, degrees True"
          # 2
          - name: "true"
            type: string
            description: "T = True"
      - id: ZDA
        description: Time & Date - UTC, day, month, year and local time zone
        payload:
          # 1
          - name: utc_time
            type: string
            description: "UTC time (hours, minutes, seconds, may have fractional subseconds)"
          # 2
          - name: day
            type: int8
            description: "Day, 01 to 31"
          # 3
          - name: month
            type: int8
            description: "Month, 01 to 12"
          # 4
          - name: year
            type: int16
            description: "Year (4 digits)"
          # 5
          - name: local_zone_hours
            type: int8
            description: "Local zone description, 00 to +- 13 hours"
          # 6
          - name: local_zone_minutes
            type: int8
            description: "Local zone minutes description, 00 to 59, apply same sign as local hours"


  # Propietary GYROCOMPAS1
  - protocol: GYROCOMPAS1
    standard: false
    sentences:
      - id: HEHDT
        payload:
          - name: heading
            type: float
            units: deg
          - name: symbol
            type: string
      - id: PHTRO
        payload:
          - name: pitch
            type: float
            units: deg
          - name: pitch_direction
            type: string
            description: M bow up, P bow down
          - name: roll
            type: float
            units: deg
          - name: roll_direction
            type: string
            description: M bow up, P bow down
      - id: PHINF
        payload:
          - name: status
            type: string

Protocol (JS) Object

It is an object which has the next type.

type Protocol = {
  // Protocol name
  protocol: string,
  // If the protocol is NMEA stantard or propietary (false by default)
  standard?: boolean = false,
  // Semantic version
  version?: string,
  // Array of sentences
  sentences: Array<{
    // Sentence ID
    id: string,
    // Each field metadata
    payload: Array<{
      name: string,
      type: 'string' | 'boolean' | 'uint8' | 'uint16' | 'uint32' | 'int8' | 'int16' | 'int32' | 'float32' | 'float64',
      units?: string,
      note?: string,
    }>
    // Optional description
    description?: string
  }>
}

API

  • Get parsed data

    // NMEA ascii string with multiple sentences
    const input = '$GPGGA,...\r\n$GPAAM,...\r\n'
    const output: NMEASentence[] = parser.parseData(input)
  • Feed the parser (add more sentences)

    let output: NMEASentence[]
    // From YAML file path
    const YAML_FILE: string = './my_yaml_file.yaml'
    output = parser.addProtocols({ file: YAML_FILE })
    // From YAML file content
    const YAML_CONTENT: string = fs.readFileSync(YAML_FILE, 'utf8')
    output = parser.addProtocols({ content: YAML_CONTENT })
    // From Protocol object
    const PROTOCOL_OBJECT: Protocol[] = [{...}, {...}, ..., {...}]
    output = parser.addProtocols({ content: PROTOCOL_OBJECT })
  • Get known protocols with their sentences

    type StoredSentence = {
      id: string,
      protocol: {
        name: string,
        standard: boolean,
        version?: string
      },
      payload: Array<{
        name: string,
        type: 'string' | 'boolean' | 'uint8' | 'uint16' | 'uint32' | 'int8' | 'int16' | 'int32' | 'float32' | 'float64',
        units?: string,
        description?: string
      }>,
      description?: string
    }
    
    type ProtocolOutput = Record<string, StoredSentence>
    
    // GET protocols
    const knownProtocols: ProtocolOutput = parser.getSentencesByProtocol()
  • Get sentence info by id

    type Sentence = {
      // ID of the sentence
      id: string,
      // Protocol info
      protocol: {
        name: string,
        standard: boolean,
        version?: string
      },
      // Ordered fields with its info
      payload: Array<{
        name: string,
        type: 'string' | 'boolean' | 'uint8' | 'uint16' | 'uint32' | 'int8' | 'int16' | 'int32' | 'float32' | 'float64',
        units?: string,
        note?: string
      }>,
      // Optional talker
      talker?: null | { id: string, description: string }
      // Optional description
      description?: string
    }
    
    // GET sentence
    const id = 'AAM'
    const sentenceInfo: null | Sentence = parser.getSentence(id)
  • Get fake NMEA-like sentence by id

    const id: string = 'AAM'
    // GET fake sentence
    const fakeSentence: string | null = parser.getFakeSentenceByID(id)
  • Get / set memory and / or buffer character length

    // Get
    const memory = parser.memory
    const charactersLimit = parser.bufferLimit
    // Set
    parser.memory = !memory
    parser.bufferLimit = charactersLimit + 10

Notes

bufferLimit it is used to stored an incompleted frame if memory is enabled. So you can add the needed it data later.

bufferLimit is set to 1024 characters which is more than enough to store an incompleted frame.

NMEA standard fix the max length of a frame to 82 characters (flags included)

It is not recommended you change its value if you don't understand well the NMEA protocol pr how it works.