Universal NMEA 0183 Parser / Formatter in C # (+ port in JAVA)

Prefix


No matter how trite it may sound, but looking for a ready-made solution that could (in my opinion) fully support working with NMEA messages, I did not find it.

Having studied the official document , I was wholly imbued with the idea to certainly realize this, and without thinking twice , I took myself to the show.

Plot


Forgive me, knowledgeable people, but for the sake of clarity, I will nevertheless briefly describe the physics of the phenomenon.
So, the message of the NMEA standard, in the standard itself is called "sentence", the one who these "sentences" "speaks" - "Talker". For example, a GPS-application within the NMEA has the identifier “GP”, and our answer to Cheberlein is “GL”.

Existing solutions worked either only with these two types of devices, and in the best case they understood various specific for specific manufacturers (Germin, UBLox, etc.) team receivers.
And who knows, suddenly it will urgently be necessary to interpret the data coming from the atomic clock (Talker: ZA), or position yourself using the Loran-C system (Talker: LC), but the possibility of chatting with autopilot (Talker: AG) cannot be ruled out at all!



In fact, the standard describes 34 types of devices that act as talkers, about 74 types of messages (sentence), the possibility of using a proprietary code (Talker: P) - its own set of commands at the discretion of the device manufacturer. And it would be just a shame to take and not cover this whole kitchen under a single namespace!

In general, I started from the desired result. As a result, the task was set up so that the following code worked:

NMEASentence parsedSentence = NMEAParser.Parse(sourceString);


and further:

string NMEASentenceString = NMEAParser.Build(parsedSentence);


And so that the NMEASentence from the inside looks something like this:

public sealed class NMEASentence
{
   public   TalkerIdentifiers TalkerID;
   public   SentenceIdentifiers SentenceID;
   public   object[] parameters;
}


By the way, the general view of NMEA sentence has this:

$[parameter 1], [parameter 2], ... [<* checksum>]

This is an ASCII string no longer than 82 characters, with a "$" at the beginning and at the end. There is no difficulty in sorting such a string into parts - everything is done with simple string.Split () ;, the problem is how to check and parse specific expressions.
The standard describes several basic data types, in the standard documentation they are indicated as follows:

“x” - integer
“xx” - real
“c - c” - string
“hh” - hexadecimal
“llll.ll” - latitude
“yyyyy.yy "- longitude
" hhmmss.ss "- time
" ddmmyy "- date
" a "- the character

still has a magic string" ... ", meaning that all subsequent message parameters are of the same type as the previous one.

To implement parsing, various options were thought out, starting from inheriting each command from something basic and implementing the logic of each command separately, to heuristically parse each parameter.

The solution turned out to be on the surface: apply the antipattern Magic strings !

Since all documents describe the message format in this form:

$ GPRMA, A, llll.ll, a, yyyyy.yy, a, xx, xx, xx, xx, xx, a

so let them be stored in the code - at the same time adding support for new messages is extremely simple:
1) copy the description from the manual
2) paste it into the code
3) ???
4) PROFIT

Under three questions the following idea.
All message descriptions are stored in the dictionary, where the key is the message identifier, enum:

public enum SentencesIdentifiers
{
   AAM,
   ALM,
   APA,
   ...
}


and the meaning, the desired "magic line" from the manual. It looks like this:

public static Dictionary SentencesFormats =
            new Dictionary() { { SentenceIdentifiers.AAM, "A,A,x.x,N,c--c" },
                                                            { SentenceIdentifiers.ALM, "x.x,x.x,xx,x.x,hh,hhhh,hh,hhhh,hhhhhh,hhhhhh,hhhhhh,hhhhhh,hhh,hhh" },
                                                            { SentenceIdentifiers.APB, "A,A,x.x,a,N,A,A,x.x,a,c--c,x.x,a,x.x,a" },
            ...


What to do with it now?
The output is this: having selected the message parameters and its identifier, determine its format description from the SentencesFormats dictionary , which is then divided into components, and then fed to the input of some method:

private static object ParseToken(string token, string formatter)
{
     ???
}


The most awkward part of the entire system, by all laws and alphabets, will settle under these three questions, in the form of some kind of long switch:

switch (formatter)
{
   case "x.x": 
   {
       return double.Parse(token, CultureInfo.Invariant);
   }
   ...
   default:
      return token; // просто вернуть исходную строку, если непонятно, что с ней делать
}


On this bottleneck, all the flexibility of the system would be lost, which is not good, so it is best to do this:

return parsers[format](token);


Under the name parsers just sugar itself is hiding:

private static Dictionary> parsers = new Dictionary>()
        {
            { "x", x => int.Parse(x) },
            ...
            { "hh", x => Convert.ToByte(x, 16) },
            ...
            { "x.x", x => double.Parse(x, CultureInfo.InvariantCulture) },
            { "c--c", x => x },
            { "llll.ll", x => ParseLatitude(x) },
            { "yyyyy.yy", x => ParseLongitude(x) },
            { "hhmmss.ss", x => ParseCommonTime(x) },
            { "ddmmyy", x => ParseCommonDate(x) },
            ...
        };


As you can see, this is a dictionary, where the key is a formatting string, and the value is a function that makes the parameter string turn into an object.

Such a dictionary can be serialized and easily expanded with new entries, as well as a list of supported messages.

The situation is similar with proprietary messages, with slight deviations from the above scenario.

Postfix


The ideas described above were fully implemented in the NMEAParser library.
There is also a complete list of manufacturer codes, a list of 222 anchor points (Datums, DOP).

I would like to attach an archive with the library source codes to the topic, but since there is no such opportunity, forgive me the link to my own article on CodeProject, not for my own PR - but I tried for people so that the work would not be wasted.

I will be glad to hear criticism, wishes and suggestions.

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~
Update: Java port appears in the specified CodeProject link

Also popular now: