Closed hiltswaltts closed 6 years ago
I want to extract the GPS-UTC leap second offset from a Garmin $PGRMF sentence
You're the first one to try this. Congrats?
Generally, you should follow the example of the ubloxNMEA class (in ubxNMEA.h & cpp). You need
NeoGPS/src/Garmin
subdirectory for your new class (in grmNMEA.h & cpp)parseMfrID
method to watch for the characters "GRM" (the Garmin manufacturer ID)parseField
that handles the F sentence or delegates to NMEAGPS for all the standard sentences:bool GarminNMEA::parseField(char chr)
{
if (nmeaMessage >= (nmea_msg_t) PGRM_FIRST_MSG) {
switch (nmeaMessage) {
case PGRMF: return parseF( chr );
default:
break;
}
} else
// Delegate
return NMEAGPS::parseField(chr);
return true;
} // parseField
Then declare and provide the parse function for the F sentence, using parseGGA
in NMEAGPS.cpp as an example. Something like this:
bool GarminNMEA::parseF( char chr )
{
#ifdef GARMINGPS_PARSE_F
switch (fieldIndex) {
case 3: return parseDDMMYY( chr );
case 4: return parseTime( chr );
case 5: return parseLeapSeconds( chr ); // <-- you will write this
PARSE_LOC(6);
//case 10: return parseFix( char ); // not needed, because next field sets status
case 11: return parseFix( chr );
//case 12: return parseSpeed( chr ); // a little messier because units are km/h, not kts/h
case 13: return parseHeading( chr );
case 14: return parsePDOP( chr );
//case 15: return parseTDOP( chr ); // not yet supported
}
#endif
return true;
} // parseF
Your GarminNMEA class will also declare and implement the parseLeapSeconds
function, which should use one of the provided parseInt
functions. I used parseSatellites
from NMEAGPS.cpp as an example:
//----------------------------------------------------------------
bool GarmingNMEA::parseLeapSeconds( char chr )
{
static uint8_t newLeapSeconds; // just used for parsing
if (parseInt( newLeapSeconds, chr )) {
GPSTime::.leap_seconds = newLeapSeconds; // assign parsed value to global
}
return true;
} // parseGPSleapSeconds
Notice that this uses the leap_seconds
variable declared in GPSTime.h. You will not have to add anything to gps_fix
. When you read a fix
, you can access GPSTime::leap_seconds
:
if (gps.available( gpsPort )) {
fix = gps.read();
Serial.print( F("GPS leap seconds = ") );
Serial.println( GPSTime::leap_seconds );
One additional thing: you must also provide a msg_table
function. This data structure identifies all the message types that can be parsed by your class. It can also be linked to other tables (e.g., the standard NMEA message table).
The ubloxNMEA class does not need that, because all the PUBX sentences are numbered: they are all $PUBX,##
. There is no message type, like $PUBXFOO
. The ublox message type is simply the numeric value of the first field.
Simply (!) declare your own static PROGMEM message table in your grmNMEA.cpp
, with one entry for the "F" sentence. The types for this data structure are declared in NMEAGPSprivate.h
. Using NMEAGPS.cpp as an example, you should have something like this
//----------------------------------------------------------------
// Garmin Proprietary NMEA Sentence strings (alphabetical)
#if defined(GARMINGPS_PARSE_F) | defined(NMEAGPS_RECOGNIZE_ALL)
static const char garminF[] __PROGMEM = "F";
#endif
static const char * const grm_nmea[] __PROGMEM =
{
#if defined(GARMINGPS_PARSE_F) | defined(NMEAGPS_RECOGNIZE_ALL)
garminF,
#endif
};
const NMEAGPS::msg_table_t GarminNMEA::garmin_msg_table __PROGMEM =
{
GarminNMEA::PGRM_FIRST_MSG,
(const msg_table_t *) NMEAGPS::nmea_msg_table, // <-- link to standard message table
sizeof(garmin_nmea)/sizeof(garmin_nmea[0]),
garmin_nmea
};
Then your msg_table
function in grmNMEA.h
should look like this:
NMEAGPS_VIRTUAL const msg_table_t *msg_table() const
{ return &garmin_msg_table; };
This will allow the NMEAGPS base class to detect the PGRMF
sentence, in this way:
parseCommand
function see the initial 'P' character that indicates that a proprietary sentence is coming. parseCommand
will call the parseMfrID
to handle the next 3 characters'parseMfrID
function (returning true
indicates it matched).NMEAGPS::parseCommand
finds the 'F' character in your garmin_msg_table
and sets the nmea_message
variable to PGRMF
(your enum type)parseField
, which sees the PGRMF
message type, and forwards them to your parseF
function,parseF
calls various field parsers, including your parseLeapSeconds
.You should also have a grmNMEA_cfg.h
file with a single configuration item. Using PUBX_cfg.h as an example,
#ifndef GRMNMEA_CFG_H
#define GRMNMEA_CFG_H
//------------------------------------------------------------
// Enable/disable the parsing of specific proprietary NMEA sentences.
//
// Configuring out a sentence prevents its fields from being parsed.
// However, the sentence type may still be recognized by /decode/ and
// stored in member /nmeaMessage/. No valid flags would be available.
//#define GARMINGPS_PARSE_F
#endif
Include this at the top of grmNMEA.h
, just likeubxNMEA.h
:
#ifndef _GRMNMEA_H_
#define _GRMNMEA_H_
// Copyright (C) 2014-2017, SlashDevin
//
// This file is part of NeoGPS
//
// NeoGPS is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NeoGPS is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with NeoGPS. If not, see <http://www.gnu.org/licenses/>.
#include "NMEAGPS_cfg.h"
// Disable the entire file if derived types are not allowed.
#ifdef NMEAGPS_DERIVED_TYPES
#include "NMEAGPS.h"
#include "GRMNMEA_cfg.h"
#if !defined(NMEAGPS_PARSE_PROPRIETARY)
#error NMEAGPS_PARSE_PROPRIETARY must be defined in NMEAGPS_cfg.h in order to parse PGRM messages!
#endif
#if !defined(NMEAGPS_PARSE_MFR_ID)
#error NMEAGPS_PARSE_MFR_ID must be defined in NMEAGPS_cfg.h in order to parse PGRM messages!
#endif
//=============================================================
// NMEA 0183 Parser for Garmin GPS Modules.
//
// @section Limitations
// Very limited support for Garmin proprietary NMEA messages.
// Only NMEA messages of types F are parsed (i.e., $PGRMF).
//
class GarminNMEA : public NMEAGPS
{
GarminNMEA( const GarminNMEA & );
public:
GarminNMEA() {};
/** Garmin proprietary NMEA message types. */
enum grm_msg_t {
PGRMF = NMEA_LAST_MSG+1,
PGRM_END
};
static const nmea_msg_t PGRM_FIRST_MSG = (nmea_msg_t) PGRMF;
static const nmea_msg_t PGRM_LAST_MSG = (nmea_msg_t) (PGRM_END-1);
protected:
bool parseMfrID( char chr )
...
In NMEAGPS_cfg.h, what do I need to set #define LAST_SENTENCE_IN_INTERVAL NMEAGPS:: to?
You should do this in NMEAGPS_cfg.h:
#define LAST_SENTENCE_IN_INTERVAL (NMEAGPS::nmea_msg_t)(NMEAGPS::NMEA_LAST_MSG+1)
// NOTE: For derived parser types, like Garmin, PUBX and UBX configs, use
// (NMEAGPS::nmea_msg_t)(NMEAGPS::NMEA_LAST_MSG+1)
This will set the LAST_SENTENCE to the enum in your grmNMEA.h file.
Voilà!
Please feel free to ask for further clarification. I can use this to develop better documentatin for others trying to do the same thing. And if you're willing, I would be happy to add your new class to the distribution. _____ EDIT: 3 occurrences of PUBX changed to PGRM.
Maybe this was more than you wanted to tackle? :)
I just added $PGRMF support for you. See example PGRM.ino
@hiltswaltts, I do have two further questions. You said,
Specifically I want to extract the GPS-UTC leap second offset from a Garmin $PGRMF sentence so that my project can accurately calculate the absolute number of elapsed seconds from a specific date/time, even after additional leap seconds are inserted.
The date/time provided by all GPS receivers is already UTC. It is offset from the GPS time by the current number of GPS leap seconds. The NeoGPS fix.dateTime
is a UTC date/time, so are you sure that you need the GPS leap seconds?
So far, I have only needed this when a GPS message contained a GPS date/time only (i.e. GPS week number + seconds/milliseconds since start of week), no UTC date/time. The $PGRMF message contains a UTC time, so you would not need leap seconds to offset fix.dateTime
.
The only way to calculate an exact number of seconds between any two dates is to have a table that lists all these leap seconds and when they were implemented. Generally, you would need a table of all UTC discontinuities (e.g., including Gregorian/Julian changeover). Is that what you are doing?
Even then, will your program also detect new leap seconds as they are inserted? You would have to record the implementation timestamp in some non-volatile way.
Specifically I want to extract the GPS-UTC leap second offset from a Garmin $PGRMF sentence so that my project can accurately calculate the absolute number of elapsed seconds from a specific date/time, even after additional leap seconds are inserted.
Using Adafruit's GPS_HardwareSerial_EchoTest.ino, I've been able to confirm that, when it's enabled, $PGRMF is the last sentence to be transmitted my by my Garmin 18x LVC
I think I understand how to add leap seconds as a new fix member from the closed issue, "How to add data members to fix?," but I'm having trouble understanding exactly what I need to do to add a parser because the suggested examples in the documentation and all the relevant closed issues I could find have to do with with u-blox, which I'm not using, so I'm not clear on how relevant those are or are not to my situation.
So, the general questions I have at this point are:
#define LAST_SENTENCE_IN_INTERVAL NMEAGPS::
to?$P
, do I want to enableNMEAGPS_PARSE_PROPRIETARY
, or is that strictly for parsing theMFR_ID
andTALKER_ID
?#define NMEAGPS_PARSE_RMF
to NMEAGPS_cfg.h, or do I want to make a separate file, as in the u-blox examples?