Git Product home page Git Product logo

nmeaparser's Introduction

NMEAParser

An Arduino library to parse NMEA sentences.

NMEA is a communication standard in the marine equipment industry: GPS, anemometers,... The NMEAParser library allows you to analyze NMEA sentences and associate handlers to each of those that need to be recognized. The library provides the means to associate a handler to each identifier and also provides functions to retrieve the arguments of the sentence in different data formats: character, integer, float, character string.

Changelog

  • 1.1 : Added joker in type. Remove binaries from extra
  • 1.0 : Initial release

Memory footprint

On an Arduino Uno, an instance of a NMEAParser requires 97 bytes with only one handler. 8 bytes per additional handler are required.

Using NMEAParser

As usual, the library should be included at the beginning of the sketch.

#include <NMEAParser.h>

Then, you must instantiate a parser as follows:

NMEAParser<4> parser;

The 4 is the maximum number of handlers desired, parser is the name of the object that will allow the analysis of NMEA sentences. you can of course use the name you want.

In setup you configure your parser as you wish using the following functions.

void addHandler(<type>, <handler>)

where <type> is a character string and the type of sentence to recongnize, and <handler> the function to call when a sentence is recognize. <type> can be a string stored un RAM or a string stored in flash : F("ASTRI"). If <type> has more than 5 characters, it is trucated.

For example, suppose you want to recognize the following sounder sentences which give the water depth below keel (DBK) and below surface (DBS):

$SDDBK,...

and

$SDDBS,...

You will install two handlers as follows (assuming your parsing object is named parser):

parser.addHandler("SDDBK", handleDepthBelowKeel);
parser.addHanlder("SDDBS", handleDepthBelowSurface);

handleDepthBelowKeel and handleDepthBelowSurface are functions that will be called when sentences are recognized.

With version 1.1, <type> may include hyphens. An hyphens matches any character. For instance if you want the handler to match all sentences coming from the sounder, you would write:

parser.addHandler("SD---", handleSounder);

handleSounder function would be called for any sentence with the type beginning with SD.

void setDefaultHandler(<handler>)

When a sentence is succefully parsed but none of the handler correspond to it, <handler> is called. It is a function corresponding to a void f(void) prototype. By default, no function is called.

void setErrorHandler(<handler>)

When a sentence is malformed : too long, wrong characters, etc, <handler> is called. It is a function corresponding to a void f(void) prototype. By default, no function is called.

void setHandleCRC(<doCRC>)

Specifies whether the CRC is checked or not. By default, the CRC is checked. If you do not want CRC verification, pass false to setHandleCRC.


In the handlers, you will get the arguments of the sentence, the sentence type or the error if any by using the following functions:

bool getArg(<argnum>, <argval>)

is used to get the arguments of the sentence. <argnum> is the number of the argument, starting at 0 for the argument that follows the identifier. <argval is a variable where the argument value will be stored if successful. getArg returns a boolean which is true if successfull, false if not.

Continuing with the example, both sentences have the same 6 arguments

  • Argument 0 is a float number giving the depth in feet.
  • Argument 1 is a f for feet.
  • Argument 2 is a float number giving the depth in meters.
  • Argument 3 is a M for Meters.
  • Argument 4 is a float number giving the depth in fathoms.
  • At last Argument 5 is a F for Fathoms.

Suppose you are interested by the depths in meters and you have two float variables to store theses data:

float depthBelowKeel;
float depthBelowSurface;

You would implement handleDepthBelowKeel and handleDepthBelowSurface as follow:

void handleDepthBelowKeel(void)
{
  float depth;
  if (parser.getArg(2, depth))
  {
    depthBelowKeel = depth;
  }
}

void handleDepthBelowSurface(void)
{
  float depth;
  if (parser.getArg(2, depth))
  {
    depthBelowSurface = depth;
  }
}

bool getType(<type>) / bool getType(<num>, <charType>)

3 versions of getType exist. The first one puts the type of the sentence in <type> which is a char *. The second one does the same but <type> is a String. Return true if a type has been parsed, false otherwise. The third one puts a character of the type at position <num>

uint8_t argCount()

Return the number of arguments.

NMEA::ErrorCode error()

