GPS - NMEA decoder $GPGSV

I change nothing in the order of declaration in sketch you write and give me.

-dev:
The lastSatellites array should be declared above signal_block and TableSatellitesInView:

In signal_block and TableSatellitesInView I just replace gps.satellites by lastSatellites and I got error..

-dev:
... and signal_block and TableSatellitesInView should use lastSatellites instead of gps.satellites:

void signal_block()

{ int colo, cuid, cuva, floo, idsa, luva, posx, posy, unit, valu;
 const int widt  = 320;                          // screen (x left to right)
 const int high  = 240;                          // screen (y top to bottom)
      ...
   if (i < nbsat) {
     // draw the next satellite
     idsa = lastSatellites[ i ].id;  <-- NOT gps.satellites
     luva = lastSatellites[ i ].snr;



... and:



void TableSatellitesInView()
{
 int i = 0;
    ...
 for ( i=0; i < nbsat; i++) {
   print( lastSatellites[i].id       , 3 );   <- NOT gps.satellites
   print( lastSatellites[i].elevation, 3 );
   print( lastSatellites[i].azimuth  , 4 );
   if (lastSatellites[i].tracked)
     print( lastSatellites[i].snr, 3 );
   else
     DEBUG.print( F(" - ") );

It is what I did and got error.

-dev:
Hmm... I don't think you should modify the gps.satellites array as you insert each element into the lastSatellites array:

      nbsat = gps.sat_count;

for(uint8_t i = 0 , j; i < nbsat; i++)
     {  
        for(j = i ; (j > 0) && (lastSatellites[j-1].snr < gps.satellites[i].snr); j--)   <-- parens, comparison
        {  
            lastSatellites[j] = lastSatellites[j-1];    <-- move element
        }
        lastSatellites[j] = gps.satellites[i];  // store element here  !!!!<--- not for order !!!!
     }

In Loop() I create a variable tempoSatellite and I ordered by snr desc lastSatellites.
Because I have error and can't use lastSatellites, I order also gps.satellites.
Maybe have you a best or faster method to reorder ?

I do not know C, and I think in PHP to write for Arduino. But in PHP exist array.sort arraykeys.sort and associative array.
I read a lot of on web to replace in C what I have in PHP.

I use google about gps change baud rate and google translate.

I think I understood it is possible to change (volatil) or config (permanent) baud rate and select NMEA sentence through sketch.
https://forum.u-blox.com/index.php?qa=11949&qa_1=configuration-of-gy-neo6mv2

http://www.rei-labs.net/using-and-programming-a-neo-6-gps-receiver-module/3/
All the commands that are available in uCenter can also be input “manually” using the binary UBX protocol. The protocol is well documented in the module manual. You can also observe the communication in u-center by opening View->Packet Console/Binary Console/Text Console/Messages View windows while doing some configuration. I guess this is a bit more on advanced side but can be useful if you want to change the module configuration on-the-fly from your own program.

Before explose my mint, did you have already tested this ?
http://www.gpsinformation.org/dale/nmea.htm#sirf

Here's a complete sketch with your insertion sort. Notice that I defined a C++ "operator <" function that compares one satellite_view_t structure to another:

static bool operator <
  ( NMEAGPS::satellite_view_t const & l, NMEAGPS::satellite_view_t const & r )
{
  // This operator compares two satellites views.  It returns true if
  //   the left satellite view is less than the right satellite view.
  //   This determines how the satellite array is sorted.

  return
     (!l.tracked &&  r.tracked) ||
    (( l.tracked &&  r.tracked) && (l.snr < r.snr))   ||
    ((!l.tracked && !r.tracked) && (l.id  < r.id));
}

This is used by the insertion sort. If you ever want to change the criteria for sorting, just change that operator function.

...gps change baud rate... Before explose my mint, did you have already tested this ?

"Explode my mind?"

Yes, NeoGPS has an example program to change the baud rate of ublox GPS devices. The attached sketch includes the command to switch from 9600 to 38400.

This version can use INTERRUPT_PROCESSING commented or uncommented. I was not losing any satellites from the Table when INTERRUPT_PROCESS is defined (uncommented). I would occasionally lose some satellites from the Table when INTERRUPT_PROCESS is not defined (commented).

neutrinos-42.ino (18.1 KB)

-dev:
"Explode my mind?"

I am sorry ! google translate ... I want said explode my brain !

I need 1H00 to understand how do you do to order lastSatellittes.

I only understand static bool operator < but I do not know why and how !
It is same for DEL : Volt + mA + Ohms + DEL = light, but what append in DEL ? It's same as your function.

This WE I can't test your neutrinos42.ino and I have a question "why static bool operator < do not pertub the other comparison operator < ?" I 'll ask google later...

Your are the best of the best.

I read all file.md in"extra\doc" and I find nothing about $GPGSA sentence.
How can I see third parse about 3D fix (values include: 1 = no fix, 2 = 2D fix, 3 = 3D fix) ?

It is same for DEL : Volt + mA + Ohms + DEL = light, but what append in DEL ?

I have no idea what you are trying to say.

I read all file.md in"extra\doc" and I find nothing about $GPGSA sentence.

It is a column in this table. The GSA sentence is for getting the Degrees Of Precision (a relative quality indicator).

How can I see third parse about 3D fix (values include: 1 = no fix, 2 = 2D fix, 3 = 3D fix) ?

The fix structure has a status member:

  if (fix.status == gps_fix::STATUS_STD)   // 2D or 3D fix

You can tell if it has at least a 2D fix from this:

  if (fix.valid.location)   // 2D or 3D fix

You can tell if it has a 3D fix from this:

  if (fix.valid.altitude)   // 3D fix

You can tell if it has a higher-precision 3D fix from this:

  if (fix.status == gps_fix::STATUS_DGPS)   // Differential fix

This is only true for certain satellite constellations with SBAS corrections. You may never see DGPS status.

You do not need the GSA sentence for fix.status. It will be set by other sentences (see that table).

What are you trying to do? Checking fix.valid.location and fix.valid.altitude are usually sufficient.

fix.status give me only 0 or 3.

In GSA sentense

 $GPGSA,A,3,04,05,,09,12,,,24,,,,,2.5,1.3,2.1*39

Where:
     GSA      Satellite status
     A        Auto selection of 2D or 3D fix (M = manual) 
     3        3D fix - values include: 1 = no fix
                                       2 = 2D fix
                                       3 = 3D fix
     04,05... PRNs of satellites used for fix (space for 12) 
     2.5      PDOP (dilution of precision) 
     1.3      Horizontal dilution of precision (HDOP) 
     2.1      Vertical dilution of precision (VDOP)
     *39      the checksum data, always begins with *

I want the third parse. In this exemple is it 3 but value can be 1 or 2 or 3.


How can I make a sketch with inside a specific configuration without modify the files NMEAGPS_cfg , GPSfix_cfg, as I do with

#include <NeoSWSerial.h>
 NeoSWSerial gpsPort( 12, 11 );

in stead ofGPSport.h

maybe something like this if I just want see all values from RMC sentence in serial monitor

#include <NMEAGPS.h>

NMEAGPS  gps; // This parses the GPS characters
gps_fix  fix;      // This holds on to the latest values

#define NMEAGPS_PARSE_RMC
#define RX_PIN 12
#define TX_PIN 11

#include <NeoSWSerial.h>
NeoSWSerial gpsPort( 12, 11 );

// surely other define and include

void setup(){
.../...}
void loop(){
.../...}

About
It is same for DEL : Volt + mA + Ohms + DEL = light, but what append in DEL ?.

I wanted said that I know than an LED with a resistance to limit the current connected to a voltage gives light, but I am unable to explain why.
I know how, it's already not so bad.
It's same with your function...

I tried to understand how NMEAGPS parses the GPS characters, I did not.

I want the third parse field.

Why? That information is available from fix fields:

  • (fix.status == 0) is the same as value 1

  • (fix.valid.location && !fix.valid.altitude) is the same as value 2

  • (fix.valid.location && fix.valid.altitude) is the same as value 3

The NMEA sentences have a lot of duplicated information. In this case, field 3 of the GSA sentence is a duplicate of other fields in GGA, GLL and RMC sentences. GSA uniquely provides DOPs. If you don't need DOPs, don't enable the GSA.

How can I make a sketch with inside a specific configuration without modify the files NMEAGPS_cfg , GPSfix_cfg,

The IDE doesn't allow that. However...

I think you can put a complete copy of the NeoGPS src files in your Sketch directory, and remove the entire NeoGPS directory from Arduino/Libraries. I haven't tried to do this for a while. You might need to put the files in a Sketch/Libraries/NeoGPS subdirectory. Then edit those config files.

I tried to understand how NMEAGPS parses the GPS characters, I did not.

You would have to understand Finite-State Machines and several advanced C/C++ language features. Like Ohm's Law (and all the rest of the Arduino code), you don't have to understand "why" in order to use it.

Some of the complexity is due to the poor design of NMEA sentences. Some is due to the configurability, speed and size optimizations.

-dev:
Why? That information is available from fix fields:

  • (fix.status == 0) is the same as value 1

  • (fix.valid.location && !fix.valid.altitude) is the same as value 2

  • (fix.valid.location && fix.valid.altitude) is the same as value 3

Because I made 3 icons named status1 status2 status3 and I use a select case for 1 2 3.

Maybe you can add value 2 in fix.status ?

I find some thing in NMEAGPS.cpp ...

bool NMEAGPS::parseGSA( char chr )
{
 #ifdef NMEAGPS_PARSE_GSA

   switch (fieldIndex) {
     case 2:
       if (chrCount == 0) {
         if ((chr == '2') || (chr == '3')) {                // value I need to use :-)
           m_fix.status =  gps_fix::STATUS_STD;
           m_fix.valid.status = true;
         } else if (chr == '1') {
           m_fix.status = gps_fix::STATUS_NONE;
           m_fix.valid.status = true;
         }
       }
       break;

     case 15: return parsePDOP( chr );
     case 16: return parseHDOP( chr );
     case 17: return parseVDOP( chr );
        #if defined(GPS_FIX_VDOP) & !defined(NMEAGPS_COMMA_NEEDED)
          #error When GSA and VDOP are enabled, you must define NMEAGPS_COMMA_NEEDED in NMEAGPS_cfg.h!
        #endif

     #ifdef NMEAGPS_PARSE_SATELLITES

       // It's not clear how this sentence relates to GSV and GGA.  
       // GSA only allows 12 satellites, while GSV allows any number.
       // GGA just says how many are used to calculate a fix.

         case 1: break; // allows "default:" case for SV fields

       // GGA shall have priority over GSA with respect to populating the
       // satellites field.  Ignore the satellite field if GGA is enabled.
       #ifndef NMEAGPS_PARSE_GGA
         case 2: return parseSatellites( chr );
       #endif

       // GSV shall have priority over GSA with respect to populating the
       // satellites array.  Ignore the satellite fields if GSV is enabled.
       #ifndef NMEAGPS_PARSE_GSV
         case 3:
           if (chrCount == 0) {
             sat_count = 0;
             comma_needed( true );
           }
         default:
           if (chr == ',') {
             if (chrCount > 0) {
               sat_count++;
             }
           } else
             parseInt( satellites[ sat_count ].id, chr );
           break;
       #endif
     #endif
   }
 #endif

 return true;

} // parseGSA



bool NMEAGPS::parseFix( char chr )
{
 if (chrCount == 0) {
   NMEAGPS_INVALIDATE( status );
   bool ok = true;
   if ((chr == '1') || (chr == 'A'))
     m_fix.status = gps_fix::STATUS_STD;
   else if ((chr == '0') || (chr == 'N') || (chr == 'V'))
     m_fix.status = gps_fix::STATUS_NONE;
   else if ((chr == '2') || (chr == 'D'))
     m_fix.status = gps_fix::STATUS_DGPS;
   else if ((chr == '6') || (chr == 'E'))
     m_fix.status = gps_fix::STATUS_EST;
   else
     ok = false;
   if (ok)
     m_fix.valid.status = true;
 }

 return true;

} // parseFix

In some days I think I can add something somewhere giving me value of 3rd field...
I just need to understand how it work !

All GPS I know display only the 12th best SNR, theese information are in GSA. It is easier and perhaps faster than decode GSV...
and gps 1 gps 2 gps 3 gps 4 gps 5 (not show to keep place)

-dev:
You would have to understand Finite-State Machines and several advanced C/C++ language features. Like Ohm's Law (and all the rest of the Arduino code), you don't have to understand "why" in order to use it.

Some of the complexity is due to the poor design of NMEA sentences. Some is due to the configurability, speed and size optimizations.

I learn Ohm's Law to use DEL, but I am unable to explain (chimical/electronic) why DEL light.

If I am not educated enough to design a car I can still replace a manual glass by an electric one.

For Arduino it's the same. I do not just copy / paste the sketch that I find.

Images:

Because I made 3 icons named status1 status2 status3 and I use a select case for 1 2 3.

Ok, just do this:

if (fix.valid.location && !fix.valid.altitude)
  showIcon( status2 );
else if (fix.valid.location && fix.valid.altitude)
  showIcon( status3 );
else // no fix
  showIcon( status1 );

This will work even if you disable GSA and GSV. You don't have to parse either sentence to know that the GPS has a 2D or 3D fix, or NO fix. The GGA sentence provides the same information in its "fix quality" field, the "lat/lon" fields (could be empty, which means no location fix), and the "altitude" field (could be empty, which means 2D fix only).

This is an example of Information Hiding, a good characteristic of software components. That's why NeoGPS merges the information from all enabled sentences into one fix structure. Sketches don't need to know anything about the various sentences (or their fields), nor do they have to ignore/skip duplicated information between sentences. Exactly once per second, a coherent fix structure becomes available. It contains all the unique information contained in all the fields. The GSA sentence field 3 is not unique information -- it is a duplicate of information in the GGA sentence.

Regarding data from GPS, what I would like to do and that works poorly with a menu and touchsreen:

choice_1 : GPS select choice

choice_11 : GPS show fix value 1 or 2 or 3 (or 0, 1, 2 for fix.status false or true) from $GPGSA field 3
choice_12 : GPS show snr from $GPGSV
choice_13 : GPS show date & time from $GPRMC (UTC and LOCAL)
choice_14 : GPS show location (lon & lat & alt) from $GPGGA and accurency(meter) from $GP???
choice_15 : GPS show speed from $GPRMC

choice_X : otherX

choice_XY : otherXY

choice_XYZ : otherXYZ

If no choice made at choice_1 after 10 seconds do automatically:

  • as long as value from $GPGSA field 3 != 3 make choice_11
  • if speed> 5km / h make choice_15
  • else do
  • choice_12 for 30 seconds
  • choice_14 pandant 20 seconds
  • choice_13 for 15 seconds

For choice_12 or choice_13 or choice_14 or choice_15
while value from $GPGSA field 3 != 3
display "wait a moment please! I am looking for ..."
make choice_11
end while
end for

If duration choice_X > 5 minutes and speed > 5 km/h
If speed > 5km/h make choice_15
If speed <= 5 km/h go back to choice_X
end if

My difficulty is I take too much time in my tests because sometime I have snr = 0 or 0 gps in view or 0 gps in use.

Can you help me how to properly use your librairy?

I know I should use

    if (fix.valid.location) 
    { Serial.print( F("location: ") ); 
      Serial.print( fix.latitude(), 6 );
      Serial.print( ',' );
      Serial.print( fix.longitude(), 6 );
    }

    if (fix.valid.altitude)
    {  Serial.print( F(", Altitude (m): ") );
       Serial.print( fix.altitude() );
    }


    if(fix.valid.speed)
    {  Serial.print( F(",speed (Km/h): ") );
       Serial.print( fix.speed_kph()); 
    }
    
    if (fix.valid.time && fix.valid.date)
    {  Serial.print( F(", Time and Date : ") );
       Serial.print(fix.dateTime.hours);
       Serial.print( F(":") );
       Serial.print(fix.dateTime.minutes);
       Serial.print( F(":") );
       Serial.print(fix.dateTime.seconds);
       Serial.print( F("  ") );
       Serial.print(fix.dateTime.date);
       Serial.print( F("/") );
       Serial.print(fix.dateTime.month);  
       Serial.print( F("/") );
       Serial.print(fix.dateTime.year); 
       
    }


    signal_block() // my function

-dev:
Here's a complete sketch with your insertion sort.

Yes, NeoGPS has an example program to change the baud rate of ublox GPS devices. The attached sketch includes the command to switch from 9600 to 38400.

This version can use INTERRUPT_PROCESSING commented or uncommented. I was not losing any satellites from the Table when INTERRUPT_PROCESS is defined (uncommented). I would occasionally lose some satellites from the Table when INTERRUPT_PROCESS is not defined (commented).

I have problem with sketch netrinos-42.ino you give me 14/07/2017

I just use this today

   if (fix.valid.time && fix.valid.date) {
    DEBUG.print( F("     time and date : ") );
    printTime( fix.dateTime );
    DEBUG.print( ' ' );
    printDate( fix.dateTime );
    // We have a valid GPS date/time, shift it to local+DST
    adjustTime( fix.dateTime );
    DEBUG.print( F(" new time and date : ") );
    printTime( fix.dateTime );
    DEBUG.print( ' ' );
    printDate( fix.dateTime );
   }

and in serial monitor

time and date : 12:23:45 saturday 23 july
new time and date : 14:23:45 sunday 23 july

in your function

void syncRTCwithGPS()
{
  //  Set the RTC from the GPS or vice versa

  if (fix.valid.time && fix.valid.date) {
    // We have a valid GPS date/time, shift it to local+DST
    adjustTime( fix.dateTime );

    // Set the RTC time once per hour
    if (fixCount - lastRTCset > RTC_UPDATE_INTERVAL) {
      lastRTCset = fixCount; // wait another hour

      DateTime now( fix.dateTime.year , fix.dateTime.month  , fix.dateTime.date,
                    fix.dateTime.hours, fix.dateTime.minutes, fix.dateTime.seconds );
      if (RTC.isrunning())
        RTC.adjust( now );
      DEBUG.println( F("RTC updated with GPS time") );
    }

  } else  if (RTC.isrunning()) {

    //  We don't have a valid GPS date/time, get it from the RTC
    //     (it is already adjusted to local+DST).
    DateTime now = RTC.now();

    fix.dateTime.date       = now.day();
    fix.dateTime.month      = now.month();
    fix.dateTime.year       = now.year();
    fix.dateTime.hours      = now.hour();
    fix.dateTime.minutes    = now.minute();
    fix.dateTime.seconds    = now.second();
    fix.valid.date = true;
    fix.valid.time = true;
  }

} // syncRTCwithGPS

If I use my arduino before the last day of DST and I reuse my arduino after the previous last day of DST, I need waiting (fix.valid.time && fix.valid.date) = true to have a valid date hour.
If I am inside my home, I can wait long time ... isn't it ?

Is it possible to :

  • store in RTC RAM (data storage nonvolatile - battery backed) the value of ((spring <= seconds) && (seconds < fall))
  • when arduino switch on, compare value stored in RTC RAM with actual ((spring <= seconds) && (seconds < fall))
  • if needed run adjustTime() function and update new value in RTC RAM ?
     time and date : 12:23:45 saturday 23 july

new time and date : 14:23:45 sunday 23 july



If I use my arduino before the last day of DST and I reuse my arduino after the previous last day of DST

Yes, those are both my mistakes. :frowning:

Is it possible to :

  • store in RTC RAM (data storage nonvolatile - battery backed) the value of ((spring <= seconds) && (seconds < fall))
  • when arduino switch on, compare value stored in RTC RAM with actual ((spring <= seconds) && (seconds < fall))
  • if needed run adjustTime() function and update new value in RTC RAM ?

No, I think you should recalculate the spring and fall variables from the RTC year, when no GPS is available. When the RTC or GPS year changes (DEC 31 to JAN 1), the spring and fall variables would be incorrect. Checking for 0 (Arduino reset) or a year change (via RTC or GPS) is the way to set those variables.

what I would like to do and that works poorly with a menu and touchsreen:

You need to use a "non-blocking" approach. Write your sketch so it never delays, and it never stays in a routine/loop, waiting for a key press.

Instead, use a "currentMenu" variable to do something when a new GPS fix is ready:

bool autoDisplay;
uint16_t fixCount;
uint16_t selectedFix;

enum MenuSelection
  { CHOICE_1, CHOICE_11, CHOICE_12, CHOICE_13, CHOICE_14, CHOICE_15... };

MenuSelection currentMenu;

void loop()
{
  // Always check for GPS data!
  while (gps.available( gpsPort )) {
    fix = gps.read();
    fixCount++;

    newFixReceived();
  }

  // Always check for key presses!
  if (key pressed) {
    switch (currentMenu) {
      case CHOICE_1:
        autoDisplay = false;
        selectGPS( key );
        break;

      case CHOICE_xxx:
        // what to do for each menu?
        break;
    }
  }
      
}

void selectGPS( key )
{
  switch (key) {
    case 1: displayChoice11(); break;
    case 2: displayChoice12(); break;
    case 3: displayChoice13(); break;
    case 4: displayChoice14(); break;
    case 5: displayChoice15(); break;
  }
}

void newFixReceived()
{
  switch (currentMenu) {
    case CHOICE_1:
      if ((fixCount - selectedFix) >= 10) {
        selectedFix = fixCount;

        if (fix.valid.location && fix.valid.altitude)
          displayChoice11();
        else if (fix.valid.speed_kph() > 5.0)
          displayChoice15();
        else {
          autoDisplay = true;
          displayChoice12(); // start on this page
        }
      }
      break;

    case CHOICE_11:
      showStatus();
      break;

    case CHOICE_12:
      if (autoDisplay && ((fixCount - selectedFix) >= 30) {
        // Automatically go to the next choice
        selectedFix = fixCount;
        displayChoice14();
      } else
        showSNR();
      break;

    case CHOICE_13:
      if (autoDisplay && ((fixCount - selectedFix) >= 15) {
        // Automatically go to the next choice
        selectedFix = fixCount;
        displayChoice12(); // back to 12
      } else
        showDateTime();
      break;
      
    case CHOICE_14:
      if (autoDisplay && ((fixCount - selectedFix) >= 20) {
        // Automatically go to the next choice
        selectedFix = fixCount;
        displayChoice13();
      } else
        showLatLonAltAcc();
      break;
      ...
  } // switch

} // newFixReceived


void displayChoice11()
{
  draw some things...
  showStatus();
  currentMenu = 11;
}

void showStatus()
{
  if (fix.valid.location && !fix.valid.altitude)
    showIcon( status2 );
  else if (fix.valid.location && fix.valid.altitude)
    showIcon( status3 );
  else // no fix
    showIcon( status1 );
}


void displayChoice12()
{
  draw some things...
  showSNR();
  currentMenu = 12;
}

void displayChoice13()
{
  draw some things...
  showDateTime();
  currentMenu = 13;
}

void displayChoice14()
{
  draw some things...
  showLatLonAltAcc();
  currentMenu = 14;
}

void showLatLonAltAccuracy()
{
  draw fix.latitude() somewhere
  draw fix.longitude() somewhere
  draw fix.altitude() somewhere
  draw fix.lat_err_cm() somewhere
  draw fix.lon_err_cm() somewhere
  draw fix.alt_err_cm() somewhere
}

It uses fixCount just like millis(), except it counts seconds. selectedFix is like a timestamp you compare with fixCount to measure elapsed seconds.

Also notice that it does not know anything about sentences. The fix contains all the fields from all the sentences that were received during the 1-second update interval.

You must enable the GST sentence to receive the error estimates.

Thank you for your completed answer.

I will understand it later.

I have an other problem it is about order sat by traked, snr, id.
It is ok for tracked sat, but not for untracked (gray color)

undorder_untracked.jpg

static bool operator <
  ( NMEAGPS::satellite_view_t const & l, NMEAGPS::satellite_view_t const & r )
{
  //   This operator compares two satellites views.  It returns true if
  //   the left satellite view is less than the right satellite view.
  //   This determines how the satellite array is sorted.

  return
     (!l.tracked &&  r.tracked) ||
    (( l.tracked &&  r.tracked) && (l.snr < r.snr))   ||
    ((!l.tracked && !r.tracked) && (l.id  < r.id));
}//  bool operator


  if (different)
      {  nbsat = gps.sat_count;
         for(uint8_t i = 0; i < nbsat; i++)        // Save the new satellites array
         {  uint8_t j = i;
            while ( (j > 0) &&
                    (lastSatellites[j-1] < gps.satellites[i]) // <-- "operator <" used
                  )
           {
              lastSatellites[j] = lastSatellites[j-1];  // move element forward
              j--;
           }
           lastSatellites[j] = gps.satellites[i];  // store element here
         }
      }

It is ok for tracked sat, but not for untracked (gray color)

Yes, just change the operator:

static bool operator <
  ( NMEAGPS::satellite_view_t const & l, NMEAGPS::satellite_view_t const & r )
{
  //   This operator compares two satellites views.  It returns true if
  //   the left satellite view is less than the right satellite view.
  //   This determines how the satellite array is sorted.

  return
     (!l.tracked &&  r.tracked) ||
    (( l.tracked ==  r.tracked) && (l.snr < r.snr)); // both tracked or not tracked, compare SNR

}//  bool operator

I think the RTC should be in UTC. The GPS time can be used to set the RTC time, without adjusting. Then the time read from both GPS and the RTC should be adjusted for local and DST time. It wouldn't matter when you set the RTC, because you would always recalculate sprint and fall from the current RTC year (in UTC). That would fix the RTC issues.

neutrinos:
In most of the United States Daylight Saving Time begins at 2:00 a.m. local time on the second Sunday in March. On the first Sunday in November areas on Daylight Saving Time return to Standard Time at 2:00 a.m.

In Europe Daylight Saving Time is commonly referred to as Summer Time. Summer Time begins at 1:00 a.m. UTC (GMT) on the last Sunday in March. On the last Sunday in October areas on Summer Time (Daylight Saving Time) return to Standard Time at 1:00 am UTC (GMT).

neutrinos:
About Daylight Saving Time, few days ago I write this to verify my DLST function.

boolean DLST(int y, byte m, byte d, byte h=2)

{ // before 1976 it is not always last Sunday of March and October at 01h00 UTC
 byte s = (31 - (5 * y /4 + 4) % 7);  // spring
 byte a = (31 - (5 * y /4 + 1) % 7);  // autumn
 if(((m>3)&&(m<10))||((m==3)&&(d>s))||((m==3)&&(d==s)&&(h>=1))||((m==10)&&(d<a))||((m==10)&&(d==a)&&(h<1)))
 {return true;} else {return false;}
}

Hello,

With Excell, from 1976 to 2037, I calculate all values and after many tests, reflections, comparisons and verifications I have simplified the calculations to this :

lp = leap_year(year);
dw = day_week("01/01/"+year);

// spring is day in march
spring = dw + lp > 1 ? 33 : 26;
spring -= dw + lp;

// fall is day in october
fall =  dw + lp > 3 ? 36 : 29;
fall -=  dw + lp;

I do not search for USA because I have not mathematical formulas...as given :
byte s = (31 - (5 * y /4 + 4) % 7); // spring
byte a = (31 - (5 * y /4 + 1) % 7); // autumn

I don't know if my formulas are faster