Return the error. The returned code can be:

  • NMEA::NO_ERROR: no error;
  • NMEA::UNEXPECTED_CHAR: a char which is not expected in the sentence has been encountered;
  • NMEA::BUFFER_FULL: the sentence is too long to fit in the buffer;
  • NMEA::TYPE_TOO_LONG: the sentence type has more than 5 characters;
  • NMEA::CRC_ERROR: the CRC is wrong;
  • NMEA::INTERNAL_ERROR: the internal state of the parser is wrong, should not happen by the way.

Feeding characters to the parser

Characters are fed to the parser in loop, assuming we get the characters from Serial, the following way:

while (Serial.available()) {
  parser << Serial.read();
}

This can also be done in serialEvent. while could be replaced by if.

A complete example

Let's say you want to turn the Arduino's LED on and off. We define a NMEA sentence taking a single argument: 1 to turn on the LED and 0 to turn it off. The sentence can therefore be:

$ARLED,1*43

to turn the LED on and

$ARLED,0*42

to turn the LED off. We define a single handler to retrieve the argument and control the LED accordingly:

void ledHandler()
{
  Serial.print("Got ARLED with ");
  Serial.print(parser.argCount());
  Serial.println(" arguments");
  int ledState;
  if (parser.getArg(0,ledState)) {
    digitalWrite(LED_BUILTIN, ledState == 0 ? LOW : HIGH);
  }
}

We define 2 other handlers for anything else than ARLED and for errors

void errorHandler()
{
  Serial.print("*** Error : ");
  Serial.println(parser.error());
}

void unknownCommand()
{
  Serial.print("*** Unkown command : ");
  char buf[6];
  parser.getType(buf);
  Serial.println(buf);
}

In setup, the handlers are installed:

void setup() {
  Serial.begin(115200);
  parser.setErrorHandler(errorHandler);
  parser.addHandler("ARLED", ledHandler);
  parser.setDefaultHandler(unknownCommand);
  pinMode(LED_BUILTIN, OUTPUT);
}

At last in loop, we feed the parser with the chars coming from Serial.

void loop() {
  if (Serial.available()) {
    parser << Serial.read();
  }
}

Extra software

Additional software can be found in the extra directory.

NMEA sentences generator

The gen subdirectory contains nmeagen, a NMEA sentence generator program. This program generates well formed sentences with good or bad CRC. It can be used to test the parser. To build nmeagen, run the build.sh script. nmeagen takes 1 or 2 arguments. The first argument is the number of sentences to generate. The second optional one is the number of sentences with bad CRC.

Test program

The test subdirectory contains a test program that compile on Linux or Mac OS X. It takes sentences from the standard input, parse them and print out the type, the arguments and if an error occured.


Additional links

That's all folks !

nmeaparser's People

Contributors

glinnes avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar

nmeaparser's Issues

Handle NMEA statements starting with '!'

Some NMEA statements begin with '!' character. ie: !AIVDM, !AIVDO, etc.

I think this is very easy to implement. Just modify line 402 in NMEAParser.h from if (inChar == '$') { to if (inChar == '$' || inChar == '!') {

Tips to improve stability

Found two issues and wanted to share the findings:

  • I know the NMEA spec specifies 82 character max, however when using a high-accuracy GPS which needs a lot more digits to represent the precision, you go beyond the limit. I'd suggest making the buffer size configurable beyond 77. (100 works just fine for me).
  • I was getting A LOT of parsing errors. I couldn't reproduce in a console app, making me suspect the buffer. I found the following approach to run way more stable:

in setup() set timeout pretty low:

Serial.setTimeout(10);

In the loop reach in chunks:

char buf[1024];
int readCount;
void loop()
{
  if (Serial1.available()) {
    while((readCount = Serial.readBytes(buf, 1024)) > 0)
    {
      for(int i=0;i<readCount;i++)
        parser << buf[i];
    }
 }

With these two changes, all my parser errors went away.
You might want to update the doc to suggest the read approach. Might only be an issue on slow arduino's?

How to Serial.Print full received nmea message

Hi,

is there a function to print a full nmea message received after using:
if (Serial.available()) {
nmea_parse();
}
The ideia is to do a data out to the next device on the network.

Kind regards,

*** Error : 1

Hi,
I continuously receive :
*** Error : 1 sequences, and sometimes *** Error : 3.
It only parsed once a sentence in all the tests is did.
Why?
Could be due to the rate of com?
Regards.

PS I checked also the led example and gives the same error.

Connections:


Instrument sending NMEA sentences on Serial3. PC on Serial.

Type of sentences:


$PSONCMS,-0.9888,0.0072,-0.0056,-0.1491,-0.1582,-0.1052,9.8084,0.0001,-0.0016,-0.0004,0.2222,-0.1642,-1.1150,31.9*71

$GPGGA,003906.33,0000.0000,S,00000.0000,W,0,00,100.0,-17.0,M,0.0,M,,*44

$XSVEL,+000.0000,+000.0000,+000.0000*4D

CODE:


// LIBRARIES
#include <NMEAParser.h>

//INITIALISE LIBRARIES
NMEAParser<3> NMEAin; //Number of NMEA Parsers Handlers & NAME of the Parser

//INITIALISE VARIABLES
float GPS_Vx;
float GPS_Vy;
float GPS_Vz;

int incomingByte = 0; // for incoming Serial Data Bytes
char incomingChar; // for Bytes to String Data
//char incomingStr[50];

/* SETUP NMEA PARSER /
void errorHandler()
{
Serial.print("
** Error : ");
Serial.println(NMEAin.error());
}

void unknownCommand()
{
Serial.print("*** Unkown command : ");
char buf[20];
NMEAin.getType(buf);
Serial.println(buf);
}

//SENTENCES HANDLERS

void PSONCMS_Handler()
{
Serial.print("Got PSONCMS with ");
Serial.print(NMEAin.argCount());
Serial.println(" arguments");
}

void GPGGA_Handler()
{
Serial.print("Got GPGGA with ");
Serial.print(NMEAin.argCount());
Serial.println(" arguments");
}

void XSVEL_Handler()
{
Serial.print("Got XSVEL with ");
Serial.print(NMEAin.argCount());
Serial.println(" arguments");
float GPS_Vx;
if (NMEAin.getArg(0,GPS_Vx)) Serial.println(GPS_Vx);
//if (NMEAin.getArg(1,GPS_Vy)) Serial.println(GPS_Vy);
//if (NMEAin.getArg(2,GPS_Vz)) Serial.println(GPS_Vz);
}
/* ----------------- */

void setup() {
// Turn on serial monitor and set baud rate
Serial3.begin(115200); // GNSS-IMU [Serial3]
Serial.begin(115200); // PC [Serial0] | MAIN-ARDUINO [Serial1]
Serial.write("COM On");

NMEAin.setErrorHandler(errorHandler);
NMEAin.addHandler("PSON-", PSONCMS_Handler);
NMEAin.addHandler("GPGGA", GPGGA_Handler);
NMEAin.addHandler("XSVEL", XSVEL_Handler);
//NMEAin.setDefaultHandler(unknownCommand);
}

void loop() {

while (Serial3.available()) {

  String incomingStr = Serial3.readStringUntil('\n');//READ UNTIL NEW LINE
  Serial.println(incomingStr);                       //PRINT

    for (uint8_t i = 0; i < (incomingStr.length()); i++) {
      NMEAin << incomingStr[i];
   }
  
  //NMEAin << Serial3.read();                        //FORWARD INCOMING CHAR TO PARSER DIRECTLY (Alternative still not working)

}
}

Returning large number of Error 1 responses

Very neat library! I was able to pull depth and temperature data from my transducer and Arduino MEGA but I'm finding that there are many different error outputs for the temperature string. How can I debug or check the full string being sent for the temperature sentence? How does the library determine that a character is out of place?

ERROR : 1

i, a implement the library in my project that read NMEA sentence from WiFI, This is code:
....
while (client.available()) {
String Sentence = client.readStringUntil('\r');
int len = Sentence.length() + 1;
char WindSentence[len];
Sentence.toCharArray(WindSentence, len);
for (uint8_t i = 0; i <= strlen(WindSentence); i++) {
parser << WindSentence[i];
}
}
........

And i recive only error.
the sentence in question is : $WIMWV,243.7,T,64.8,N,A*1D
but this is the response:

  • Error : 1
    *** Error : 1
    *** Error : 1
    *** Error : 1

$WIMWV,243.7,T,64.8,N,A*1D
*** Error : 1

$WIMWD,243.7,T,243.7,M,64.8,N,33.3,M*53
*** Error : 1

$WIMWV,271.0,R,64.0,N,A*15
*** Error : 1

$IIMTW,5.4,C*22
*** Error : 1

$SDDPT,59.6,01*42
*** Error : 1

Have you any idea?

Thank's
Stefano

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.